- PR -

C# で COM の WebBrowser を使って mshtml で制御する際に、イベントハンドラーの設定方法が分からない

1
投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-01-19 01:29
フォームを持った Windows アプリケーションで C# から IE(WebBrowser) を制御しています。

私の知識では、このような制御のやりかたにはつぎの2つに大別できると思っています。

(1)
System.Windows.Forms 名前空間の WebBrowser クラスを使い、HTML の解析もこの名前空間のクラス(HtmlDocument/HtmlElement クラスなど)を使うやりかた。

(2)
COM の
SHDocVw.DLL(Microsoft Internet Controls)
MSHTML.DLL(Microsoft HTML Object Library)
の2つを参照設定し、Interop.SHDocVw の WebBrowser クラスを使い、HTML の解析は Microsoft.mshtml 名前空間のクラスを使うやりかた。

ここで(2)の mshtml を使う場合、HTML 要素にイベントハンドラーを設定するやりかたが分かりません。
ちなみに、(1)の System.Windows.Forms を使うやりかたはできています。つぎの擬似コードのような感じです。

コード:
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace WindowsApplication1
{
    public class Hoge
    {
        private void MyHandler(object o, EventArgs ea)
        {
            Console.WriteLine("event");
        }

        public void Foo()
        {
            HtmlElement myElement;
            myElement.AttachEventHandler("onpropertychange", new System.EventHandler(MyHandler));
        }
    }
}



これを(2)用に書き直したいのですが、まず、どのプロパティーやメソッドを使えば良いのかが分かりません。値が変わったことをイベントとして捕まえたいので onpropertychange プロパティーを使えば良いのかとも思ったのですが、実際に代入してみようと試しに null を代入(set)すると、もうそれだけで NotImplementedException が起きてしまいます。ちゃんとしたイベントハンドラーを代入してもおそらくはじかれるように思います。
attachEvent メソッドという、それっぽい名前のメソッドもあるのですが、こっちは(1から数えて)2番目の引数にイベントハンドラーをどう書けば良いのかが皆目見当が付きません。

以下に、(2)のこれらを試行錯誤した擬似コードを示します。

コード:
using System;
using System.Collections.Generic;
// using System.Windows.Forms; // この名前空間は使わない。

namespace WindowsApplication1
{
    public class Hoge
    {
        public void Foo()
        {
            mshtml.HTMLInputElement myElement;

            // null を入れるだけでも NotImplementedException が出るので、使えないのだと思う。
            myElement.onpropertychange = null;

            // (1から数えて)2番目の引数にどう書けば良いか分からない。
            myElement.attachEvent("onpropertychange", ○○○);
        }
    }
}



ちなみに VB(VB6やそれ未満) だったら、(2)と同等のことは、とくに悩まなくてもIDE上で簡単にイベントハンドラーの設定ができ、ちゃんと動かすことができた記憶があります。
今回はこれと同じことを .NET(C#) でやりたいです。

なお(1)も(2)も、上記のコードは雰囲気を示す擬似コードです。完全なサンプルコードを提示しようかとも思ったのですが、ちょっと大変だったのでやめましたが、リクエストがあれば言ってください。(ちゃんとできるかどうかは分かりませんが。)
todo
ぬし
会議室デビュー日: 2003/07/23
投稿数: 682
投稿日時: 2009-01-19 12:25
引用:

私の知識では、このような制御のやりかたにはつぎの2つに大別できると思っています。


第3の方法
System.Windows.Forms 名前空間の WebBrowser クラスを使う。
HTML の解析は Microsoft.mshtml 名前空間のクラスを使う。
http://msdn.microsoft.com/ja-jp/library/system.windows.forms.htmldocument.domdocument.aspx


引用:

これを(2)用に書き直したいのですが、まず、どのプロパティーやメソッドを使えば良いのかが分かりません。



Visual C# .NET アプリケーションでドキュメント イベントを処理する方法
http://support.microsoft.com/kb/312777/ja

を真似すると、HTMLElementEvents2_Eventインターフェースのonpropertychangeイベントかな?
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-01-20 00:43
todoさん、こんにちは。
ご回答ありがとうございます。

引用:

todoさんの書き込み (2009-01-19 12:25) より:
第3の方法
System.Windows.Forms 名前空間の WebBrowser クラスを使う。
HTML の解析は Microsoft.mshtml 名前空間のクラスを使う。
http://msdn.microsoft.com/ja-jp/library/system.windows.forms.htmldocument.domdocument.aspx


なるほど、こういうやりかたもあるのですね。

引用:

todoさんの書き込み (2009-01-19 12:25) より:
Visual C# .NET アプリケーションでドキュメント イベントを処理する方法
http://support.microsoft.com/kb/312777/ja

を真似すると、HTMLElementEvents2_Eventインターフェースのonpropertychangeイベントかな?


これを今、試しています。
まず、このページのサンプルどおりのことは動かすことができました。こういうふうに使うんですね。このサンプルですごく助かりました。

つぎに、特定の HTML 要素(input タグなど)だけに onpropertychange イベントのイベントハンドラーを設定してみましたが、なんとなくうまく動くような感じがします。
コード:
private void iEvent_onpropertychange(mshtml.IHTMLEventObj pEvtObj)
{
}

public void Foo()
{
    mshtml.IHTMLElement myInputElement;
    mshtml.HTMLInputTextElementEvents2_Event iEvent = (mshtml.HTMLInputTextElementEvents2_Event)myInputElement;
    iEvent.onpropertychange += new mshtml.HTMLInputTextElementEvents2_onpropertychangeEventHandler(iEvent_onpropertychange);
}


ただ、mshtml はクラスの種類が異様に多く、デバッガーで一時停止しながらソースコードを書き換えて、どのクラスを使えば良いか試行錯誤しながらやっています。もしも Visual Studio のこの機能(デバッグ時にソースコードを書き換えることができる機能)がないと辛いところです。
ちなみに Visual Studio で、ソースコードを書くときに += を入力すると、イベントハンドラーが自動生成される機能が使えるので、それも便利です。

なお、もうひとつ大きな問題が残っていて、なぜか簡単なサンプルコードなら動くのですが、自分が開発している大きなコードに組み込むと、実行時にエラーは出ないもののイベントがまったく発生しなくなってしまい、悩んでいます。

とりあえず途中経過報告まで。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-01-20 21:57
引用:

unibonさんの書き込み (2009-01-20 00:43) より:
なお、もうひとつ大きな問題が残っていて、なぜか簡単なサンプルコードなら動くのですが、自分が開発している大きなコードに組み込むと、実行時にエラーは出ないもののイベントがまったく発生しなくなってしまい、悩んでいます。


この件についてですが、その後、試行錯誤してみたところ、私が前回書いたコードで言うと、変数 iEvent をローカル変数にしているだけだとダメみたくて、インスタンス変数(メンバー変数)などにして保持しておく措置が必要なように感じました。
まだちょっとあやふやな見解ですが、こうすれば確実にイベントが起きるけど、こうしないとほとんどイベントが起きないような感じがします。大きなコードに組み込むと動かなくなるのも、ガーベッジコレクションの頻度の違いのためなのでしょうか?

私は .NET で COM を使った経験がなくて、この辺の知識がないのですが、本当にこの措置は必要なのでしょうか?

ちなみに、
http://support.microsoft.com/kb/312777/ja
のサンプルでも、これと同様の目的として使っている変数 iEvent は、ローカル変数にしています。これはすなわち axWebBrowser1.Document なので、わざわざ別に保持する必要性はないのかもしれませんが、厳密には、アプリケーションプログラムの側で明示的に保持するべきなのでしょうか?
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-01-29 22:37
引用:

unibonさんの書き込み (2009-01-20 21:57) より:
この件についてですが、その後、試行錯誤してみたところ、私が前回書いたコードで言うと、変数 iEvent をローカル変数にしているだけだとダメみたくて、インスタンス変数(メンバー変数)などにして保持しておく措置が必要なように感じました。
まだちょっとあやふやな見解ですが、こうすれば確実にイベントが起きるけど、こうしないとほとんどイベントが起きないような感じがします。大きなコードに組み込むと動かなくなるのも、ガーベッジコレクションの頻度の違いのためなのでしょうか?


古い話ですが、これについてはやはりこの推測のとおりのようです。自分のメモついでに掲示板にも書かせていただきます。

まとめとして、以下に再現コードを示します。
コード:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace Hoge7
{
    public partial class Form1 : Form
    {
        private mshtml.HTMLInputTextElementEvents2_Event iEventMember;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            axWebBrowser1.Navigate(@"http://www.example.com/hoge.html");
        }

        private void iEvent_onpropertychange(mshtml.IHTMLEventObj pEvtObj)
        {
            Console.WriteLine("value = " + pEvtObj.srcElement.getAttribute("value", 0));
        }

        private void button2_Click(object sender, EventArgs e)
        {
            mshtml.HTMLDocument doc = (mshtml.HTMLDocument)axWebBrowser1.Document;
            mshtml.IHTMLElement input = doc.getElementById("bar");
            mshtml.HTMLInputTextElementEvents2_Event iEventLocal = (mshtml.HTMLInputTextElementEvents2_Event)input;
            iEventLocal.onpropertychange += new mshtml.HTMLInputTextElementEvents2_onpropertychangeEventHandler(iEvent_onpropertychange);
            Console.WriteLine("added.");

            // これをやらないと、イベントの取得ができなくなる。
            // iEventMember = iEventLocal;
        }
    }
}


の Windows Application で、つぎのようなページにアクセスします。

hoge.html はつぎのような内容です。
コード:
<html>
<head>
<script>
var i = 0;
function foo() {
    document.hoge.bar.value = "" + i;
    i++;
}
setInterval("foo()", 1000);
</script>
</head>
<body>
<form name=hoge>
<input type=text id=bar>
</form>
</body>
</html>


このページは1秒おきに input 要素(id=bar)の value が変わるような例です。この変化を onpropertychange で捕まえます。

button1 を押すとページに Navigate して、button2 を押すと onpropertychange のイベントハンドラーが設定されます。

これを動かすと、実行環境にもよるのですが、1〜2回ぐらいのイベントを捕まえた後、すぐにイベントが捕まえられなくなるときもあれば、100回以上大丈夫なときもあります。ウィンドウをマウスでごにょごにょいじっていると止まりやすくなります。

上記でコメントになっている行を有効にしてメンバー変数(フィールド)に保持するようにすれば、イベントの発生が止まることはなくなります。

以下は私の推測ですが、これは別段 COM だからというわけではなく、.NET の段階でもすでにあたりまえのことのようです。

件名:「Timer()クラスについて」
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=38912&forum=7

なども参考にさせていただきました。
1

スキルアップ/キャリアアップ(JOB@IT)