none
カスタムContentControlの作り方 またはサンプルの在処を教えてください。 RRS feed

  • 質問

  • System.Windows.Controls.ContentControlから派生させて独自のコントロールを作りたいのですが、そのコンテンツに名前を付けられなくて困っています。
    作成してみたMyCtl.xamlとMyCtl.xaml.vbは次のようなものです。
    <ContentControl x:Class="MyCtl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <ContentControl.Template>
            <ControlTemplate>
                <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
            </ControlTemplate>
        </ContentControl.Template>
    </ContentControl>
    

    Partial Public Class MyCtl
        ''' <summary>
        ''' このコンストラクタがないと、コンテンツのバインディングが失敗する
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            ' warning BC40054 : デザイナで生成された型 'WPFctl.MyCtl' の 'Public Sub New()' は InitializeComponent メソッドを呼び出さなければなりません。
            InitializeComponent()
        End Sub
    End Class
    

    次に、これを使用するウィンドウを次のように作りました。
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
        xmlns:WPFctl="clr-namespace:WPFctl;assembly=WPFctl"
        x:Class="Window1"
        Title="Window1">
        <Grid>
            <WPFctl:MyCtl>
                <Button Content="うさぎ" />
            </WPFctl:MyCtl>
        </Grid>
    </Window>
    

    質問
    ・そもそもこのようにしてControlTemplateを作成するのは正しい手順なのでしょうか?
    ・うさぎに名前(x:Name)をつけるとerror MC3093となりますが、解決策をおしえてください。(コンテンツ内で要素同士をバインディングさせるときに必要)
    • 編集済み もももん 2009年6月28日 10:58 InitializeComponentを追加しました
    2009年6月28日 9:06

回答

  • 派生してコントロールを作成すると言う事は、カスタムコントロールって事ですね。

    カスタムコントロールを作りたい場合には、
    新しいプロジェクトの「WPFカスタムコントロールライブラリ」で
    作ったプロジェクトを参考にするのが良いと思います。

    新しくプロジェクトを作成して、CustomControl1が、Controlから派生となっていますが、
    これをContentControlから派生するように変えてあげてください。

    (CustomControl1が参照するxamlは、Generic.xaml に書かれていると思います。)

    調べごとのきっかけにでもなれば幸いです。
    Sorry, I am not good at English.
    • 回答としてマーク もももん 2009年6月29日 15:58
    2009年6月29日 2:49
  • このページがいいですね。
    http://msdn.microsoft.com/ja-jp/library/ms745025(VS.80).aspx

    Control や FrameworkElement の派生である ContentControl でも Control や FrameworkElement と同じようにカスタムコントロールは作れます。
    ただし上記ページに書いてあるとおり 質問にあった ContentControl で始まるようなXAMLはかけません、ここが UserControl と違うところです。
    Style や Template として XAMLを適用してやる必要があります。

    その置き場所は FC-Shiro さんの書かれているように Generic.xaml がいいと思います。

    えムナウ@わんくま同盟 Microsoft MVP Visual Studio C# Since 2005/01-2009/12
    • 回答としてマーク もももん 2009年6月29日 15:58
    2009年6月29日 6:18
  • 前にItemsControlを継承する形ですが、カスタムコントロール作ったときの
    エントリがあるのでご参考までに。
    http://blogs.wankuma.com/kazuki/archive/2009/03/29/170380.aspx


    かずき Blog:http://blogs.wankuma.com/kazuki/
    • 回答としてマーク もももん 2009年6月29日 15:58
    2009年6月29日 14:56
  • すべての問題が解決しました。ここに記録しておきます。

    1. (VisualStudio2008 では?)新しいプロジェクトを Visual Basic または Visual C# の下の階層にある WPF カスタム コントロール ライブラリ で作成する
      !!!WPF カスタム コントロール ライブラリ!!!
      !!!WPF カスタム コントロール ライブラリ!!!
    2. 派生元を ContentControl に変える
    3. テンプレートを編集して ContentPresenter Content="{TemplateBinding ContentControl.Content}" を書き込む

    FC-Shiro 氏の回答がすべてなのですが、プロジェクトを [クラスライブラリ] からの発展でこしらえていたので全く気づきませんでした。プロジェクトファイルを調べると両者はそれなりに違っています。そして決定的に違うのは、次のコードがエラーを引き起こすことです。

    Shared Sub New()
        ' このコードはビルドできるがプロジェクトの種類が適切でないと実行時にエラーとなる
        ' (ResourceDictionary インスタンスを再初期化することはできません。)
        DefaultStyleKeyProperty.OverrideMetadata(GetType(CustomControl1), _
                                                 New FrameworkPropertyMetadata(GetType(CustomControl1)))
    End Sub
    

    そもそも既存の[クラスライブラリ]プロジェクトにWPFの機能を追加しようとしたのがよろしくないらしい。

    • 回答としてマーク もももん 2009年6月29日 16:36
    2009年6月29日 16:35

すべての返信

  • どんどん壊れてくので、コード片の修正はあきらめます。読みにくくて申し訳ない。

    2009年6月28日 9:14
  • これを追加したら、うさぎからWindowへのバインディングはうまくいくようになった。
    こんなに大袈裟なことをしなければならないのだろうか。
    .NET Reflectorで例えばLabelを覗いてみてもこんなメンドクサイことはやっていない。
    Partial Public Class MyCtl
        Implements INameScope
    
        Private ReadOnly _Names As Dictionary(Of String, Object) = New Dictionary(Of String, Object)()
    
        Public Function FindNameX(ByVal name As String) As Object Implements INameScope.FindName
            Try
                Dim FE As FrameworkElement = TryCast(MyBase.Parent, FrameworkElement)
                If FE IsNot Nothing Then
                    Return FE.FindName(name)
                Else
                    Return _Names.Item(name)
                End If
            Catch ex As Exception
                Debug.WriteLine(String.Format("{0}", ex.Message))
                Return Nothing
            End Try
        End Function
    
        Public Sub RegisterNameX(ByVal name As String, ByVal scopedElement As Object) Implements INameScope.RegisterName
            Try
                Dim FE As FrameworkElement = TryCast(MyBase.Parent, FrameworkElement)
                If FE IsNot Nothing Then
                    FE.RegisterName(name, scopedElement)
                Else
                    _Names.Add(name, scopedElement)
                End If
            Catch ex As Exception
                Debug.WriteLine(String.Format("{0}", ex.Message))
            End Try
        End Sub
    
        Public Sub UnregisterNameX(ByVal name As String) Implements INameScope.UnregisterName
            Try
                Dim FE As FrameworkElement = TryCast(MyBase.Parent, FrameworkElement)
                If FE IsNot Nothing Then
                    FE.UnregisterName(name)
                Else
                    _Names.Remove(name)
                End If
            Catch ex As Exception
                Debug.WriteLine(String.Format("{0}", ex.Message))
            End Try
        End Sub
    
    End Class
    
    2009年6月28日 12:12
  • 派生してコントロールを作成すると言う事は、カスタムコントロールって事ですね。

    カスタムコントロールを作りたい場合には、
    新しいプロジェクトの「WPFカスタムコントロールライブラリ」で
    作ったプロジェクトを参考にするのが良いと思います。

    新しくプロジェクトを作成して、CustomControl1が、Controlから派生となっていますが、
    これをContentControlから派生するように変えてあげてください。

    (CustomControl1が参照するxamlは、Generic.xaml に書かれていると思います。)

    調べごとのきっかけにでもなれば幸いです。
    Sorry, I am not good at English.
    • 回答としてマーク もももん 2009年6月29日 15:58
    2009年6月29日 2:49
  • このページがいいですね。
    http://msdn.microsoft.com/ja-jp/library/ms745025(VS.80).aspx

    Control や FrameworkElement の派生である ContentControl でも Control や FrameworkElement と同じようにカスタムコントロールは作れます。
    ただし上記ページに書いてあるとおり 質問にあった ContentControl で始まるようなXAMLはかけません、ここが UserControl と違うところです。
    Style や Template として XAMLを適用してやる必要があります。

    その置き場所は FC-Shiro さんの書かれているように Generic.xaml がいいと思います。

    えムナウ@わんくま同盟 Microsoft MVP Visual Studio C# Since 2005/01-2009/12
    • 回答としてマーク もももん 2009年6月29日 15:58
    2009年6月29日 6:18
  • お二方 ご助言ありがどうございます。

    えムナウ氏が示された資料は当然のごとく読んでおりました。

    ここで資料の一部を引用します。
    外部のコントロール ライブラリ
    最後の手順は、NumericUpDown コントロールをその独自のアセンブリにパッケージ化して、簡単に再利用できるようにすることです。
    
    テーマ ファイルの作成
    NumericUpDown クラスをライブラリ アセンブリに移動したら、スタイル定義を移動する必要があります。最初に、すべてのテーマ ファイルを格納するための "themes" フォルダを作成する必要があります。次に、generic.xaml という名前のファイルを作成します。このファイルは、このアセンブリのすべてのリソース検索のフォールバックとして機能します。
    generic.xaml についてはこのあたりで登場しますが、[最後の手順は]の後で、[スタイル定義を移動する必要があります。]とあり、それが要請する結果として、generic.xamlが登場しています。

    私のコードがスタイルのことに注意を払っていないことが見て取れると思いますが、
    そんなことは後回しで構わない
    という解釈でした。
    が、その遥か以前にある
    テンプレートへの移動
    基本クラスを更新したら、コントロールのコンテンツをテンプレートに移動する必要があります。テンプレートはスタイルで定義され、アプリケーション内の多くの場所に置くことができます。この例では、アプリケーション リソースに置きます。
    ここを読み落としていたようです。

    結果は後ほど。
    2009年6月29日 8:33
  • 前にItemsControlを継承する形ですが、カスタムコントロール作ったときの
    エントリがあるのでご参考までに。
    http://blogs.wankuma.com/kazuki/archive/2009/03/29/170380.aspx


    かずき Blog:http://blogs.wankuma.com/kazuki/
    • 回答としてマーク もももん 2009年6月29日 15:58
    2009年6月29日 14:56
  • すべての問題が解決しました。ここに記録しておきます。

    1. (VisualStudio2008 では?)新しいプロジェクトを Visual Basic または Visual C# の下の階層にある WPF カスタム コントロール ライブラリ で作成する
      !!!WPF カスタム コントロール ライブラリ!!!
      !!!WPF カスタム コントロール ライブラリ!!!
    2. 派生元を ContentControl に変える
    3. テンプレートを編集して ContentPresenter Content="{TemplateBinding ContentControl.Content}" を書き込む

    FC-Shiro 氏の回答がすべてなのですが、プロジェクトを [クラスライブラリ] からの発展でこしらえていたので全く気づきませんでした。プロジェクトファイルを調べると両者はそれなりに違っています。そして決定的に違うのは、次のコードがエラーを引き起こすことです。

    Shared Sub New()
        ' このコードはビルドできるがプロジェクトの種類が適切でないと実行時にエラーとなる
        ' (ResourceDictionary インスタンスを再初期化することはできません。)
        DefaultStyleKeyProperty.OverrideMetadata(GetType(CustomControl1), _
                                                 New FrameworkPropertyMetadata(GetType(CustomControl1)))
    End Sub
    

    そもそも既存の[クラスライブラリ]プロジェクトにWPFの機能を追加しようとしたのがよろしくないらしい。

    • 回答としてマーク もももん 2009年6月29日 16:36
    2009年6月29日 16:35