質問するログイン新規登録

回答編集履歴

1

コレクションのアイテムの変更通知について追記

2016/08/06 08:42

投稿

flied_onion
flied_onion

スコア2604

answer CHANGED
@@ -124,4 +124,126 @@
124
124
 
125
125
  public event PropertyChangedEventHandler PropertyChanged;
126
126
  }
127
- ```
127
+ ```
128
+ ---
129
+ ## 追記
130
+
131
+ 上記のListName0 と ListName1 を別個のプロパティではなく、配列 ListName[] として持たせたいということですね。
132
+
133
+ この場合、ListNameのsetterの処理によってRaisePropertyChangedが呼ばれても、そもそも 配列ListNameをまるごと更新することはないので、そのsetterは使わることがなく、RaisePropertyChangedが発行されることはありません。
134
+
135
+ * MyModel のプロパティを配列にした場合
136
+ (MainWindowの変更は省略。Bindingに設定するnameは `"ListName[" + iKoumokuNo + "]";` にすれば良い)
137
+ ```cs
138
+ private string[] listName = {"Hello 0", "Hello 1"};
139
+ public string[] ListName {
140
+ get { return listName; }
141
+ set { SetProperty(ref listName, value, "ListName"); }
142
+ }
143
+ ```
144
+
145
+ このセッターは要素への代入では呼ばれない。
146
+ ```cs
147
+ private void Button_Click(object sender, RoutedEventArgs e) {
148
+ vm.ListName[0] = "a"; // これではListNameのセッターは呼ばれない
149
+ ```
150
+
151
+ 一番単純な解決策は、要素へのSetメソッドを用意してPropertyChangedを呼ぶことです。
152
+
153
+ MyModel
154
+ ```
155
+ public void SetListName(int index, string value) {
156
+ listName[index] = value;
157
+ var h = PropertyChanged;
158
+ if (h != null)
159
+ h(this, new PropertyChangedEventArgs("ListName"));
160
+ }
161
+ ```
162
+
163
+ MainWindow#Button_Click
164
+ ```
165
+ private void Button_Click(object sender, RoutedEventArgs e) {
166
+ vm.SetListName(0, "a");
167
+ vm.SetListName(1, "b");
168
+ }
169
+ ```
170
+
171
+ ただ使い方が変わるので扱いにくいですね。
172
+ WPFには `System.Collections.ObjectModel.ObservableCollection<T>` があるのでこれを利用することができます。
173
+
174
+ ```cs
175
+ // using System.Collections.ObjectModel; を追加する。
176
+ public partial class MainWindow : Window {
177
+ public MainWindow() {
178
+ InitializeComponent();
179
+ }
180
+
181
+ private MyModel vm = new MyModel();
182
+ private void Window_Loaded(object sender, RoutedEventArgs e) {
183
+
184
+ List<Label> labelns = new List<Label> { Label0, Label1 };
185
+ for (var i = 0; i < labelns.Count; i++) {
186
+ Binding textNamebinding = new Binding();
187
+ var iKoumokuNo = i; var iLabelNo = i;
188
+ string name = "ListName[" + iKoumokuNo + "]";
189
+ textNamebinding.Path = new PropertyPath(name);
190
+ textNamebinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
191
+ BindingOperations.SetBinding(labelns[iLabelNo], Label.ContentProperty, textNamebinding);
192
+ }
193
+
194
+ this.DataContext = vm;
195
+ }
196
+
197
+ private void Button_Click(object sender, RoutedEventArgs e) {
198
+ vm.ListName[0] = "a";
199
+ vm.ListName[1] = "b";
200
+ }
201
+
202
+
203
+ public class MyModel : INotifyPropertyChanged {
204
+ private ObservableCollection<string> listName = new ObservableCollection<string>();
205
+
206
+ public MyModel() {
207
+ listName.Add("Hello 0");
208
+ listName.Add("Hello 1");
209
+
210
+ listName.CollectionChanged += listName_CollectionChanged;
211
+ }
212
+
213
+
214
+ public ObservableCollection<string> ListName {
215
+ get {
216
+ return listName;
217
+ }
218
+ set {
219
+ SetProperty(ref listName, value, "ListName");
220
+ }
221
+ }
222
+
223
+ void listName_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
224
+ // 項目の変更やリスト全体の変更時に発生するイベント
225
+ var h = PropertyChanged;
226
+ if (h != null)
227
+ h(this, new PropertyChangedEventArgs("ListName"));
228
+
229
+ }
230
+
231
+ public virtual bool SetProperty(ref ObservableCollection<string> target, ObservableCollection<string> value, string name) {
232
+ // サンプルでは使ってないのでnullチェック省略省略
233
+ target = value;
234
+ RaisePropertyChanged(name);
235
+ return true;
236
+ }
237
+
238
+ protected virtual void RaisePropertyChanged(string name) {
239
+ var h = PropertyChanged;
240
+ if (h != null)
241
+ h(this, new PropertyChangedEventArgs(name));
242
+ }
243
+
244
+ public event PropertyChangedEventHandler PropertyChanged;
245
+ }
246
+ }
247
+ ```
248
+
249
+ もっと良いやり方もあるかもしれませんが、これで目的は達成できるかと思います。