質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

4回答

4348閲覧

プロパティを持つ依存プロパティ(オブジェクト)の実装方法について教えてください。

nekome4

総合スコア24

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

0クリップ

投稿2018/09/26 04:14

編集2018/09/28 12:52

カスタムコントロールの依存プロパティを複数のプロパティを持つオブジェクトにしたいと考えています。
この依存プロパティは各プロパティのデータが全て揃った時点でコントロールに反映させたいため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を使ってみましたがこちらは問題は発生しませんでした。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gaya-K

2018/09/26 22:17 編集

Hoge を struct にする必要があるのでしょうか? class にしていれば大半の問題は起きないと思います。
nekome4

2018/09/27 11:01

構造体にしたのは冒頭でも説明しましたが、値型ならFoo=fooとした場合にプロパティの値が変更されたという通知が発生するからです。これがクラスであった場合は通知が発生しないのでバインディングがうまく機能しません。
guest

回答4

0

Foo に INotifyPropertyChanged を実装するか、Foo に Freezable or DispatcherObject を継承させればいいかと。
xaml から使う分にはそれで十分ですが、コードビハインドで使うにはイベントハンドラの購読が必要です。

##追記

INotifyPropertyChanged にピンとこないのであれば、WPF で非常に重要な知識が身についてないことになります。
これをなくしてデータバインドは語れませんし、WPF の魅力を発揮できません。
まずは以下のリンクで同じように悩んだ方の記事を読んでみてください。コメント欄に解説もあります。

wpfのBindingでハマり

提示されたコードではわざわざカスタムコントロールを作る必要がありません。
なので、まずはデータバインドの仕組みや MVVM パターンの記事をいろいろ漁って読んでみてください。

追記2

Hoge と Thickness の違だけ先に回答します。
Thickness は IEquatable<T> を実装していて、等値比較ができるようになっています。
これにより値の変化を検知しているのだと思います。

追記3

"B"のコントロール領域内でマウスダウンをするとこのコントロールのFooとPaddingに値が代入されてしまうので以降"A"でマウスダウンをしても反映されなくなってしまいます。これはバグ?仕様?わかりません・・・。

明確なソースが見つからないですが、仕様だと思います。
代入時点でバインドが消失するのでしょう。

ちなみに私なら極力コードビハインドを使わずこんな風に書きます。
(Foo は VM のプロパティにします。)

cs

1public class Hoge : INotifyPropertyChanged 2{ 3 // イベント省略 4 public int X {get; private set;} 5 public int Y {get; private set;} 6 public void Set(int x, int y) 7 { 8 X=x; 9 Y=y; 10 } 11}

xml

1<Style TargetType="TextBox"> 2 <Setter Property="Text"> 3 <Setter.Value> 4 <MultiBinding StringFormat="{}X: {0}, Y: {1}"> 5 <Binding Path="Foo.X"/> 6 <Binding Path="Foo.Y"/> 7 </MultiBinding> 8 </Setter.Value> 9 </Setter> 10</Style>

投稿2018/09/27 11:19

編集2018/10/01 09:55
gaya-K

総合スコア449

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nekome4

2018/09/27 11:52

ご返信ありがとうございます。 申し訳ありません。 内容がよくわからないためどの質問に対する回答なのかがわかりません。。 具体的にどうすればいいのでしょうか?
nekome4

2018/09/27 23:54

ご返信ありがとうございます。 INotifyPropertyChanged やMVVMパターンなどは一通り@IT等のサイトで勉強したので仕組みも理解しているつもりです。 他の内容に関しましては調べましたがよくわかりませんでした。 今回Fooのプロパティ1つ1つに対しての変更通知は必要ありません。 というよりかは逆にしてほしくないためPaddingやMarginの実装方法?を参考にしました。 このあたりの実装について参考になるサイトや資料が無かったので質問した次第です。 >提示されたコードではわざわざカスタムコントロールを作る必要がありません。 現在カスタムコントロールを作っているので似たようなコードをわかりやすく簡略化しサンプルとして掲示しました。
gaya-K

2018/09/28 09:02

よくよく考えると IEquatable も関係ない気がしてきました。
nekome4

2018/09/28 12:00

いろいろやってみて質問5の現象がわかりました。 簡潔に申し上げますとインスタンスの生成が原因でした。 理由はわかりませんが構造体の依存プロパティに対してインスタンスを生成する(値渡しをする)とOneWayが機能しなくなるようです。 試しにPaddingの値を弄ったところ同様の現象が発生しました。 ただし以下のような条件があるようです。 ・ターゲットとソースで同一の構造体の依存プロパティを使用する。 ・ターゲットプロパティに対して値を代入する。ソースはOK。 (ちょっとわかりにくいかもしれませんのでサンプルコードを修正しておきます。)
nekome4

2018/10/05 15:11

お返事が遅くなりました。 やはり仕様なのですね。 クラスでも同じ事をしてみましたが同様にバインドが消失しました。 >ちなみに私なら極力コードビハインドを使わずこんな風に書きます。 なるほど、こんな記述方法があるのですね。勉強になりました。
guest

0

自己解決

いろいろ探ってみてわかったこととしては、今回の場合の依存プロパティの実装は構造体でもクラスでもよいということがわかりました。
といいますのも、コントロールに対してあるイベントが発生したら依存プロパティのプロパティ全部になんらかの値が代入され最終的に反映されるという仕組みになっていますので構造体の場合はPaddingやMarginなどのようにすればいいですし、クラスの場合はnewでインスタンスを生成すれば変更通知が発生します。

C#

1protected override void On~~~~~~(***** e) 2{ 3base.On~~~~~~(e); 4// _fooのデータの設定(省略) 5 6// 構造体の場合はそのまま代入で変更通知 7Foo = _foo; 8}

C#

1protected override void On~~~~~~(***** e) 2{ 3base.On~~~~~~(e); 4// Fooのデータの設定(省略) 5 6// クラスの場合コピーコンストラクタ等でインスタンス生成し変更通知 7Foo = new Hoge(Foo); 8}

今回、上記のような処理なので依存プロパティの個々のプロパティの変更通知が必要無いためINotifyPropertyChangedは必要ありません(上記でFooのプロパティの変更通知がされる)。
逆にFooのXプロパティだけがどこかで変更されそれを通知しなければいけない場合は必要かもしれません。

投稿2018/10/05 16:03

nekome4

総合スコア24

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

まず、Foo に INotifyPropertyChanged を実装し、個々のプロパティの変更を通知できるようにしてください。
そして次の回答を参考に、Foo の PropertyChanged をフックしてください。

How To Raise Property Changed events on a Dependency Property?

Adding the callback:

C#

1public static DependencyProperty FirstProperty = DependencyProperty.Register( 2 "First", 3 typeof(string), 4 typeof(MyType), 5 new FrameworkPropertyMetadata( 6 false, 7 new PropertyChangedCallback(OnFirstPropertyChanged)));

Raising PropertyChanged in the callback:

C#

1private static void OnFirstPropertyChanged( 2 DependencyObject sender, DependencyPropertyChangedEventArgs e) 3{ 4 PropertyChangedEventHandler h = PropertyChanged; 5 if (h != null) 6 { 7 h(sender, new PropertyChangedEventArgs("Second")); 8 } 9}

投稿2018/09/28 00:17

Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2018/09/28 00:22

Foo がプロパティの変更を通知して問題などないはずです。 通知を得るには PropertyChanged イベントにイベントハンドラを登録する必要があります。 Foo の PropertyChanged イベントにイベントハンドラを登録しなければいいだけです。
nekome4

2018/10/05 15:07

お返事が遅くなりました。 Firstが変更されたらSecondの変更通知をするようになっているのですね。 翻訳してもよくわからなかったため何がしたいのかよくわかりませんでしたが、今回のFooのプロパティに対して変更通知をしたい場合は下記サイトの最初の内容を適用すればよいという事でしょうか。 [PropertyChangedイベントの処理方法] https://blog.okazuki.jp/entry/20091210/1260417214
Zuishin

2018/10/05 15:20

読んでいませんが、やってみてうまくいくのならそれでいいのではないですか? 回答の後で質問の内容が大きく変わっているので何が聞きたいのかわからないのはこちらです。 聞きたいことを一つにしぼり、一つの質問で一つだけ聞いてください。 そして解決したら解決済みにし、次の聞きたいことは新たに質問してください。
nekome4

2018/10/05 16:13

>回答の後で質問の内容が大きく変わっているので何が聞きたいのかわからないのはこちらです。 個々の質問は何も変更はしていないはずですよ。 >聞きたいことを一つにしぼり、一つの質問で一つだけ聞いてください。 バラバラに質問してもよかったのですが、そうすると個々の質問は関連が深いため逆に分かりにくくなるからです。 >そして解決したら解決済みにし、次の聞きたいことは新たに質問してください。 解決してないのに解決済みはおかしいでしょう。
guest

0

失礼しました。
ちょっと考えるのでお時間ください。

投稿2018/09/28 00:07

gaya-K

総合スコア449

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問