トップ > Tech > CSharp > WebBrowserコントロールにNewWindow2イベントを実装する

WebBrowserコントロールにNewWindow2イベントを実装する

はじめに

ActiveX の WebBrowser をラップしている System.Windows.Forms.WebBrowser コントロール。非常に便利なのだが、target="_blank" や JavaScript で新しいウィンドウを開く動作をされると、IE が開いてしまう

個人的には新しいウィンドウを開くかどうかはユーザーに委ねるべきで、target="_blank" などはなくなればいいと思っているのだが、利用しているサイトがあるのだから致し方ない。

まぁ、IE が新しく開いても問題ない場合も多いのだろうが、クッキーが維持されないし、開いた先のウィンドウをコントロールするのが面倒なので、どうせならアプリ内で完結したい。

対策

さて、対策はと言うと、概ね下記のようなものである。

  • ActiveX コントロールのもつ NewWindow2 イベントを取得できれば、開く先のウィンドウを指定できる。
  • WebBrowser コントロールには NewWindow2 イベントはない。
  • WebBrowser を継承し、CreateSink メソッドを使って、定義されていないイベントを捕捉できる。
  • これにより NewWindow2 イベントを実装して利用しよう!

というわけである。

Web で情報をかき集めるものの、断片的でなぜか VB.NET のコードが多い。 やっとこさ、あちこちから集めたソースで動くようになったので、記録しておく。 なお、各要素のこまかい説明に関しては参考サイトを参照されたい。

注意点・問題点

  • Vista, .NET 3.5, IE8 環境で動作確認。まったくもって自信がないので、動作保証はしない。
  • はっきり言ってややこしいし、意味不明なので、よくわからない人はあきらめたほうがいい(笑)
  • ppDisp に自身(this.Application)を指定すると、挙動不審になる(表示されなかったり、JS のエラーがでたりする)ので、このコードでは一つのウィンドウに強制的に表示させることはできない(2つ以上の WebBrowser が必要)。

コード

WebBrowserEx(本体)

WebBrowser クラスを継承したクラス。名前は適当に変えましょう。

public class WebBrowserEx : WebBrowser

    #region NewWindow2 イベント関連

    private AxHost.ConnectionPointCookie cookie;
    private WebBrowser2EventHelper helper;

    [DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
    [DispIdAttribute(200)]
    public object Application
    {
        get
        {
            if (this.ActiveXInstance == null)
            {
                throw new AxHost.InvalidActiveXStateException("Application", AxHost.ActiveXInvokeKind.PropertyGet);
            }
            return (this.ActiveXInstance as IWebBrowser2).Application;
        }
    }

    [DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
    [DispIdAttribute(552)]
    public bool RegisterAsBrowser
    {
        get
        {
            if (this.ActiveXInstance == null)
            {
                throw new AxHost.InvalidActiveXStateException("RegisterAsBrowser", AxHost.ActiveXInvokeKind.PropertyGet);
            }
            return (this.ActiveXInstance as IWebBrowser2).RegisterAsBrowser;
        }
        set
        {
            if (this.ActiveXInstance == null)
            {
                throw new AxHost.InvalidActiveXStateException("RegisterAsBrowser", AxHost.ActiveXInvokeKind.PropertyGet);
            }
            (this.ActiveXInstance as IWebBrowser2).RegisterAsBrowser = value;
        }
    }

    [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
    protected override void CreateSink()
    {
        base.CreateSink();
        helper = new WebBrowser2EventHelper(this);
        cookie = new AxHost.ConnectionPointCookie(this.ActiveXInstance, helper, typeof(DWebBrowserEvents2));
    }

    [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
    protected override void DetachSink()
    {
        if (cookie != null)
        {
            cookie.Disconnect();
            cookie = null;
        }
        base.DetachSink();
    }

    public event WebBrowserNewWindow2EventHandler NewWindow2 = (o, e) => { };

    protected virtual void OnNewWindow2(WebBrowserNewWindow2EventArgs e)
    {
        NewWindow2(this, e);
    }

    private class WebBrowser2EventHelper : StandardOleMarshalObject, DWebBrowserEvents2
    {
        private WebBrowserEx parent;

        public WebBrowser2EventHelper(WebBrowserEx parent)
        {
            this.parent = parent;
        }

        public void NewWindow2(ref object ppDisp, ref bool cancel)
        {
            var e = new WebBrowserNewWindow2EventArgs(ppDisp);
            this.parent.OnNewWindow2(e);
            ppDisp = e.ppDisp;
            cancel = e.Cancel;
        }

    }
    #endregion
}

NewWindow2 イベントに必要なクラス群

public delegate void WebBrowserNewWindow2EventHandler(object sender, WebBrowserNewWindow2EventArgs e);

public class WebBrowserNewWindow2EventArgs : CancelEventArgs
{
    public object ppDisp { get; set; }
    
    public WebBrowserNewWindow2EventArgs(object ppDisp)
    {
        this.ppDisp = ppDisp;
    }
}

[ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(TypeLibTypeFlags.FHidden)]
public interface DWebBrowserEvents2
{
    [DispId(251)]
    void NewWindow2(
    [InAttribute(), OutAttribute(), MarshalAs(UnmanagedType.IDispatch)] ref object ppDisp,
    [InAttribute(), OutAttribute()] ref bool cancel);
}

[ComImport, Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWebBrowser2
{
    object Application { get; }
    bool RegisterAsBrowser { get; set; }
}

使用方法

Form の Load イベントなどで下記のようにイベントハンドラを関連づけておく。

webBrowser.NewWindow2 += new WebBrowserNewWindow2EventHandler(webBrowser_NewWindow2);

イベントハンドラの中身は下記のようなもの。newBrowser が新しい WebBrowser コントロールなので、表示したい親フォームの Controls に Add してやればよい。

// 新しくウィンドウを開かれようとするときに発生する
void webBrowser_NewWindow2(object sender, WebBrowserNewWindow2EventArgs e)
{
    // 新しい WebBrowser の初期化
    var newBrowser = new WebBrowserEx();
    newBrowser.Dock = DockStyle.Fill;

    // 新しい WebBrowser のコンテナ(下記はタブの場合)
    //var tabPage = new TabPage();
    //tabPage.Controls.Add(newBrowser);
    //tabControl1.TabPages.Add(tabPage);

    // 新しい WebBrowser に表示させる設定
    e.ppDisp = newBrowser.AxApplication;
    newBrowser.RegisterAsBrowser = true;
    
    // 新しい WebBrowser からさらにウィンドウを開かれるときも同じようにする
    newBrowser.NewWindow2 += webBrowser_NewWindow2;
}

参考

(2010/09/09 15:16:42)
2010/03/40
プロフィール

Kenz Yamada(山田研二)。1984年生。大阪。ちょっとずつ好きなプログラム作ってます。 好きなものはカメラと旅行。ガジェットや身の回り、ちょっとこだわります。 詳しくは Web mixi で。

Bookmark and Share