DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

WPFコントロールをWindowsフォームに配置する

WPFコントロール(WPF要素、UIElement)をWindowsフォームに配置(ホスト)するには、ElementHostコントロールを使います。方法は、ElementHost.ChildプロパティにWPFコントロールを割り当てたElementHostコントロールをWindowsフォームに配置するだけです。

ElementHostコントロールはWindowsFormsIntegration.dll内にありますので、使用するにはWindowsFormsIntegration.dllを参照に追加する必要があります。また、WPFコントロールを使用するためにPresentationFramework.dll、PresentationCore.dll、WindowsBase.dllも参照に追加します。さらに.NET Framework 4.0以降では、System.Xaml.dllも追加します。なおDLLを参照に追加する方法は、「「○○○.dllを参照に追加します」の意味は?」をご覧ください。

動的にコントロールを配置する

まずは、Visual Studioのフォームデザイナを使わずに、コードのみで動的にコントロールを配置してみます。なお動的にコントロールを配置する方法については、「コントロールを実行時に作成する」で詳しく説明しています。

下に示す例では、WPFのButtonコントロールをWindowsフォームに配置しています。なおこのコードは、コントロールを配置するフォームクラス内に記述されているものとします。

VB.NET
コードを隠すコードを選択
'WPFコントロールをホストするElementHostコントロール
Private elementHost1 As System.Windows.Forms.Integration.ElementHost

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(sender As Object, e As System.EventArgs) _
        Handles MyBase.Load
    'WPFのButtonコントロールを作成する
    Dim wpfButton As New System.Windows.Controls.Button()
    wpfButton.Content = "Push!"
    AddHandler wpfButton.Click, AddressOf wpfButton_Click

    'ElementHostコントロールを作成する
    elementHost1 = New System.Windows.Forms.Integration.ElementHost()
    'コントロールの位置と大きさを設定する
    elementHost1.SetBounds(20, 10, 100, 30)

    'ElementHostのChildプロパティにWPFコントロールを設定する
    elementHost1.Child = wpfButton

    'ElementHostをフォームに配置する
    Me.Controls.Add(elementHost1)
End Sub

'WPFのButtonコントロールのClickイベントハンドラ
Private Sub wpfButton_Click(sender As Object, _
                            e As System.Windows.RoutedEventArgs)
    System.Windows.Forms.MessageBox.Show("ボタンが押されました")
End Sub
C#
コードを隠すコードを選択
//WPFコントロールをホストするElementHostコントロール
private System.Windows.Forms.Integration.ElementHost elementHost1;

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //WPFのButtonコントロールを作成する
    System.Windows.Controls.Button wpfButton =
        new System.Windows.Controls.Button();
    wpfButton.Content = "Push!";
    wpfButton.Click += wpfButton_Click;

    //ElementHostコントロールを作成する
    elementHost1 = new System.Windows.Forms.Integration.ElementHost();
    //コントロールの位置と大きさを設定する
    elementHost1.SetBounds(20, 10, 100, 30);

    //ElementHostのChildプロパティにWPFコントロールを設定する
    elementHost1.Child = wpfButton;

    //ElementHostをフォームに配置する
    this.Controls.Add(elementHost1);
}

//WPFのButtonコントロールのClickイベントハンドラ
private void wpfButton_Click(
    object sender, System.Windows.RoutedEventArgs e)
{
    System.Windows.Forms.MessageBox.Show("ボタンが押されました");
}

WPFコントロールのプロパティをElementHostコントロールのプロパティで設定する

ElementHostコントロールによってホストされているWPFコントロールにアクセスするには、ElementHost.ChildプロパティからWPFコントロールを取得すればよいでしょう。しかし、ElementHostコントロールの「プロパティマッピング」という機能を使えば、ElementHostコントロールのプロパティ値を変更することで、WPFコントロールのプロパティ値を変更することができます。例えば、ElementHost.EnabledプロパティをFalseにすると、ホストされているWPFコントロールのIsEnabledプロパティがFalseになり、無効化されます。

このように、プロパティマッピングによってElementHostコントロールとWPFコントロールが連動しているプロパティには、以下の様なものがあります。

  • BackColorプロパティ
  • BackgroundImageプロパティ
  • BackgroundImageLayoutプロパティ
  • Cursorプロパティ
  • Enabledプロパティ
  • Fontプロパティ
  • ImeModeプロパティ
  • RightToLeftプロパティ
  • Visibleプロパティ
補足:このようなプロパティの一覧は、以下のようなコードで確認できます。
VB.NET
コードを隠すコードを選択
Dim host As New System.Windows.Forms.Integration.ElementHost()
For Each pn As String In host.PropertyMap.Keys
    Console.WriteLine(pn)
Next
host.Dispose()
C#
コードを隠すコードを選択
System.Windows.Forms.Integration.ElementHost host =
    new System.Windows.Forms.Integration.ElementHost();
foreach (string pn in host.PropertyMap.Keys)
{
    Console.WriteLine(pn);
}
host.Dispose();

プロパティマップに独自の設定を追加する

上記以外のプロパティであっても、自分で新たに設定を追加する事もできます。それには、ElementHost.PropertyMapプロパティからプロパティマップを取得して、PropertyMap.Addメソッドを使って設定を追加します。

PropertyMap.Addメソッドの1番目の引数には、ElementHostコントロールのプロパティの名前をString型で指定します。2番目の引数には、そのプロパティに値が設定された時に呼び出されるPropertyTranslatorデリゲートを指定します。

難しい話は置いておいて、具体例を示します。以下の例では、ElementHost.Textプロパティを変更すると、ホストしているButtonコントロールのContentプロパティも変更されるようにしています。

VB.NET
コードを隠すコードを選択
'WPFコントロールをホストするElementHostコントロール
Private elementHost1 As System.Windows.Forms.Integration.ElementHost

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(sender As Object, e As System.EventArgs) _
        Handles MyBase.Load
    'ElementHostコントロールを作成する
    elementHost1 = New System.Windows.Forms.Integration.ElementHost()
    elementHost1.SetBounds(20, 10, 100, 30)
    elementHost1.Child = New System.Windows.Controls.Button()

    'フォームにElementHostを配置する
    Me.Controls.Add(elementHost1)

    'ElementHostのTextプロパティが変更された時に
    ' TextPropertyTranslatorメソッドが呼び出されるようにする
    elementHost1.PropertyMap.Add("Text", _
        New System.Windows.Forms.Integration.PropertyTranslator( _
            AddressOf Me.TextPropertyTranslator))
    'ElementHost.TextプロパティでButton.Contentプロパティを変更する
    elementHost1.Text = "押してね"
End Sub

'ElementHost.Textプロパティが変更された時に、ホストしている
' ButtonコントロールのContentプロパティを変更するためのメソッド
Private Sub TextPropertyTranslator( _
        h As Object, propertyName As String, value As Object)
    'ElementHostを取得する
    Dim host As System.Windows.Forms.Integration.ElementHost = _
        DirectCast(h, System.Windows.Forms.Integration.ElementHost)
    If host IsNot Nothing AndAlso host.Child IsNot Nothing Then
        'ホストしているButtonコントロールを取得する
        Dim wpfButton As System.Windows.Controls.Button = _
            DirectCast(host.Child, System.Windows.Controls.Button)
        'Contentプロパティを変更する
        wpfButton.Content = value
    End If
End Sub
C#
コードを隠すコードを選択
//WPFコントロールをホストするElementHostコントロール
private System.Windows.Forms.Integration.ElementHost elementHost1;

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //ElementHostコントロールを作成する
    elementHost1 = new System.Windows.Forms.Integration.ElementHost();
    elementHost1.SetBounds(20, 10, 100, 30);
    elementHost1.Child = new System.Windows.Controls.Button();

    //フォームにElementHostを配置する
    this.Controls.Add(elementHost1);

    //ElementHostのTextプロパティが変更された時に
    // TextPropertyTranslatorメソッドが呼び出されるようにする
    elementHost1.PropertyMap.Add("Text",
        new System.Windows.Forms.Integration.PropertyTranslator(
            this.TextPropertyTranslator));
    //ElementHost.TextプロパティでButton.Contentプロパティを変更する
    elementHost1.Text = "押してね";
}

//ElementHost.Textプロパティが変更された時に、ホストしている
// ButtonコントロールのContentプロパティを変更するためのメソッド
private void TextPropertyTranslator(
    object h, String propertyName, object value)
{
    //ElementHostを取得する
    System.Windows.Forms.Integration.ElementHost host =
        (System.Windows.Forms.Integration.ElementHost)h;
    if (host != null && host.Child != null)
    {
        //ホストしているButtonコントロールを取得する
        System.Windows.Controls.Button wpfButton =
            (System.Windows.Controls.Button)host.Child;
        //Contentプロパティを変更する
        wpfButton.Content = value;
    }
}

このようにマッピングできるプロパティは、ElementHostコントロールが持っているプロパティだけです。ElementHostコントロールにないプロパティ名を追加しようとすると、例外ArgumentExceptionがスローされます。

ElementHostコントロールが持っているプロパティならばどれでもプロパティマップに追加できますが、上の例のTextプロパティのように、プロパティ値を変更すると自動的にPropertyTranslatorデリゲートが実行されるプロパティは限られています。そのようなプロパティは、以下のプロパティだけのようです。

  • AutoSizeプロパティ
  • BackColorプロパティ
  • BackgroundImageプロパティ
  • BackgroundImageLayoutプロパティ
  • BindingContextプロパティ
  • CausesValidationプロパティ
  • ContextMenuプロパティ
  • ContextMenuStripプロパティ
  • Cursorプロパティ
  • Dockプロパティ
  • Enabledプロパティ
  • Fontプロパティ
  • ForeColorプロパティ
  • ImeModeプロパティ
  • Locationプロパティ
  • Marginプロパティ
  • Paddingプロパティ
  • Parentプロパティ
  • Regionプロパティ
  • RightToLeftプロパティ
  • Sizeプロパティ
  • TabIndexプロパティ
  • TabStopプロパティ
  • Textプロパティ
  • Visibleプロパティ

手動でPropertyTranslatorを実行する

上記以外のプロパティ(プロパティ値を変更しても自動的にPropertyTranslatorデリゲートが実行されないプロパティ)の場合は、プロパティ値を変更した時に自分でPropertyMap.ApplyメソッドPropertyMap.ApplyAllメソッドを呼び出すか、ElementHost.OnPropertyChangedメソッドを呼び出すことで、PropertyTranslatorデリゲートを実行することができます。

補足:PropertyMap.Addメソッドを呼び出した時は、自動的にPropertyMap.Applyメソッドが呼び出されます。

以下の例では、UseWaitCursorプロパティをプロパティマップに追加して、ElementHost.UseWaitCursorプロパティが変更された時に、ホストしているWPFコントロールのCursorプロパティが変更されるようにしています。ただしUseWaitCursorプロパティの値を変更した後でPropertyMap.Applyメソッドを呼び出さないとPropertyTranslatorは実行されません。そのコードは、Button1のClickイベントハンドラ(Button1_Clickメソッド)内に記述しています。

VB.NET
コードを隠すコードを選択
'WPFコントロールをホストするElementHostコントロール
Private elementHost1 As System.Windows.Forms.Integration.ElementHost

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(sender As Object, e As System.EventArgs) _
        Handles MyBase.Load
    'ElementHostコントロールを作成する
    elementHost1 = New System.Windows.Forms.Integration.ElementHost()
    elementHost1.SetBounds(20, 10, 100, 30)
    elementHost1.Child = New System.Windows.Controls.Button()

    'フォームにElementHostを配置する
    Me.Controls.Add(elementHost1)

    'ElementHostのUseWaitCursorプロパティが変更された時に
    ' UseWaitCursorPropertyTranslatorメソッドが呼び出されるようにする
    elementHost1.PropertyMap.Add("UseWaitCursor", _
        New System.Windows.Forms.Integration.PropertyTranslator( _
            AddressOf Me.UseWaitCursorPropertyTranslator))
End Sub

'ElementHost.UseWaitCursorプロパティが変更された時に、ホストしている
' ButtonコントロールのCursorプロパティを変更するためのメソッド
Private Sub UseWaitCursorPropertyTranslator( _
        h As Object, propertyName As String, value As Object)
    'ElementHostを取得する
    Dim host As System.Windows.Forms.Integration.ElementHost = _
        DirectCast(h, System.Windows.Forms.Integration.ElementHost)
    If host IsNot Nothing AndAlso host.Child IsNot Nothing Then
        'ホストしているButtonコントロールを取得する
        Dim wpfButton As System.Windows.Controls.Button = _
            DirectCast(host.Child, System.Windows.Controls.Button)
        'UseWaitCursorの値に応じて、カーソルを変更する
        If CBool(value) Then
            wpfButton.Cursor = System.Windows.Input.Cursors.Wait
        Else
            wpfButton.Cursor = Nothing
        End If
    End If
End Sub

'Button1のClickイベントハンドラ
Private Sub Button1_Click(sender As Object, e As System.EventArgs) _
        Handles Button1.Click
    'ElementHost.UseWaitCursorプロパティの値を変更する
    elementHost1.UseWaitCursor = Not elementHost1.UseWaitCursor

    'UseWaitCursorプロパティが変更されたことをPropertyMapに知らせる
    elementHost1.PropertyMap.Apply("UseWaitCursor")

    'または、次のようにすることもできる
    'elementHost1.OnPropertyChanged("UseWaitCursor", _
    '    elementHost1.UseWaitCursor)

    '次のようにすると、PropertyMapに登録されている
    ' すべてのPropertyTranslatorが実行される
    'elementHost1.PropertyMap.ApplyAll()
End Sub
C#
コードを隠すコードを選択
//WPFコントロールをホストするElementHostコントロール
private System.Windows.Forms.Integration.ElementHost elementHost1;

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //ElementHostコントロールを作成する
    elementHost1 = new System.Windows.Forms.Integration.ElementHost();
    elementHost1.SetBounds(20, 10, 100, 30);
    elementHost1.Child = new System.Windows.Controls.Button();

    //フォームにElementHostを配置する
    this.Controls.Add(elementHost1);

    //ElementHostのUseWaitCursorプロパティが変更された時に
    // UseWaitCursorPropertyTranslatorメソッドが呼び出されるようにする
    elementHost1.PropertyMap.Add("UseWaitCursor",
        new System.Windows.Forms.Integration.PropertyTranslator(
            this.UseWaitCursorPropertyTranslator));
}

//ElementHost.UseWaitCursorプロパティが変更された時に、ホストしている
// ButtonコントロールのCursorプロパティを変更するためのメソッド
private void UseWaitCursorPropertyTranslator(
    object h, String propertyName, object value)
{
    //ElementHostを取得する
    System.Windows.Forms.Integration.ElementHost host =
        (System.Windows.Forms.Integration.ElementHost)h;
    if (host != null && host.Child != null)
    {
        //ホストしているButtonコントロールを取得する
        System.Windows.Controls.Button wpfButton =
            (System.Windows.Controls.Button)host.Child;
        //UseWaitCursorの値に応じて、カーソルを変更する
        if ((bool)value)
        {
            wpfButton.Cursor = System.Windows.Input.Cursors.Wait;
        }
        else
        {
            wpfButton.Cursor = null;
        }
    }
}

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //ElementHost.UseWaitCursorプロパティの値を変更する
    elementHost1.UseWaitCursor = !elementHost1.UseWaitCursor;

    //UseWaitCursorプロパティが変更されたことをPropertyMapに知らせる
    elementHost1.PropertyMap.Apply("UseWaitCursor");

    //または、次のようにすることもできる
    //elementHost1.OnPropertyChanged("UseWaitCursor",
    //    elementHost1.UseWaitCursor);

    //次のようにすると、PropertyMapに登録されている
    // すべてのPropertyTranslatorが実行される
    //elementHost1.PropertyMap.ApplyAll();
}

フォームデザイナを使って、WPFユーザーコントロールを配置する

同じソリューション内にあるWPFユーザーコントロールであれば、Visual Studioのフォームデザイナを使って簡単に配置することができます。その手順を紹介します。

  1. Windowsフォームに配置するWPFユーザーコントロールライブラリのプロジェクトを、Windowsフォームアプリケーションのプロジェクトと同じソリューションに作成、または追加します。Visual Studioのメニューの[ファイル]-[新規作成]と[追加]によって、プロジェクトの作成、追加ができます。

    WPFユーザーコントロールライブラリプロジェクトを追加

  2. Windowsフォームアプリケーションのプロジェクトに戻ります。Windowsフォームデザイナを使って、フォームにElementHostコントロールを配置します。ElementHostコントロールは、ツールボックスの「WPF相互運用機能」の中にあります。

    ツールボックスからElementHostを選択

  3. 配置したElementHostコントロールを選択します。そして、プロパティウィンドウで「Childプロパティ」を探し、右側にある下矢印をクリックします。すると、Childプロパティに設定できるWPFコントロールの一覧が表示されます。ここに先程ソリューションに追加したWPFユーザーコントロールが表示されるはずですので、クリックして選択してください。

    ホストするWPFユーザーコントロールを選択

  4. このようにしてWPFユーザーコントロールをChildプロパティに設定すると、フォームクラスに新たにWPFユーザーコントロール型のフィールドが追加されます。これを使えば、簡単にホストしたWPFユーザーコントロールにアクセスすることができます。フィールド名は、ElementHostコントロールのプロパティウィンドウで、「(HostedContentName)」として表示されています。例えば「(HostedContentName)」が「userControl11」であれば、「userControl11.Content」としてContentプロパティにアクセスできます。
    ただし、ホストしたWPFユーザーコントロールのプロパティをフォームデザイナを使って変更することはできないようです。というのは、「プロパティウィンドウ」のコンボボックスにホストしたWPFコントロールは表示されるのですが、選択しても「(Name)」しか表示されないからです。

    ホストしたWPFユーザーコントロールのプロパティ

  5. 以上です。

フォームデザイナを使って、通常のWPFコントロールを配置する

同じソリューション内にあるWPFユーザーコントロールであれば上記のようにできますが、それ以外のWPFコントロールの場合は、フォームデザイナを使って配置することはできません。しかし、私が試した所、強引にフォームクラスのInitializeComponentメソッドを書き換えることによって、それらしいことはできました。ただし、正規の方法ではありませんし、InitializeComponentメソッドをコードエディタで書き換えることは危険ですので、本来はすべきではありません。

以下に私がやってみたことを一応書いておきます(Visual Studio 2012)。

  1. 先程と同じやり方で、フォームにElementHostコントロールを配置します。配置したコントロールの名前を「elementHost1」(VB.NETでは「ElementHost1」)にしたものとします。
  2. ElementHostコントロールを配置したフォームのInitializeComponentメソッドを探します。通常は、「Form1.Designer.cs」(VB.NETでは「Form1.Designer.vb」)のようなファイル内にあります。どこにあるか分からない場合は、「InitializeComponent」でソリューション全体を検索してみてください(または、「ElementHost」で検索した方が良いかもしれません)。
  3. InitializeComponentメソッド内で次のような記述を探します。
    VB.NET
    コードを隠すコードを選択
    Me.ElementHost1.Child = Nothing
    
    C#
    コードを隠すコードを選択
    this.elementHost1.Child = null;
    
    ここを書き換えて、ホストしたいWPFコントロールをElementHost.Childプロパティに割り当てます。例えば、WPFのButtonコントロールをホストする場合は、次のようにします。
    VB.NET
    コードを隠すコードを選択
    Me.ElementHost1.Child = New System.Windows.Controls.Button()
    
    C#
    コードを隠すコードを選択
    this.elementHost1.Child = new System.Windows.Controls.Button();
    
  4. 再びフォームデザイナに戻って、フォームを表示します。配置したElementHostを選択して、プロパティウィンドウで「(HostedContentName)」プロパティを探します。ここに、ホストしたWPFコントロールの名前を入力します。名前を変更したくなくても、とりあえず一度変更して、元に戻します。するとその名前のフィールドがフォームクラスに追加され、その名前でホストしたWPFコントロールにアクセスできるようになります。例えば「(HostedContentName)」を「Button1」にすると、InitializeComponentメソッドは次のように書き換えられます。
    VB.NET
    コードを隠すコードを選択
    Private Sub InitializeComponent()
        '(省略)
        Me.Button1 = New System.Windows.Controls.Button()
        '(省略)
        Me.ElementHost1.Child = Me.Button1
        '(省略)
    End Sub
    '(省略)
    Friend Button1 As System.Windows.Controls.Button
    
    C#
    コードを隠すコードを選択
    private void InitializeComponent()
    {
        //(省略)
        this.Button1 = new System.Windows.Controls.Button();
        //(省略)
        this.elementHost1.Child = this.Button1;
        //(省略)
    }
    //(省略)
    private System.Windows.Controls.Button Button1;
    
  5. 以上です。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。