カスタムコントロールの依存プロパティを複数のプロパティを持つオブジェクトにしたいと考えています。
この依存プロパティは各プロパティのデータが全て揃った時点でコントロールに反映させたいためMarginやPaddingのように構造体として定義しました。
構造体(値型)ならFoo=fooとした場合にプロパティの値が変更されたという通知が発生するのですが、クラスではダメだったのでこのようにしました。
#サンプルコード
[CustomControl1.cs]
C#
1public struct Hoge 2{ 3 public Hoge(int x, int y) { X = x; Y = y; } 4 public int X { get; set; } 5 public int Y { get; set; } 6} 7 8public class CustomControl1 : TextBox 9{ 10 public static readonly DependencyProperty FooProperty = DependencyProperty.Register( 11 "Foo", typeof(Hoge), 12 new FrameworkPropertyMetadata( 13 new Hoge(1, 1), 14 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 15 (d, e) => { 16 (d as CustomControl1).Text = "X: " + ((Hoge)e.NewValue).X.ToString() + " Y: " + ((Hoge)e.NewValue).Y.ToString(); 17 }) 18 ); 19 20 public Hoge Foo 21 { 22 get { return (Hoge)GetValue(FooProperty); } 23 set { SetValue(FooProperty, value); } 24 } 25 26 static CustomControl1() 27 { 28 DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1))); 29 } 30 31 public override void OnApplyTemplate() 32 { 33 base.OnApplyTemplate(); 34 35 Foo = new Hoge(0, 0); 36 37 var foo = Foo; 38 foo.X = 100; 39 foo.Y = 100; 40 Foo = foo; 41 } 42 43 //...処理など 44}
#質問
ここでいくつか質問があります。
1.目的を実現するにあたってこの実装方法でよいのでしょうか?
2.依存プロパティを構造体にしているため値変更時に一度コピーしてから変更、そして元に戻すという事をしています。ちょっと不細工なのでもっとスマートに記述する方法はありますか?
(編集用のprivateな変数を作りデータが出来上がった(更新時)ら、「Foo=_foo」とするのがいいかと思いました。)
3.デフォルト値をnew Hoge(0,0)としてしまうとOnApplyTemplateでのFoo初期化時に同値?とみなされてしまい変更通知がいかなかったためnew Hoge(1,1)としましたがこの方法しかないでしょうか?
質問5で解決したかもしれません。
4.今回は必要ありませんが、オブジェクトのプロパティごとに変更の通知をしたい場合はどのようにすればよいのでしょう?
当方WPF(C#)初心者のためまだよくわかっていなことがたくさんありますが、どうかご教示よろしくお願いいたします。
#追加質問(解決)
5.以下のように"B"のFooのバインディングモードをOneWayにして"A"のFooの内容を変更しても"B"に反映されません。TwoWayにすると反映されます。何故でしょうか?
XAML
1<Controls1:CustomControl1 x:Name="A" Width="200" Height="50" Grid.Row="0"/> 2 3<Controls1:CustomControl1 x:Name="B" Foo="{Binding ElementName=A, Path=Foo, Mode=OneWay}" Width="200" Height="50" Grid.Row="1"/>
C#
1protected override void OnMouseDown(MouseButtonEventArgs e) 2{ 3 base.OnMouseDown(e); 4 5 // NG 6 Hoge foo = Foo; 7 foo.X += 1; 8 Foo = foo; 9 10 // OK 11 Thickness p = Padding; 12 p.Left += 1; 13 Padding = p; 14/* 15 // HogeをThicknessにした場合もNG 16 Thickness foo = Foo; 17 foo.Left += 1; 18 Foo = foo; 19*/ 20}
Foo(Hoge構造体)をPadding(Thickness構造体)に置き換えた場合はうまくバインディングが機能してくれます。
そこでFooの構造体をPaddingなどで使われているThicknessに置き換えるとやはり同様の問題が発生します。
以上の事からDependencyPropertyの作り方に何か問題があるのではないかと推測しています。
本現象の原因がわかりましたのでサンプルなどを交えて記載しておきます。
原因はOnApplyTemplate()でのHogeのインスタンス生成、「構造体の依存プロパティに対しての値代入」でした。
なので、以下のようにして構造体の依存プロパティに対して値を代入しないようにします。
(質問3での変更通知のために代入していましたが、無くしてしまうと困るので代わりにメタデータからコールバック関数を強制的に呼ぶようにしました。)
C#
1public override void OnApplyTemplate() 2{ 3 base.OnApplyTemplate(); 4 5 // Foo = new Hoge(0, 0); 6 FooProperty.GetMetadata(FooProperty.OwnerType).PropertyChangedCallback( 7 this, new DependencyPropertyChangedEventArgs(FooProperty, Foo, Foo)); 8}
この状態で、"A"のコントロール領域内でマウスダウンをすると"B"に"A"の内容が反映されることが確認できます。
この時、"A"のコントロールのFooやPaddingプロパティに対して値を代入していますが、問題が発生するのはターゲット(この場合"B")のプロパティに対して値を代入する時だけのようです。
なので、"B"のコントロール領域内でマウスダウンをするとこのコントロールのFooとPaddingに値が代入されてしまうので以降"A"でマウスダウンをしても反映されなくなってしまいます。これはバグ?仕様?わかりません・・・。
しかしながらPaddingと同じ動作になっていますのでこれ以上できる事は無いと思いました 。
できることとしては常にTwoWayにするか、OneWayToSourceを使うか、値の更新をしないことが分かっている時のみOneWayを使う・・・ぐらいです。
※HogeにTypeConverterを使ってみましたがこちらは問題は発生しませんでした。
回答4件
あなたの回答
tips
プレビュー