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

回答編集履歴

2

見直しキャンペーン中

2023/08/14 10:10

投稿

TN8001
TN8001

スコア10108

answer CHANGED
@@ -295,8 +295,8 @@
295
295
  }
296
296
  }
297
297
  ```
298
-
299
298
  「WinForms的発想」のほうを一時しのぎのつもりで書きましたが、かえって難しいしトータル行数も大差ない気がします^^;
299
+ ![アプリ画像](https://ddjkaamml8q8x.cloudfront.net/questions/2023-08-14/40d47835-a91b-44e4-b2da-a8ac5e0062f7.png)
300
300
 
301
301
  ---
302
302
 

1

見直しキャンペーン中

2023/07/27 16:16

投稿

TN8001
TN8001

スコア10108

answer CHANGED
@@ -1,303 +1,303 @@
1
- > コード作成者がすでに不在で既存部分も理解できていないため1から学習を始めるのですが、本件はすぐに対応が必要なため一時的にしのぎたいです。
2
-
3
- 本当に何もかもわからない状態だとすると、なかなか厳しいと思いますね。
4
- 少なくともテスト用のミニマムなプロジェクトを作って、試されたほうがいいと思います(提示コードはなんだかさっぱりわかりません)
5
-
6
- `DataGrid`の仮想化を切っても使える程度の少量の件数であれば、WinForms的な発想もありえます。
7
- 具体的には`CellEditEnding`で`cell.Background = Brushes.Yellow`のような(あくまでイメージです。実際はこれではダメです)
8
-
9
- ```xaml
10
- <DataGrid
11
- EnableColumnVirtualization="False"
12
- EnableRowVirtualization="False"
13
- ```
14
- こうしてみてとてもじゃないが使いものにならない(読み込み速度やスクロール・カラムサイズ変更等)なら、このアプローチはすっぱり諦めたほうがいいでしょう(手元では1000件程度でもかなり厳しい)
15
-
16
- WPF的には98mateさんの調査通り、おそらく`DataTrigger`でやるのが真っ当なのでしょう。
17
-
18
- > →(疑問)DataTriggerっていつ反映される?
19
-
20
- やってみたらすぐわかるのでは?(WPFでは`INotifyPropertyChanged`は欠かせないので、必ず押さえておいてください)
21
-
22
- > →(疑問)これだと背景を変えるセル(座標)が不明?参考サイトだと元々バインドされたプロパティが変化したときの例。
23
-
24
- 発想が逆です。セルごとに状態が必要ならば、セル個数分プロパティが必要です。
25
-
26
- ```xaml
27
- <Window
28
- x:Class="Questions346520.MainWindow"
29
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
30
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
31
- Width="800"
32
- Height="450">
33
- <Window.Resources>
34
- <Style x:Key="dirtyStyle" BasedOn="{StaticResource {x:Type DataGridCell}}" TargetType="DataGridCell">
35
- <Setter Property="Background" Value="Yellow" />
36
- </Style>
37
- </Window.Resources>
38
- <Grid>
39
- <Grid.ColumnDefinitions>
40
- <ColumnDefinition />
41
- <ColumnDefinition />
42
- </Grid.ColumnDefinitions>
43
-
44
- <GroupBox Header="WinForms的発想">
45
- <DockPanel>
46
- <Button Click="Button1_Click" Content="更新" DockPanel.Dock="Top" />
47
- <DataGrid
48
- x:Name="dataGrid1"
49
- AutoGenerateColumns="False"
50
- BeginningEdit="DataGrid1_BeginningEdit"
51
- CellEditEnding="DataGrid1_CellEditEnding"
52
- EnableColumnVirtualization="False"
53
- EnableRowVirtualization="False"
54
- ItemsSource="{Binding Records1}"
55
- SelectionUnit="CellOrRowHeader">
56
- <DataGrid.Columns>
57
- <DataGridTextColumn Binding="{Binding No}" Header="No" IsReadOnly="True" />
58
- <DataGridTextColumn Binding="{Binding UshortValue}" Header="ushort" />
59
- <DataGridTextColumn Binding="{Binding StringValue}" Header="string" />
60
- </DataGrid.Columns>
61
- </DataGrid>
62
- </DockPanel>
63
- </GroupBox>
64
-
65
- <GroupBox Grid.Column="1" Header="DataTrigger">
66
- <DockPanel>
67
- <Button Click="Button2_Click" Content="更新" DockPanel.Dock="Top" />
68
- <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Records2}" SelectionUnit="CellOrRowHeader">
69
- <DataGrid.Columns>
70
- <DataGridTextColumn Binding="{Binding No}" Header="No" IsReadOnly="True" />
71
- <DataGridTextColumn Binding="{Binding UshortValue}" Header="ushort">
72
- <DataGridTextColumn.CellStyle>
73
- <Style TargetType="DataGridCell">
74
- <Style.Triggers>
75
- <DataTrigger Binding="{Binding IsDirtyUshortValue}" Value="True">
76
- <Setter Property="Background" Value="Yellow" />
77
- </DataTrigger>
78
- </Style.Triggers>
79
- </Style>
80
- </DataGridTextColumn.CellStyle>
81
- </DataGridTextColumn>
82
- <DataGridTextColumn Binding="{Binding StringValue}" Header="string">
83
- <DataGridTextColumn.CellStyle>
84
- <Style TargetType="DataGridCell">
85
- <Style.Triggers>
86
- <DataTrigger Binding="{Binding IsDirtyStringValue}" Value="True">
87
- <Setter Property="Background" Value="Yellow" />
88
- </DataTrigger>
89
- </Style.Triggers>
90
- </Style>
91
- </DataGridTextColumn.CellStyle>
92
- </DataGridTextColumn>
93
- </DataGrid.Columns>
94
- </DataGrid>
95
- </DockPanel>
96
- </GroupBox>
97
- </Grid>
98
- </Window>
99
- ```
100
-
101
- ```C#
102
- using System;
103
- using System.Collections.Generic;
104
- using System.ComponentModel;
105
- using System.Linq;
106
- using System.Runtime.CompilerServices;
107
- using System.Windows;
108
- using System.Windows.Controls;
109
- using System.Windows.Media;
110
-
111
- namespace Questions346520
112
- {
113
- public partial class MainWindow : Window
114
- {
115
- public List<Record1> Records1 { get; } = new List<Record1>();
116
- public List<Record2> Records2 { get; } = new List<Record2>();
117
-
118
- private bool isDirty1;
119
- private string oldValue;
120
- private Style defaultStyle;
121
- private Style dirtyStyle;
122
-
123
- public MainWindow()
124
- {
125
- InitializeComponent();
126
- DataContext = this;
127
-
128
- defaultStyle = FindResource(typeof(DataGridCell)) as Style;
129
- dirtyStyle = FindResource("dirtyStyle") as Style;
130
-
131
- for (var i = 0; i < 1000; i++)
132
- {
133
- Records1.Add(new Record1()
134
- {
135
- No = i,
136
- UshortValue = (ushort)(i * 10),
137
- StringValue = (i * 100).ToString(),
138
- });
139
- // ↑↓片方ずつコメント化してパフォーマンスチェック
140
- Records2.Add(new Record2()
141
- {
142
- No = i,
143
- UshortValue = (ushort)(i * 10),
144
- StringValue = (i * 100).ToString(),
145
- IsDirtyUshortValue = false,
146
- IsDirtyStringValue = false,
147
- });
148
- }
149
- }
150
-
151
- private void DataGrid1_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
152
- {
153
- if (e.EditingEventArgs.Source is TextBlock textBlock)
154
- oldValue = textBlock.Text;
155
-
156
- if (e.EditingEventArgs.Source is DataGridCell cell)
157
- oldValue = (cell.Content as TextBlock).Text;
158
- }
159
-
160
- private void DataGrid1_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
161
- {
162
- if (e.EditAction != DataGridEditAction.Commit)
163
- {
164
- oldValue = null;
165
- return;
166
- }
167
-
168
- var textbox = e.EditingElement as TextBox;
169
- var newValue = textbox.Text;
170
- if (oldValue == newValue)
171
- {
172
- oldValue = null;
173
- return;
174
- }
175
-
176
- var cell = textbox.Parent as DataGridCell;
177
- cell.Style = dirtyStyle;
178
- isDirty1 = true;
179
- }
180
-
181
- private void Button1_Click(object sender, RoutedEventArgs e)
182
- {
183
- if (!isDirty1) return;
184
-
185
- foreach (var cell in dataGrid1.Descendants<DataGridCell>())
186
- cell.Style = defaultStyle;
187
-
188
- isDirty1 = false;
189
- }
190
-
191
- private void Button2_Click(object sender, RoutedEventArgs e)
192
- {
193
- if (!Record2.IsDirty2) return;
194
-
195
- foreach (var record2 in Records2)
196
- {
197
- record2.IsDirtyUshortValue = false;
198
- record2.IsDirtyStringValue = false;
199
- }
200
- Record2.IsDirty2 = false;
201
- }
202
-
203
-
204
- public class Record1
205
- {
206
- public int No { get; set; }
207
- public ushort UshortValue { get; set; }
208
- public string StringValue { get; set; }
209
- }
210
-
211
- public class Record2 : Observable
212
- {
213
- // ダサいがこうするしかないか?
214
- public static bool IsDirty2;
215
-
216
- public int No { get; set; }
217
-
218
- public ushort UshortValue
219
- {
220
- get => _UshortValue;
221
- set
222
- {
223
- if (_UshortValue != value)
224
- {
225
- _UshortValue = value;
226
- IsDirtyUshortValue = true;
227
- }
228
- }
229
- }
230
- private ushort _UshortValue;
231
-
232
- // ValueはPropertyChangedを発砲する必要はないが、↑は面倒なので使っちゃうw
233
- public string StringValue
234
- {
235
- get => _StringValue;
236
- set { if (SetProperty(ref _StringValue, value)) IsDirtyStringValue = true; }
237
- }
238
- private string _StringValue;
239
-
240
- // こちらはコードから変更されるため、PropertyChangedを発砲しないとViewに反映されない
241
- public bool IsDirtyUshortValue
242
- {
243
- get => _IsDirtyUshortValue;
244
- set { if (SetProperty(ref _IsDirtyUshortValue, value)) IsDirty2 = true; }
245
- }
246
- private bool _IsDirtyUshortValue;
247
-
248
- public bool IsDirtyStringValue
249
- {
250
- get => _IsDirtyStringValue;
251
- set { if (SetProperty(ref _IsDirtyStringValue, value)) IsDirty2 = true; }
252
- }
253
- private bool _IsDirtyStringValue;
254
- }
255
- }
256
-
257
- // [VisualTreeの子孫要素を取得する - xin9le.net](https://blog.xin9le.net/entry/2013/10/29/222336)
258
- static class DependencyObjectExtensions
259
- {
260
- public static IEnumerable<DependencyObject> Children(this DependencyObject obj)
261
- {
262
- if (obj == null) throw new ArgumentNullException(nameof(obj));
263
- var count = VisualTreeHelper.GetChildrenCount(obj);
264
- if (count == 0) yield break;
265
- for (var i = 0; i < count; i++)
266
- {
267
- var child = VisualTreeHelper.GetChild(obj, i);
268
- if (child != null) yield return child;
269
- }
270
- }
271
- public static IEnumerable<DependencyObject> Descendants(this DependencyObject obj)
272
- {
273
- if (obj == null) throw new ArgumentNullException(nameof(obj));
274
- foreach (var child in obj.Children())
275
- {
276
- yield return child;
277
- foreach (var grandChild in child.Descendants())
278
- yield return grandChild;
279
- }
280
- }
281
- public static IEnumerable<T> Descendants<T>(this DependencyObject obj) where T : DependencyObject => obj.Descendants().OfType<T>();
282
- }
283
-
284
- public class Observable : INotifyPropertyChanged
285
- {
286
- public event PropertyChangedEventHandler PropertyChanged;
287
- protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
288
- {
289
- if (Equals(storage, value)) return false;
290
- storage = value;
291
- OnPropertyChanged(propertyName);
292
- return true;
293
- }
294
- protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
295
- }
296
- }
297
- ```
298
-
299
- 「WinForms的発想」のほうを一時しのぎのつもりで書きましたが、かえって難しいしトータル行数も大差ない気がします^^;
300
-
301
- ---
302
-
1
+ > コード作成者がすでに不在で既存部分も理解できていないため1から学習を始めるのですが、本件はすぐに対応が必要なため一時的にしのぎたいです。
2
+
3
+ 本当に何もかもわからない状態だとすると、なかなか厳しいと思いますね。
4
+ 少なくともテスト用のミニマムなプロジェクトを作って、試されたほうがいいと思います(提示コードはなんだかさっぱりわかりません)
5
+
6
+ `DataGrid`の仮想化を切っても使える程度の少量の件数であれば、WinForms的な発想もありえます。
7
+ 具体的には`CellEditEnding`で`cell.Background = Brushes.Yellow`のような(あくまでイメージです。実際はこれではダメです)
8
+
9
+ ```xml
10
+ <DataGrid
11
+ EnableColumnVirtualization="False"
12
+ EnableRowVirtualization="False">
13
+ ```
14
+ こうしてみてとてもじゃないが使いものにならない(読み込み速度やスクロール・カラムサイズ変更等)なら、このアプローチはすっぱり諦めたほうがいいでしょう(手元では1000件程度でもかなり厳しい)
15
+
16
+ WPF的には98mateさんの調査通り、おそらく`DataTrigger`でやるのが真っ当なのでしょう。
17
+
18
+ > →(疑問)DataTriggerっていつ反映される?
19
+
20
+ やってみたらすぐわかるのでは?(WPFでは`INotifyPropertyChanged`は欠かせないので、必ず押さえておいてください)
21
+
22
+ > →(疑問)これだと背景を変えるセル(座標)が不明?参考サイトだと元々バインドされたプロパティが変化したときの例。
23
+
24
+ 発想が逆です。セルごとに状態が必要ならば、セル個数分プロパティが必要です。
25
+
26
+ ```xml
27
+ <Window
28
+ x:Class="Questions346520.MainWindow"
29
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
30
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
31
+ Width="800"
32
+ Height="450">
33
+ <Window.Resources>
34
+ <Style x:Key="dirtyStyle" BasedOn="{StaticResource {x:Type DataGridCell}}" TargetType="DataGridCell">
35
+ <Setter Property="Background" Value="Yellow" />
36
+ </Style>
37
+ </Window.Resources>
38
+ <Grid>
39
+ <Grid.ColumnDefinitions>
40
+ <ColumnDefinition />
41
+ <ColumnDefinition />
42
+ </Grid.ColumnDefinitions>
43
+
44
+ <GroupBox Header="WinForms的発想">
45
+ <DockPanel>
46
+ <Button Click="Button1_Click" Content="更新" DockPanel.Dock="Top" />
47
+ <DataGrid
48
+ x:Name="dataGrid1"
49
+ AutoGenerateColumns="False"
50
+ BeginningEdit="DataGrid1_BeginningEdit"
51
+ CellEditEnding="DataGrid1_CellEditEnding"
52
+ EnableColumnVirtualization="False"
53
+ EnableRowVirtualization="False"
54
+ ItemsSource="{Binding Records1}"
55
+ SelectionUnit="CellOrRowHeader">
56
+ <DataGrid.Columns>
57
+ <DataGridTextColumn Binding="{Binding No}" Header="No" IsReadOnly="True" />
58
+ <DataGridTextColumn Binding="{Binding UshortValue}" Header="ushort" />
59
+ <DataGridTextColumn Binding="{Binding StringValue}" Header="string" />
60
+ </DataGrid.Columns>
61
+ </DataGrid>
62
+ </DockPanel>
63
+ </GroupBox>
64
+
65
+ <GroupBox Grid.Column="1" Header="DataTrigger">
66
+ <DockPanel>
67
+ <Button Click="Button2_Click" Content="更新" DockPanel.Dock="Top" />
68
+ <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Records2}" SelectionUnit="CellOrRowHeader">
69
+ <DataGrid.Columns>
70
+ <DataGridTextColumn Binding="{Binding No}" Header="No" IsReadOnly="True" />
71
+ <DataGridTextColumn Binding="{Binding UshortValue}" Header="ushort">
72
+ <DataGridTextColumn.CellStyle>
73
+ <Style TargetType="DataGridCell">
74
+ <Style.Triggers>
75
+ <DataTrigger Binding="{Binding IsDirtyUshortValue}" Value="True">
76
+ <Setter Property="Background" Value="Yellow" />
77
+ </DataTrigger>
78
+ </Style.Triggers>
79
+ </Style>
80
+ </DataGridTextColumn.CellStyle>
81
+ </DataGridTextColumn>
82
+ <DataGridTextColumn Binding="{Binding StringValue}" Header="string">
83
+ <DataGridTextColumn.CellStyle>
84
+ <Style TargetType="DataGridCell">
85
+ <Style.Triggers>
86
+ <DataTrigger Binding="{Binding IsDirtyStringValue}" Value="True">
87
+ <Setter Property="Background" Value="Yellow" />
88
+ </DataTrigger>
89
+ </Style.Triggers>
90
+ </Style>
91
+ </DataGridTextColumn.CellStyle>
92
+ </DataGridTextColumn>
93
+ </DataGrid.Columns>
94
+ </DataGrid>
95
+ </DockPanel>
96
+ </GroupBox>
97
+ </Grid>
98
+ </Window>
99
+ ```
100
+
101
+ ```cs
102
+ using System;
103
+ using System.Collections.Generic;
104
+ using System.ComponentModel;
105
+ using System.Linq;
106
+ using System.Runtime.CompilerServices;
107
+ using System.Windows;
108
+ using System.Windows.Controls;
109
+ using System.Windows.Media;
110
+
111
+ namespace Questions346520
112
+ {
113
+ public partial class MainWindow : Window
114
+ {
115
+ public List<Record1> Records1 { get; } = new List<Record1>();
116
+ public List<Record2> Records2 { get; } = new List<Record2>();
117
+
118
+ private bool isDirty1;
119
+ private string oldValue;
120
+ private Style defaultStyle;
121
+ private Style dirtyStyle;
122
+
123
+ public MainWindow()
124
+ {
125
+ InitializeComponent();
126
+ DataContext = this;
127
+
128
+ defaultStyle = FindResource(typeof(DataGridCell)) as Style;
129
+ dirtyStyle = FindResource("dirtyStyle") as Style;
130
+
131
+ for (var i = 0; i < 1000; i++)
132
+ {
133
+ Records1.Add(new Record1()
134
+ {
135
+ No = i,
136
+ UshortValue = (ushort)(i * 10),
137
+ StringValue = (i * 100).ToString(),
138
+ });
139
+ // ↑↓片方ずつコメント化してパフォーマンスチェック
140
+ Records2.Add(new Record2()
141
+ {
142
+ No = i,
143
+ UshortValue = (ushort)(i * 10),
144
+ StringValue = (i * 100).ToString(),
145
+ IsDirtyUshortValue = false,
146
+ IsDirtyStringValue = false,
147
+ });
148
+ }
149
+ }
150
+
151
+ private void DataGrid1_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
152
+ {
153
+ if (e.EditingEventArgs.Source is TextBlock textBlock)
154
+ oldValue = textBlock.Text;
155
+
156
+ if (e.EditingEventArgs.Source is DataGridCell cell)
157
+ oldValue = (cell.Content as TextBlock).Text;
158
+ }
159
+
160
+ private void DataGrid1_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
161
+ {
162
+ if (e.EditAction != DataGridEditAction.Commit)
163
+ {
164
+ oldValue = null;
165
+ return;
166
+ }
167
+
168
+ var textbox = e.EditingElement as TextBox;
169
+ var newValue = textbox.Text;
170
+ if (oldValue == newValue)
171
+ {
172
+ oldValue = null;
173
+ return;
174
+ }
175
+
176
+ var cell = textbox.Parent as DataGridCell;
177
+ cell.Style = dirtyStyle;
178
+ isDirty1 = true;
179
+ }
180
+
181
+ private void Button1_Click(object sender, RoutedEventArgs e)
182
+ {
183
+ if (!isDirty1) return;
184
+
185
+ foreach (var cell in dataGrid1.Descendants<DataGridCell>())
186
+ cell.Style = defaultStyle;
187
+
188
+ isDirty1 = false;
189
+ }
190
+
191
+ private void Button2_Click(object sender, RoutedEventArgs e)
192
+ {
193
+ if (!Record2.IsDirty2) return;
194
+
195
+ foreach (var record2 in Records2)
196
+ {
197
+ record2.IsDirtyUshortValue = false;
198
+ record2.IsDirtyStringValue = false;
199
+ }
200
+ Record2.IsDirty2 = false;
201
+ }
202
+
203
+
204
+ public class Record1
205
+ {
206
+ public int No { get; set; }
207
+ public ushort UshortValue { get; set; }
208
+ public string StringValue { get; set; }
209
+ }
210
+
211
+ public class Record2 : Observable
212
+ {
213
+ // ダサいがこうするしかないか?
214
+ public static bool IsDirty2;
215
+
216
+ public int No { get; set; }
217
+
218
+ public ushort UshortValue
219
+ {
220
+ get => _UshortValue;
221
+ set
222
+ {
223
+ if (_UshortValue != value)
224
+ {
225
+ _UshortValue = value;
226
+ IsDirtyUshortValue = true;
227
+ }
228
+ }
229
+ }
230
+ private ushort _UshortValue;
231
+
232
+ // ValueはPropertyChangedを発砲する必要はないが、↑は面倒なので使っちゃうw
233
+ public string StringValue
234
+ {
235
+ get => _StringValue;
236
+ set { if (SetProperty(ref _StringValue, value)) IsDirtyStringValue = true; }
237
+ }
238
+ private string _StringValue;
239
+
240
+ // こちらはコードから変更されるため、PropertyChangedを発砲しないとViewに反映されない
241
+ public bool IsDirtyUshortValue
242
+ {
243
+ get => _IsDirtyUshortValue;
244
+ set { if (SetProperty(ref _IsDirtyUshortValue, value)) IsDirty2 = true; }
245
+ }
246
+ private bool _IsDirtyUshortValue;
247
+
248
+ public bool IsDirtyStringValue
249
+ {
250
+ get => _IsDirtyStringValue;
251
+ set { if (SetProperty(ref _IsDirtyStringValue, value)) IsDirty2 = true; }
252
+ }
253
+ private bool _IsDirtyStringValue;
254
+ }
255
+ }
256
+
257
+ // [VisualTreeの子孫要素を取得する - xin9le.net](https://blog.xin9le.net/entry/2013/10/29/222336)
258
+ static class DependencyObjectExtensions
259
+ {
260
+ public static IEnumerable<DependencyObject> Children(this DependencyObject obj)
261
+ {
262
+ if (obj == null) throw new ArgumentNullException(nameof(obj));
263
+ var count = VisualTreeHelper.GetChildrenCount(obj);
264
+ if (count == 0) yield break;
265
+ for (var i = 0; i < count; i++)
266
+ {
267
+ var child = VisualTreeHelper.GetChild(obj, i);
268
+ if (child != null) yield return child;
269
+ }
270
+ }
271
+ public static IEnumerable<DependencyObject> Descendants(this DependencyObject obj)
272
+ {
273
+ if (obj == null) throw new ArgumentNullException(nameof(obj));
274
+ foreach (var child in obj.Children())
275
+ {
276
+ yield return child;
277
+ foreach (var grandChild in child.Descendants())
278
+ yield return grandChild;
279
+ }
280
+ }
281
+ public static IEnumerable<T> Descendants<T>(this DependencyObject obj) where T : DependencyObject => obj.Descendants().OfType<T>();
282
+ }
283
+
284
+ public class Observable : INotifyPropertyChanged
285
+ {
286
+ public event PropertyChangedEventHandler PropertyChanged;
287
+ protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
288
+ {
289
+ if (Equals(storage, value)) return false;
290
+ storage = value;
291
+ OnPropertyChanged(propertyName);
292
+ return true;
293
+ }
294
+ protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
295
+ }
296
+ }
297
+ ```
298
+
299
+ 「WinForms的発想」のほうを一時しのぎのつもりで書きましたが、かえって難しいしトータル行数も大差ない気がします^^;
300
+
301
+ ---
302
+
303
303
  行ごとの判定でよければ`DataTable`をソースにすれば、簡単にできそうな気はします(おそらく「それじゃダメ」と言われそうなので特に調べていませんが^^;