WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

イベントハンドラ

by WebSurfer 2. July 2012 22:28

C# を使った .NET Framework アプリでのイベントとハンドラの宣言、定義の方法を忘れかけていて余計な時間がかかったので、整理して、備忘録として簡単に書いておきます。

まず、イベントを宣言するには、その前にデリゲート型の宣言が必要です(EventHandler デリゲートのようにクラスライブラリに用意されている場合は不要です)。

C# のデリゲートは、C や C++ の関数ポインタと同じと考えればよいと思います。以下の例は、Object 型と EventArgs 型の引数を持つデリゲート型 ChangedEventHandler の宣言です。

public delegate void 
  ChangedEventHandler(object sender, EventArgs e);

上の構文は、メソッド宣言の構文と同様ですが、delegate キーワードによって、ChangedEventHandler がデリゲート型であることをコンパイラに通知しています。

規約により、.NET Framework のイベントデリゲートは、そのイベントの発生元(上の例では sender)と、そのイベントのデータ(上の例では e)という 2 つのパラメータを持ちます。

デリゲートを定義した後、イベントの実装は以下のように行います。細かい説明はコメントを参照してください。

public class MyClass : ArrayList
{
  // デリゲートを用いてイベントを宣言。クラスの外からは
  // 公開フィールドのように見えるが、アクセスには制限が
  // あり、実行できるのは以下の処理のみ。
  //  ・新しいデリゲートの結合
  //  ・結合されたデリゲートの削除
  public event ChangedEventHandler Changed;

  // イベントを起動するメソッド OnChanged を作成し
  // ておき、必要なタイミングで呼び出す。イベントは、イ
  // ベントを宣言したクラスの中からしか起動できない。そ
  // れゆえ、protected として作成し、派生クラスがイベン
  // トを起動できるようにする。
  protected virtual void OnChanged(EventArgs e)
  {
    // ハンドラがイベントにアタッチされてない場合 null
    // になるので、起動する前に null でない事を確認。
    if (this.Changed != null)
    {
      this.Changed(this, e);
    }
  }

  // 例えば、何かを追加したタイミングでこのイベントを発
  // 生させたいときは、以下のようにする。
  public override int Add(object value)
  {
    int i = 0;
    // 何かの処置
    OnChanged(EventArgs.Empty);
    return i;
  }
}

ここまでの宣言、定義で、MyClass.Add メソッドが起動された時、MyClass.Changed イベントが発生します。

ただし、ハンドラの定義、ハンドラのイベントへのアタッチがないので、それ以上は何も起こりません。

ハンドラの定義とイベントへのアタッチは以下の例のようにします。

public class EventListener
{
  private MyClass _myClass;

  public EventListener(MyClass myClass)
  {
    this._myClass = myClass;
    // ハンドラをイベントにアタッチ
    this._myClass.Changed += 
      new ChangedEventHandler(MyClass_Changed);
  }

  // ハンドラの定義
  private void MyClass_Changed(object sender, EventArgs e)
  {
    // 何かの処理
  }
}

Tags:

.NET Framework

WebBrowser の拡張

by WebSurfer 1. July 2012 18:04

先の記事 SHDocVw.dll と AxSHDocVw.dll の作り方と使い方 では、ActiveX の WebBrowser コントロール (shdocvw.dll) をホストする、AxHost クラス から派生するラッパーコントロールを Visual Studio で生成し、それ使って NewWindow2 イベントを利用する例を書きました。

それと比較するために、.NET Framework の WebBrowser(これも shdocvw.dll のマネージラッパー)を拡張して同様なことを行うコードを書いてみました。

かなり面倒で、最初は COM の相互運用の知識がなかったのでお手上げ状態でした。あちこちググって調べて、動くようになるまで 3 日ぐらいかかりました。(笑)

詳しくは以下のコードとそのコメントを参照してください。参考にしたページの一覧も書いておきます。(手抜きですみません)

WebBrowser.CreateSink メソッド

Extended .NET 2.0 WebBrowser Control

COM相互運用機能の利用

COM相互運用機能の利用 - パート2

Microsoft .NET/COM の移行と相互運用性

COM ラッパー

ランタイム呼び出し可能ラッパー

COM 相互運用性 - 第 1 部 : C# クライアント チュートリアル

方法: COM ソースによって発生したイベントを処理する

.NET の WebBrowser を拡張したクラス

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Reflection;

namespace WebBrowserExtended
{
  public class MyWebBrowser : WebBrowser
  {
    // WebBrowser の AxtiveX への参照
    private IWebBrowser2 axIWebBrowser2;

    // WebBrowser の AxtiveX が作成されたとき呼び出される
    [PermissionSet(SecurityAction.LinkDemand, 
      Name = "FullTrust")]
    protected override void 
      AttachInterfaces(object nativeActiveXObject)
    {
      this.axIWebBrowser2 = 
        (IWebBrowser2)nativeActiveXObject;
      base.AttachInterfaces(nativeActiveXObject);
    }

    [PermissionSet(SecurityAction.LinkDemand, 
      Name = "FullTrust")]
    protected override void DetachInterfaces()
    {
      this.axIWebBrowser2 = null;
      base.DetachInterfaces();
    }

    public object Application
    {
      get
      {
        if ((this.axIWebBrowser2 == null))
        {
          throw new 
            AxHost.InvalidActiveXStateException(
              "Application",
              AxHost.ActiveXInvokeKind.PropertyGet);
        }

        // この Application プロパティは COM の
        // マネージラッパー
        return this.axIWebBrowser2.Application;
      }
    }

    public bool RegisterAsBrowser
    {
      get
      {
        if ((this.axIWebBrowser2 == null))
        {
          throw new 
            AxHost.InvalidActiveXStateException(
              "RegisterAsBrowser",
              AxHost.ActiveXInvokeKind.PropertyGet);
        }

        // この RegisterAsBrowser プロパティは
        // COM のマネージラッパー
        return this.axIWebBrowser2.RegisterAsBrowser;
      }
      set
      {
        if ((this.axIWebBrowser2 == null))
        {
          throw new 
            AxHost.InvalidActiveXStateException(
              "RegisterAsBrowser",
              AxHost.ActiveXInvokeKind.PropertySet);
        }

        // この RegisterAsBrowser プロパティは
        // COM のマネージラッパー
        this.axIWebBrowser2.RegisterAsBrowser = value;
      }
    }

    // シンクオブジェクトへの参照
    private MyWebBrowserEventSink sink;

    // HTTP 通信の cookie とは関係ないので注意
    private AxHost.ConnectionPointCookie cookie;

    // シンクをサブスクライバ・リストに追加
    [PermissionSetAttribute(SecurityAction.LinkDemand, 
      Name="FullTrust")]
    protected override void CreateSink() 
    {
      base.CreateSink();

      if ((this.axIWebBrowser2 == null)) 
      {
        throw new AxHost.InvalidActiveXStateException(
            "CreateSink",
            AxHost.ActiveXInvokeKind.MethodInvoke);
      }

      this.sink = new MyWebBrowserEventSink(this);
      this.cookie = new AxHost.ConnectionPointCookie(
                          this.axIWebBrowser2,
                          this.sink, 
                          typeof(DWebBrowserEvents2));
    }

    // シンクのサブスクライブを解除
    [PermissionSetAttribute(SecurityAction.LinkDemand, 
      Name="FullTrust")]
    protected override void DetachSink() 
    {
      if (cookie != null) 
      {
        this.cookie.Disconnect();
        this.cookie = null;
      }
      base.DetachSink();
    }

    // NewWindow2 イベントの定義
    public event NewWindow2EventHandler NewWindow2;

    // .NET 側の NewWindow2 イベントを発動するメソッド
    protected virtual void 
      OnNewWindow2(NewWindow2EventArgs e) 
    {
      if ((this.NewWindow2 != null)) 
      {
        this.NewWindow2(this, e);
      }
    }

    // コネクションポイントからの呼び出しを受け取る
    // クライアント・シンクのクラス定義
    [ClassInterface(ClassInterfaceType.None)]
    public class MyWebBrowserEventSink :
      StandardOleMarshalObject, DWebBrowserEvents2
    {
      private MyWebBrowser browser;

      public MyWebBrowserEventSink(MyWebBrowser browser)
      {
        this.browser = browser;
      }

      // COM ソースから発生したイベントから呼び出される
      // メソッド
      public void 
        NewWindow2(ref object ppDisp, ref bool cancel)
      {
        NewWindow2EventArgs e = 
          new NewWindow2EventArgs(ppDisp, cancel);

        // .NET 側の NewWindow2 イベントを発動
        this.browser.OnNewWindow2(e);

        ppDisp = e.PpDisp;
        cancel = e.Cancel;
      }
    }
  }

  // NewWindow2 イベントのハンドラのデリゲート
  public delegate void NewWindow2EventHandler(object sender, 
    NewWindow2EventArgs e);

  // NewWindow2 イベントハンドラ引数のクラス定義
  public class NewWindow2EventArgs : EventArgs
  {        
    public object PpDisp { get; set; }
    public bool Cancel { get; set; }
        
    public NewWindow2EventArgs(object ppDisp, bool cancel)
    {
      this.PpDisp = ppDisp;
      this.Cancel = cancel;
    }
  }

  // DWebBrowserEvents2 インターフェイスの NewWindow2 メ
  // ソッド、IWebBrowser2 インターフェイスの Application
  // プロパティと RegisterAsBrowser プロパティをインポー
  // ト(つまり、マネージラッパーをコンパイル時に生成)。
  // ComImport, InterfaceType, Guid 指定は必須らしい。
  [ComImport, 
  InterfaceType(ComInterfaceType.InterfaceIsIDispatch), 
  Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D")]
  public interface DWebBrowserEvents2
  {
    [DispId(0xfb)]
    void NewWindow2(
      [In, Out, MarshalAs(UnmanagedType.IDispatch)]
      ref object ppDisp, 
      [In, Out, MarshalAs(UnmanagedType.VariantBool)] 
      ref bool Cancel);
  }

  [ComImport, 
  Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"),
  InterfaceType(ComInterfaceType.InterfaceIsIDispatch), ]
  public interface IWebBrowser2
  {
    [DispId(200)]
    object Application
    {
      [return: MarshalAs(UnmanagedType.IDispatch)]
      get;
    }

    [DispId(0x228)]
    bool RegisterAsBrowser
    {
      get;
      set;
    }
  }
}

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WebBrowserExtended
{
  public partial class Form1 : Form
  {
    private MyWebBrowser browser;
        
    public Form1()
    {
      browser = new MyWebBrowser();
            
      InitializeComponent();
                        
      browser.Dock = DockStyle.Fill;
      this.Controls.Add(browser);
      browser.NewWindow2 += 
        new NewWindow2EventHandler(browser_NewWindow2);
    }

    private void button1_Click(object sender, EventArgs e)
    {
      browser.Navigate(
        "http://msdntestnew/159-HyperLinkToPdf.aspx");
    }

    private void browser_NewWindow2(object sender, 
      NewWindow2EventArgs e)
    {
      Form1 frmWB = new Form1();

      // WebBrowser.AttachInterfaces メソッドは Visible
      // プロパティを true にすると呼び出される。なので、
      // ここで設定しないと RegisterAsBrowser プロパティ、
      // Application プロパティで例外がスローされてしまう。
      frmWB.Visible = true;

      frmWB.browser.RegisterAsBrowser = true;
      e.PpDisp = frmWB.browser.Application;
    }
  }
}

Tags:

.NET Framework

SHDocVw.dll と AxSHDocVw.dll の作り方と使い方

by WebSurfer 23. June 2012 16:11

ActiveX の WebBrowser コントロール (SHDocVw.dll) を .NET Windows Forms アプリで扱おうとしてかなりハマったので、再びそのようなことがないよう備忘録を残しておきます。

AxSHDocVw.dll を使用した Windows Forms アプリ

.NET Framework 2.0 以降では、SHDocVw.dll のマネージラッパーである WebBrowser コントロール が提供されていますので、.NET Windows Forms アプリで SHDocVw.dll を扱うことはあまりないかもしれませんが、公開されてないイベントを利用したい場合などは困ります。

マネージラッパーの WebBrowser を拡張する方法(Extended .NET 2.0 WebBrowser Control 参照)もあるようですが、今回の目的(target="_blank" のリンクをクリックすると、セッションを維持して、上の画像のように別の Form1 に表示)程度なら、直接 SHDocVw.dll を扱ったほうが簡単そうに思えたので、そうしてみました。

以下の記事を読む前に、shdocvw.dll コンポーネントに関する予備知識として、MSDN ライブラリの Internet Explorer のアーキテクチャ に目を通しておくことをお勧めします。

まず、Windows Forms アプリで ActiveX コントロールをホストするには、AxHost クラス から派生するラッパーコントロールを生成する必要があります。

それには、SDK に含まれている Aximp.exe (Windows フォーム ActiveX コントロールインポーター) を使って、ActiveX コントロール用の COM タイプライブラリに属する型定義を Windows フォームコントロールに変換します。

具体的には、以下の画像のようにコマンドプロンプトから aximp.exe を起動し、shdocvw.dll(ActiveX コントロール用の COM タイプライブラリ。即ち IE のコンポーネント)から、Microsoft Web Browser コントロール用の共通言語ランタイムプロキシ (SHDocVw.dll) および Windows フォームプロキシ (AxSHDocVw.dll) を作成します。

コマンドプロンプトから aximp.exe を起動し SHDocVw.dll と AxSHDocVw.dll を生成

紛らわしいのが、IE のコンポーネントの shdocvw.dll と、Aximp.exe が生成する共通言語ランタイムプロキシ SHDocVw.dll の名前が同じという点です。名前が同じなだけで、中身は別物ですので注意してください。実は、自分が最初にハマったのがここです。(笑)

共通言語ランタイムプロキシの SHDocVw.dll を生成するので、IE のコンポーネントの shdocvw.dll が存在するフォルダで作業すると、「AxImp Error: 出力ファイル 'SHDocVw.dll' への書き込みエラーです。」というエラーが出てうまくいきません。

オプションで /out: e:\AxSHDocVw.dll というように、プロキシを出力したいフォルダを指定し(この例では e:\)、ファイル名を AxSHDocVw.dll とすれば、以下の画像のように期待通りプロキシが生成されます。なお、この例では /source オプションを追加しているので、AxSHDocVw.dll のソースと PDB ファイルが追加されています。

生成された SHDocVw.dll と AxSHDocVw.dll

以上で Microsoft Web ブラウザコントロール用の共通言語ランタイムプロキシ (SHDocVw.dll) および Windows フォームプロキシ (AxSHDocVw.dll) が作成できました・・・が、実は、このように手動で作る必要はなかったのでした。(汗)

Visual Studio のウィザードで作ることが可能で、その方法は以下の通りです。

まず、Visual Studio のツールボックスに Microsoft Web Browser を追加します。

Visual Studio のツールボックスに Microsoft Web Browser を追加

ツールボックスの空白部分をクリックして出てくるダイアログで[アイテムの選択(I)...]をクリックして[ツールボックス アイテムの選択]ダイアログボックスを表示します。

上の画像のように、[COM コンポーネント]タブをクリックし、一覧から Microsoft Web Browser を探してチェックを入れます。

[OK]をクリックすると、ツールボックスに WebBrowser コントロールが追加されます("Microsoft Web Browser" というテキストで表示されます)。

ツールボックスから Microsoft Web Browser を Form にドラッグ&ドロップすると自動的に SHDocVw.dll と AxSHDocVw.dll 作って、参照設定に加えてくれます。(下の「2012/7/1 注記追加」を参照)

参照設定に追加された AxSHDocVw と SHDocVw

あとは、Microsoft サポートのページ How to use the WebBrowser control NewWindow2 event in Visual Basic .NET に書いてあるとおり、NewWindow2 イベントのハンドラのコード(下記)を記述すれば、target="_blank" のリンクをクリックすると新たに Form1 生成されて表示され、その中の axWebBrowser に pdf が表示されます。また、セッションも引き継がれます(IE7 以前のブラウザはダメかも・・・未検証です)。

private void axWebBrowser1_NewWindow2(object sender, 
    AxSHDocVw.DWebBrowserEvents2_NewWindow2Event e)
{
    // target="_blank" のリンクをクリックしたとき、
    // 以下のコードがないと、新たに IE が開きそこ
    // に pdf が表示される。セッションは切れる。

    // 以下のコードがあると、新たに Form1 が表示さ
    // れ、その中の axWebBrowser に pdf が表示され
    // る。セッションは引き継がれる。

    Form1 frmWB = new Form1();
    frmWB.axWebBrowser1.RegisterAsBrowser = true;
    e.ppDisp = frmWB.axWebBrowser1.Application;
    frmWB.Visible = true;
}

----- 2012/7/1 注記追加 -----

Visual Studio 2010 のツールボックスから Microsoft Web Browser を Form にドラッグ&ドロップすると自動的に生成されるプロキシ(ラッパー)の名前は、Interop.SHDocVw.dll と AxInterop.SHDocVw.dll になります。(aximp.exe を使って作ったものとは中身も少々違うようです)

SHDocVw.dll と AxSHDocVw.dll は何が違うかというと、AxSHDocVw.dll のソースを見ての想像ですが、前者が ActiveX コントロールのプロパティ、メソッドの COM ラッパー、後者がそれらの COM ラッパーと .NET アプリを仲介するためのプロパティ、メソッド、イベントを提供するクラスのようです。

例えば、AxSHDocVw.dll に Application というプロパティが以下のように定義されています。SHDocVw.dll には return this.ocx.Application; で使われている Application という COM ラッパーが定義されてるということのようです。

namespace AxSHDocVw {
  [System.Windows.Forms.AxHost.ClsidAttribute(
    "{8856f961-340a-11d0-a96b-00c04fd705a2}")]
  [System.ComponentModel.DesignTimeVisibleAttribute(true)]
  [System.ComponentModel.DefaultProperty("Name")]
  public class AxWebBrowser : System.Windows.Forms.AxHost {
        
    private SHDocVw.IWebBrowser2 ocx;
        
    private AxWebBrowserEventMulticaster eventMulticaster;
        
    private System.Windows.Forms.
              AxHost.ConnectionPointCookie cookie;
        
    public AxWebBrowser() : 
            base("8856f961-340a-11d0-a96b-00c04fd705a2") {
    }
    
    [System.ComponentModel.DesignerSerializationVisibility(
      System.ComponentModel.
      DesignerSerializationVisibility.Hidden)]
    [System.Runtime.InteropServices.DispIdAttribute(200)]
    public virtual object Application {
      get {
        if ((this.ocx == null)) {
          throw 
            new System.Windows.Forms.AxHost.
                InvalidActiveXStateException(
                  "Application", 
                  System.Windows.Forms.AxHost.
                    ActiveXInvokeKind.PropertyGet);
        }
        return this.ocx.Application;
      }
    }
    // ・・・中略・・・
  }
  // ・・・中略・・・
}

上記はプロパティの場合ですが、イベントの場合はもっと複雑で、AxSHDocVw.dll には、イベントの宣言、デリゲートの定義、イベントの引数クラスの定義、クライアント・シンクのクラス定義等々が含まれます。詳しくは別の記事 WebBrowser の拡張 に書きましたのでそちらを見てください。

Tags:

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  June 2020  >>
MoTuWeThFrSaSu
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

View posts in large calendar