none
テンプレートバインディング時にカスタム依存プロパティの値が継承されない? RRS feed

  • 質問

  • 件名の通りなのですが、どなたかもしご存知であればご教授下さい。

    住所録コントロールを作成しておりまして、複数のデータをTabControlを継承したコントロールで切り替えるようにしています。

    タブの各ページはDataTemplateを使って表示しています。

     

    <TabControl x:Class="ctrl_AddressTab"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MyBase"
        ItemsSource="{Binding}">

        <TabControl.Resources>
            <Style TargetType="TabItem">
                <Setter Property="Header" Value="{Binding Path=Name}"/>
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <local:ctrl_AddressItem/>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </TabControl.Resources>
    </TabControl>

    このコントロールを配置する親フォーム(frmMain)にカスタム依存プロパティを定義し、

    ctrl_AddressTab、ctrl_AddressItemにも同じカスタム依存プロパティを定義しています。

     

    [ctrl_AddressTab内のコード。ctrl_AddressItemも同様]

    Friend Shared dpFunctionKey As DependencyProperty _
        = frmMain.dpSample.AddOwner(GetType(ctrl_AddressTab), GetMetadata)

    Public Shared Function GetMetadata() As FrameworkPropertyMetadata
        Dim objMetadata As New FrameworkPropertyMetadata
        With objMetadata
            .DefaultValue = String.Empty
            .Inherits = True
        End With

        Return objMetadata
    End Function

    上記の実装のとき、frmMainからctrl_AddressTabへは依存プロパティの値が継承されているのですが、

    なぜかctrl_AddressItemには継承されていません。

    他のテンプレートバインディングも試してみたのですが、すべて継承されていませんでした。

    FontSize等のシステムがもともと持っている依存プロパティは継承されているので、

    何か方法はあるのだろうと思うのですが…

     

    何かご指摘等あればよろしくお願いいたします。
    2007年8月7日 2:34

すべての返信

  • 依存プロパティを作る際,
    ラッパーとしてのプロパティ(setter/getter)の名前に対して,
    Property という接尾辞にしていないといけないけど,

    というか,そういう想定で作られているので,
    そうなっていないとか。
    仕組み的には,

    Register の第一引数がラッパーと同じになっていればいいんですが。

     

    あと,カスタム依存プロパティは以下の感じです。(VBなので自信なし,だいたいということで)

     

    Code Snippet

     

    Public Class thisClass

        Inherits DependecyObject系Class


        ''' 依存プロパティを WPF プロパティ システム に登録

        Public Shared ReadOnly HogeProperty As DependencyProperty = _

            DependencyProperty.Register( _

            "Hoge", GetType(returntype), GetType(thisclass), ...)

     

        ''' ラッパー

        Public Property Hoge As returntype

            Get

                Return CType(GetValue(HogeProperty), returntype)

            End Get

            Set(ByVal value As returntype)

                SetValue(HogePropery, value)

            End Set

        End Propery

     

    End Class

     

     

     

    また,
    Windows Presentation Foundation Unleashed という本によると,
    XAMLで,ラッパープロパティを指定しても,
    実行時は,ラッパーを利用しないで,実行時に直接 HogeProperty の方を呼ぶので,
    値のチェックは,

    別の仕組み(Register時にチェック用のメソッドハンドラを指定可能)が用意されているので,
    それでやります。

    つまり,ラッパー内では何もしないのが吉のようです。

     

    また,

    依存プロパティの取得/設定は,依存プロパティにするのでなく,

    ラッパーに対して行います。

    依存プロパティ自身の値は,

    Register 時に,プロパティ・システムから返された識別子(identifier)を入れておくものです

    つまり,

    WPFでは,クラスのフィールド値は,識別子として静的な値としてひとつ存在し,

    各インスタンスごとの値は,その各インスタンスに置かれるのでなく,
    (GetValue/SetValue時に暗黙の引数としてインスタンス参照が渡されて,それが)
    クラスのそのフィールドの入っているたったひとつの識別子と関連付けて,

    システム側がまとめて管理しています。

     

     

    # ここまでOKなのにという意味でなら,読み流してください。

    # AddOwner(...) は,別の型がRegisterした依存プロパティに対して自分もOwnerに加わるためのものです。

    2007年8月7日 6:40
  • レスありがとうございます。

     

    > 仕組み的には,

    > Register の第一引数がラッパーと同じになっていればいいんですが。

     

    これは大丈夫です。依存プロパティそのものの実装はできているので…

    説明不足だったようですが、frmMainからctrl_AddressTabへの依存プロパティの継承はできています。

    そのctrl_AddressTabのContentTemplateにDataTemplateとしてバインディングしたctrl_AddressItemには

    なぜか(全く同じ実装なのに)継承されないので「???」と思った次第です。

     

    試しにfrmMainにControlを1つ配置して、これにControlTemplate経由でctrl_AddressTabを

    バインディングすると値が継承されませんでした。

    そのため、テンプレートバインディング時は依存プロパティ継承の動作が異なるのではないかと考えています。

     

    > XAMLで,ラッパープロパティを指定しても,
    > 実行時は,ラッパーを利用しないで,実行時に直接 HogeProperty の方を呼ぶので,
    > 値のチェックは,

    > 別の仕組み(Register時にチェック用のメソッドハンドラを指定可能)が用意されているので,
    > それでやります。

     

    MetaData登録時にCoerceValueCallback及びPropertyChangedCallbackデリゲートを

    設定しておく方法ですね。今回は値チェックの必要がないので、これは行っていません。

    ちなみにDependencyPropertyの宣言時に全部書いてしまうと、やたら長くなって

    (個人的に)見にくいので、MetaDataの宣言メソッドを別にしています。

     

     

    PS.

    > あと,カスタム依存プロパティは以下の感じです。(VBなので自信なし,だいたいということで)

     

    すみません、お手数をおかけしました。職場がVB縛りなのでVBで書いてます。C#でも読めますので…

    ちなみにコードはあってます
    2007年8月7日 7:49
  • AddOwner のところはOKなんですかね。
    別の型がRegisterしたものしか登録できないようですが。

     

    追記 : よく見たら,そうなってますね。

    2007年8月7日 8:01
  • 質問の意味がわかりました。

     

    2007年8月7日 8:18
  • AddOwner時の戻り値を入れる依存プロパティ名およびそのラッパー名は,

    Registerを実行した型での依存プロパティ名/ラッパー名と同じにしないといけない縛りはないんですかね。

     

     

    # すみません,ベータ2の導入にてこずってて,検証しないで発言しています。

    2007年8月7日 8:36
  • おはようございます。レスありがとうございました。

    > AddOwner時の戻り値を入れる依存プロパティ名およびそのラッパー名は,

    > Registerを実行した型での依存プロパティ名/ラッパー名と同じにしないといけない縛りはないんですかね。

     

    静的メンバとして宣言するDependencyPropertyオブジェクトの事ですよね?

    これはクラス毎に違う名前にしても問題無いようです。

    ラッパーに実装するGetValue/SetValueメソッドの引数として必要になるだけで、

    DPシステム側からこれが参照されている様子はありません。

    (裏付けとして、Privateで宣言しても問題なく動作しますので…)

     

    WPFは触り始めて日が浅いので、テンプレートバインディングについてはまだ理解が足りないのですが、

    ひょっとすると値はバインディング元が、外観と動作はバインディングされたコントロールのものが

    適用されるのではないかと考えています。

    ちょっと今から検証してみます。

    2007年8月8日 1:34
  • 自己レスになります。先に返信した件を検証してみました。

    予想通りというか、Controlコントロールに依存プロパティを追加したControl2コントロールを作成し、

    これにバインディングしたところ依存プロパティの値が継承されました。

    また、AddOwnerを行っている場所で、

     

    Private Shared dpFunctionKey As DependencyProperty _
        = frmMain.dpSample.AddOwner(GetType(ctrl_AddressTab), GetMetadata)

    Private Shared dpDummy As DependencyProperty _
        = frmMain.dpSample.AddOwner(GetType(System.Windows.Controls.Control), GetMetadata)

    のように、DPシステムへControlをオーナーとした依存プロパティの追加を行ってやると、

    標準のControlコントロールへのバインディングでも依存プロパティの値が継承できました。

    ControlTemplateへのバインディングはこの方法でいけそうです。

     

    ただ、今度はTabControl.ContentTemplateへは何をオーナーにして依存プロパティの登録を行えば良いのかがわかりません(苦笑)

    まあこれはTabControlだけではなく、ItemsControlを継承するコレクションコントロール全般に言える事ですが。

    DataTemplateが内部的にはどのコントロールとバインディングしているのかわかれば、たぶんそれをオーナーとして登録することで実現できると思うのですが…一難去ってまた一難です。

    2007年8月8日 5:51
  • ctrl_AddressItem は,TabItem を継承したクラスなら,
    書き方が違ってますね。

    ControlTemplate は,TabItem に設定するものなので,

    今回は,TabItem 自体を入れ替えるのが目的のように見えるんですが。

    2007年8月8日 6:27
  • ContentControl系ではない TabControl の ContentTemplate プロパティは,
    TabItem の ContentTemplate プロパティ(ContentControlから継承したメンバ)が未設定の時に,
    TabItem の ContentTemplate プロパティ(ContentControlから継承したメンバ) に適用されるものですね。

    2007年8月8日 6:58
  • レスありがとうございます。先のレスに返信しようとしたらその次が来てしまった(苦笑)

     

    > ctrl_AddressItem は,TabItem を継承したクラスなら,
    > 書き方が違ってますね。

    > ControlTemplate は,TabItem に設定するものなので,

    > 今回は,TabItem 自体を入れ替えるのが目的のように見えるんですが。

     

    ctrl_AddressItemはGridを継承しています。単体で使いまわす予定があるためです。

     

    > TabControl の ContentTemplate プロパティは,
    > TabItem が ContentTemplate プロパティが未設定の時に,
    > TabItem の ContentTemplate に適用されるものですね。

     

    その通りです。そのため、ContentTemplateにDataTemplate型としてctrl_AddressItemをバインディングすれば、全てのタブにctrl_AddressItemが適用されるようになります。

    ついでに言うと、ContentTemplateで適用されるのはタブの中身だけで、ヘッダ部は別途バインディングが必要になります。

     

    余談になりますが、TabItemを継承したコントロールをTabControlに適用する場合、どのプロパティでバインディングすれば良いのでしょうか? 少し試してみたのですが、エラーが出たり変な表示になったりとうまくいきませんで(苦笑)

    2007年8月8日 7:14
  • ctrl_AddressItemはGridを継承しています。

     

    なら,Okでした。余計な心配/説明でした。はい。

     

    # あとからここにくるかもしれない他の人のためということでご勘弁。

    2007年8月8日 7:24
  • 自己レスです。

    検証中新しくわかったことがありますので追記しておきます。

    上記メッセージの中で

     

    > また、AddOwnerを行っている場所で、

    >

    > Private Shared dpFunctionKey As DependencyProperty _
    >     = frmMain.dpSample.AddOwner(GetType(ctrl_AddressTab), GetMetadata)

    > Private Shared dpDummy As DependencyProperty _
    >     = frmMain.dpSample.AddOwner(GetType(System.Windows.Controls.Control), GetMetadata)

    > のように、DPシステムへControlをオーナーとした依存プロパティの追加を行ってやると、

    > 標準のControlコントロールへのバインディングでも依存プロパティの値が継承できました。

     

    と書きましたが、ResourceセクションでControlTemplateとして継承コントロールを宣言しておき、

    これをバインディングする場合に

    ・初期状態(フォームが開く時)にバインディングされている場合→依存プロパティは継承される

    ・Triggerでテンプレートを入れ替えた場合→入れ替えたテンプレートには依存プロパティが継承されない

    という現象が見られました。

     

    推測ですが、ItemsControlのDataTemplateの場合、ItemsSourceがバインディングされた段階で

    テンプレートを動的に作成しますので、上記のTriggerで入れ替えた場合と同様の現象が

    起きているのではないかと思います。

    とりあえずの処置として、DataTemplateの定義時に継承させたい依存プロパティ同士を

    バインディングしておけば問題なく引き渡すことができましたので、それで凌ぐことにします。

     

    …ただFontsizeとかの標準で実装されているプロパティだと、どんな条件下でもちゃんと継承されるんですよねぇ…

    そう考えると方法はあるはずなんですが、何が違うんでしょうね?(苦笑)

    2007年8月30日 7:48
  • ちょっと古いスレッドなので、進展があったかどうか解りませんが、

     

    標準のFontSizeプロパティは、TextElement.FontSizePropertyでアタッチプロパティとして

    登録されたDependencyPropertyからAddOwnerやOverrideMetadataされています。

     

    アタッチプロパティでRegisterされている所がみそなのかもしれません。

     

    (未検証ですいません。m(__)m)

    2007年9月4日 12:08