C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ツリー一括表示

複数のIEを操作するには /のり (17/10/13(Fri) 17:35) #85362
New Re[1]: 複数のIEを操作するには /魔界の仮面弁士 (17/10/16(Mon) 19:20) #85382
  └New Re[2]: 複数のIEを操作するには /のり (17/10/16(Mon) 21:40) #85385
    └New Re[3]: 複数のIEを操作するには /魔界の仮面弁士 (17/10/16(Mon) 23:14) #85387
      └New Re[4]: 複数のIEを操作するには /のり (17/10/17(Tue) 00:36) #85388
        └New Re[5]: 複数のIEを操作するには /魔界の仮面弁士 (17/10/17(Tue) 11:05) #85391
          └New Re[6]: 複数のIEを操作するには /魔界の仮面弁士 (17/10/17(Tue) 11:19) #85392


親記事 / ▼[ 85382 ]
■85362 / 親階層)  複数のIEを操作するには
□投稿者/ のり (1回)-(2017/10/13(Fri) 17:35:03)

分類:[VB.NET/VB2005 以降] 

皆さま、お世話になります。
vb.net 2015
Windowsフォームアプリケーションです。

IE操作(webbrowserではない)で、ひとつのフォームアプリで複数のIEを操作したいのですが

今までIEをひとつだけ使用していた時は問題なくできていた

        For Each tags In Ie1.Document.getElementsByClassName ("b_searchbox")

            tags.innerText = "天気"
            Exit For
        Next
のようなクラス検索が、IEを2個で2個目を操作しようとしたときは以下のようなエラーになります。

型 'System.Runtime.InteropServices.COMException' のハンドルされていない例外が Microsoft.VisualBasic.dll で発生しました
追加情報:HRESULT からの例外:0x80020101


複数のIEを操作するときに必要なことが抜けているんだと思いますが私の知識では解決できません。
アドバイスをいただけれるととても助かります。

プログラムはボタンを4個使用します。
[button 1] 1つ目のIEを起動
[button 2] 1つ目のIEに対してテキスト入力

[button 3] 2つ目のIEを起動
[button 4] 2つ目のIEに対してテキスト入力

ボタン1→ボタン2→ボタン3 ボタン4でエラーになります。

ただし立ち上げ後
ボタン3→ボタン4 は問題なくテキスト入力できます。


ボタン1〜4までエラーなく完了できるようしたいです。
どうかよろしくお願いいたします。



以下、プログラム。
'-------------------------------------------------------------------
'vb.net Windowsフォームアプリケーション
'参照設定:Microsoft Internet Controls
'ボタンを4個使用しています。(button1〜4)

Public Class Form1

    'Ie1の宣言
    Public Ie1 As SHDocVw.InternetExplorer
    'Ie2の宣言
    Public Ie2 As SHDocVw.InternetExplorer

    'Ie1起動
    Private Sub Button1_Click(sender As Object, e As EventArgs) _
            Handles Button1.Click

        Ie1 = CreateObject("InternetExplorer.Application")
        Ie1.Visible = True
        Ie1.Navigate("https://www.google.co.jp/")

    End Sub

    'Ie1にテキスト入力
    Private Sub Button2_Click(sender As Object, e As EventArgs) _
    Handles Button2.Click

        For Each tags In Ie1.Document.getElementsByClassName _
        ("gsfi")

            tags.innerText = "グルメ"

            Exit For
        Next

    End Sub

    'Ie2起動
    Private Sub Button3_Click(sender As Object, e As EventArgs) _
    Handles Button3.Click

        Ie2 = CreateObject("InternetExplorer.Application")
        Ie2.Visible = True
        Ie2.Navigate("https://www.bing.com/")

    End Sub

    'Ie2にテキスト入力
    Private Sub Button4_Click(sender As Object, e As EventArgs) _
    Handles Button4.Click

        For Each tags In Ie2.Document.getElementsByClassName _
        ("b_searchbox")

            tags.innerText = "天気"

            Exit For
        Next
    End Sub

End Class

[ □ Tree ] 返信 編集キー/

▲[ 85362 ] / ▼[ 85385 ]
■85382 / 1階層)  Re[1]: 複数のIEを操作するには
□投稿者/ 魔界の仮面弁士 (1425回)-(2017/10/16(Mon) 19:20:23)
No85362 (のり さん) に返信
> tags.innerText = "天気"
相手が input 要素なら、本来は innerText ではなく value だった気も。


> 型 'System.Runtime.InteropServices.COMException' のハンドルされていない例外が Microsoft.VisualBasic.dll で発生しました

本来は小文字始まりの getElementsByClassName なのですが、
あえて大文字始まりの GetElementsByClassName にしてみたところ、
呼び出せることもありました。難読化されていることもあり、原因箇所までは特定できませんでしたが。

For Each tags In CallByName(Ie2.Document, "GetElementsByClassName", CallType.Method, "b_searchbox")
For Each tags In Ie2.Document.GetElementsByClassName("b_searchbox")



> ボタン1→ボタン2→ボタン3 ボタン4でエラーになります。

ボタン5 として、こんな物を書いてみました。

Dim S1 = CallByName(CallByName(Ie1.Document, "getElementsByClassName", CallType.Get), "toString", CallType.Method)
Dim S2 = CallByName(CallByName(Ie2.Document, "getElementsByClassName", CallType.Get), "toString", CallType.Method)
Dim S3 = CallByName(CallByName(Ie2.Document, "GetElementsByClassName", CallType.Get), "toString", CallType.Method)

上記を実行すると、
 S1 = "◆function getElementsByClassName() {◆ [native code]◆}◆"
 S2 = "◆function replaceNode() {◆ [native code]◆}◆"
が得られました。◆ の箇所は vbLf です。


S3 については、S1 と同じ文字列の場合と、S2 と同じ文字列になる場合がありました。
何がトリガーになって切り替わるのかまでは特定できていませんが…。
[ 親 85362 / □ Tree ] 返信 編集キー/

▲[ 85382 ] / ▼[ 85387 ]
■85385 / 2階層)  Re[2]: 複数のIEを操作するには
□投稿者/ のり (2回)-(2017/10/16(Mon) 21:40:04)
No85382 (魔界の仮面弁士 さん) に返信
> ■No85362 (のり さん) に返信

返信ありがとうございます。とてもありがたいです。

>>tags.innerText = "天気"
> 相手が input 要素なら、本来は innerText ではなく value だった気も。

これは知りませんでした。
取り入れてみたいと思います。ありがとうございます。


>>型 'System.Runtime.InteropServices.COMException' のハンドルされていない例外が Microsoft.VisualBasic.dll で発生しました
>
> 本来は小文字始まりの getElementsByClassName なのですが、
> あえて大文字始まりの GetElementsByClassName にしてみたところ、
> 呼び出せることもありました。難読化されていることもあり、原因箇所までは特定できませんでしたが。

すみません、自分では大文字にしてみてもボタン4ではエラーになってしまいました。


> For Each tags In CallByName(Ie2.Document, "GetElementsByClassName", CallType.Method, "b_searchbox")
> For Each tags In Ie2.Document.GetElementsByClassName("b_searchbox")
>
>
>
>>ボタン1→ボタン2→ボタン3 ボタン4でエラーになります。
>
> ボタン5 として、こんな物を書いてみました。
>
> Dim S1 = CallByName(CallByName(Ie1.Document, "getElementsByClassName", CallType.Get), "toString", CallType.Method)
> Dim S2 = CallByName(CallByName(Ie2.Document, "getElementsByClassName", CallType.Get), "toString", CallType.Method)
> Dim S3 = CallByName(CallByName(Ie2.Document, "GetElementsByClassName", CallType.Get), "toString", CallType.Method)
>
> 上記を実行すると、
>  S1 = "◆function getElementsByClassName() {◆ [native code]◆}◆"
>  S2 = "◆function replaceNode() {◆ [native code]◆}◆"
> が得られました。◆ の箇所は vbLf です。


返信していただき、ありがとうございます。
やはり自分の知識では解決できなさそうですが
返信いただけたことはかなり勇気づけられました。

もっと自分でも調べてみたいと思います。


[ 親 85362 / □ Tree ] 返信 編集キー/

▲[ 85385 ] / ▼[ 85388 ]
■85387 / 3階層)  Re[3]: 複数のIEを操作するには
□投稿者/ 魔界の仮面弁士 (1426回)-(2017/10/16(Mon) 23:14:20)
No85385 (のり さん) に返信
>>本来は小文字始まりの getElementsByClassName なのですが、
>>あえて大文字始まりの GetElementsByClassName にしてみたところ、
>>呼び出せることもありました。難読化されていることもあり、原因箇所までは特定できませんでしたが。
>
> すみません、自分では大文字にしてみてもボタン4ではエラーになってしまいました。

レイトバインドで呼び出すのではなく、mshtml を参照設定して、
mshtml.HTMLDocument や mshtml.HTMLInputElement などに
キャストして取り出してみた場合はどうでしょうか。
[ 親 85362 / □ Tree ] 返信 編集キー/

▲[ 85387 ] / ▼[ 85391 ]
■85388 / 4階層)  Re[4]: 複数のIEを操作するには
□投稿者/ のり (3回)-(2017/10/17(Tue) 00:36:49)
No85387 (魔界の仮面弁士 さん) に返信
> ■No85385 (のり さん) に返信

> レイトバインドで呼び出すのではなく、mshtml を参照設定して、
> mshtml.HTMLDocument や mshtml.HTMLInputElement などに
> キャストして取り出してみた場合はどうでしょうか。

返信ありがとうございます。

参照設定に「Microsoft HTML Object Library」を追加して
以下のように書いてみましが、キャストとはこんな感じで良いでしょうか。
(ちょっと待たないとエラーになりそうなので、読み込み待ちを追加しています)

   'Ie1の宣言
    Public Ie1 As SHDocVw.InternetExplorer
    'キャストの宣言
    Public htmlDoc As mshtml.HTMLDocument

    'Ie1起動
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Ie1 = CreateObject("InternetExplorer.Application")
        Ie1.Visible = True
        Ie1.Navigate("https://www.google.co.jp/")


        Do While Ie1.Busy = True Or Ie1.ReadyState <> 4   '読み込み待ちを追加
            Application.DoEvents()
        Loop

        'キャストする
        htmlDoc = Ie1.Document

    End Sub


    'Ie1にテキスト入力
    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click

        For Each tags In htmlDoc.Document.GetElementsByClassName("gsfi")

            tags.value = "グルメ"

            Exit For
        Next

    End Sub


これですと Button1 で開いたIEブラウザが、ずっとグルグルしていて次に進まない状態です。
作り方が間違っているでしょうか。
(基本的なことがわかっていなくてすみません)



[ 親 85362 / □ Tree ] 返信 編集キー/

▲[ 85388 ] / ▼[ 85392 ]
■85391 / 5階層)  Re[5]: 複数のIEを操作するには
□投稿者/ 魔界の仮面弁士 (1428回)-(2017/10/17(Tue) 11:05:21)
No85388 (のり さん) に返信
> (ちょっと待たないとエラーになりそうなので、読み込み待ちを追加しています)

UI スレッドでループ待機するのは、あまり望ましい処理とは言えません。
イベント処理(Timer 等による定期監視なども含む)に置き換えた方が良いでしょう。

「CheckBox1 がチェックされたときに、Label1 の文字を赤くする」という処理のために
  Do Until CheckBox1.Checked
    Application.DoEvents()
  Loop
  Label1.ForeColor = Color.Red
などというコードを書いたりはしないですよね。それと同じことです。



> Do While Ie1.Busy = True Or Ie1.ReadyState <> 4 '読み込み待ちを追加
Or ではなく、OrElse を使う癖を付けた方が良いですよ。

また、「4」のようなマジックナンバーに頼るのではなく、
SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE などの列挙定数が良いと思います。

記述が長くなる分、冗長的ではあるのですが、マジックナンバーのままですと
「4」という値が何を意味するものなのか、意図が伝わりづらいので。


> For Each tags In htmlDoc.Document.GetElementsByClassName("gsfi")
htmlDoc 自体が「document オブジェクト」を表しますので、
そこからさらに「Document プロパティ」にアクセスする必要はありません。

そもそも documet オブジェクトに Document プロパティはありません。
(documentElement プロパティならありますが)


とはいえ、GAC に登録されている MSHTML は、古い実装のままのようです。
手元にある Microsoft.mshtml.dll は、バージョン 7.0.3300.0 の PIA でしたが、
これの HTMLDocument は IHTMLDocument7 を実装していませんでした。
(getElementsByClassName メソッドを実装しているのは IHTMLDocument7 インターフェイスです)


現状、getElementsByClassName を使うとなると、
 (案1)諦めてレイトバインドにする。
 (案2)自前でインターフェイスを明示実装する
 (案3)GAC のものを使わず、mshtml.tlb を tlbimp して取り込みなおす
という選択肢になってくると思います。

もしくは getElementsByClassName メソッドは使わず、PIA でも使える
getElementById / getElementsByName / getElementsByTagName を用いるとか。



ひとまず案3で実装してみました。
ついでに、Button の Enabled を切り替えるコードも追加しています。

とはいえ手抜きコードなので、ReleaseComObject の呼び出しや、
IE 側のスレッドとの同期制御などは省略しています。


Option Strict On
Imports mshtml = Interop.mshtml

Public Class Form1
  Inherits System.Windows.Forms.Form

  Friend togetherClose As Boolean = True

  Public WithEvents Ie1 As SHDocVw.InternetExplorer
  Public WithEvents Ie2 As SHDocVw.InternetExplorer
  Public htmlDoc1 As mshtml.HTMLDocument
  Public htmlDoc2 As mshtml.HTMLDocument

  Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Button1.Enabled = True
    Button2.Enabled = False
    Button3.Enabled = True
    Button4.Enabled = False
  End Sub

  Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Ie1 = New SHDocVw.InternetExplorer()
    Ie1.Visible = True
    Ie1.Navigate("https://www.google.co.jp/")
    Button1.Enabled = False
    Button2.Enabled = True
  End Sub
  Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim elm = htmlDoc1.getElementsByClassName("gsfi").OfType(Of mshtml.HTMLInputElement).FirstOrDefault()
    If elm IsNot Nothing Then
      elm.value = "グルメ"
    End If
  End Sub
  Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
    Ie2 = New SHDocVw.InternetExplorer()
    Ie2.Visible = True
    Ie2.Navigate("https://www.bing.com/")
    Button3.Enabled = False
    Button4.Enabled = True
  End Sub
  Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
    Dim elm = htmlDoc2.getElementsByClassName("b_searchbox").OfType(Of mshtml.HTMLInputElement).FirstOrDefault()
    If elm IsNot Nothing Then
      elm.value = "天気"
    End If
  End Sub

  Private Sub Ie1_DocumentComplete(pDisp As Object, ByRef URL As Object) Handles Ie1.DocumentComplete
    htmlDoc1 = DirectCast(Ie1.Document, mshtml.HTMLDocument)
    BeginInvoke(Sub() Button2.Enabled = True)
  End Sub
  Private Sub Ie2_DocumentComplete(pDisp As Object, ByRef URL As Object) Handles Ie2.DocumentComplete
    htmlDoc2 = DirectCast(Ie2.Document, mshtml.HTMLDocument)
    BeginInvoke(Sub() Button4.Enabled = True)
  End Sub

  Private Sub Ie1_OnQuit() Handles Ie1.OnQuit
    htmlDoc1 = Nothing
    Ie1 = Nothing
    BeginInvoke( _
      Sub()
        Button1.Enabled = True
        Button2.Enabled = False
      End Sub)
  End Sub
  Private Sub Ie2_OnQuit() Handles Ie2.OnQuit
    htmlDoc2 = Nothing
    Ie2 = Nothing
    BeginInvoke( _
      Sub()
        Button3.Enabled = True
        Button4.Enabled = False
      End Sub)
  End Sub
  Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
    If Ie1 IsNot Nothing Then
      htmlDoc1 = Nothing
      If togetherClose Then
        Ie1.Quit()
      End If
      Ie1 = Nothing
    End If
    If Ie2 IsNot Nothing Then
      htmlDoc2 = Nothing
      If togetherClose Then
        Ie2.Quit()
      End If
      Ie2 = Nothing
    End If
  End Sub
End Class
[ 親 85362 / □ Tree ] 返信 編集キー/

▲[ 85391 ] / 返信無し
■85392 / 6階層)  Re[6]: 複数のIEを操作するには
□投稿者/ 魔界の仮面弁士 (1429回)-(2017/10/17(Tue) 11:19:56)
No85391 (魔界の仮面弁士) に追記
> もしくは getElementsByClassName メソッドは使わず、PIA でも使える
> getElementById / getElementsByName / getElementsByTagName を用いるとか。

わざわざタイプライブラリから取り込みなおすのも手間なので、
PIA のままで使えるようにしてみました。

Option Strict On
Partial Public Class Form1
  Inherits System.Windows.Forms.Form
  Friend togetherClose As Boolean = True
  Public WithEvents Ie1 As SHDocVw.InternetExplorer
  Public WithEvents Ie2 As SHDocVw.InternetExplorer
  Public htmlDoc1 As mshtml.HTMLDocument
  Public htmlDoc2 As mshtml.HTMLDocument
  Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Ie1 = New SHDocVw.InternetExplorer()
    Ie1.Visible = True
    Ie1.Navigate("https://www.google.co.jp/")
  End Sub
  Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim elm = htmlDoc1.getElementById("lst-ib")
    If elm IsNot Nothing Then
      DirectCast(elm, mshtml.IHTMLInputElement).value = "グルメ"
    End If
  End Sub
  Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
    Ie2 = New SHDocVw.InternetExplorer()
    Ie2.Visible = True
    Ie2.Navigate("https://www.bing.com/")
  End Sub
  Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
    Dim elm = htmlDoc2.getElementById("sb_form_q")
    If elm IsNot Nothing Then
      DirectCast(elm, mshtml.IHTMLInputElement).value = "天気"
    End If
  End Sub
  '
  '以下、 No85391 と同様のコードが続くので省略。
  '
End Class
[ 親 85362 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -