回答編集履歴

1

見直しキャンペーン中

2023/07/23 05:10

投稿

TN8001
TN8001

スコア9862

test CHANGED
@@ -1,383 +1,192 @@
1
1
  一応できましたが、地味に難しいですね^^;
2
-
3
2
  普段`ReactiveProperty`使ってないので間違いや、もっといい方法があるかもしれません。
4
-
5
3
  特にViewでのエラーをどうするか(`HasViewError`)は、考慮がいるかもしれませんね(`ReactiveProperty<string> Number1`としてしまってVM側でintにするとか)
6
-
7
-
8
4
 
9
5
  `NuGet`で`Microsoft.Xaml.Behaviors.Wpf`を入れてください。
10
6
 
11
-
12
-
13
- ```xaml
7
+ ```xml
14
-
15
8
  <Window
16
-
17
9
  x:Class="Questions289115.MainWindow"
18
-
19
10
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
20
-
21
11
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
22
-
23
12
  xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
24
-
25
13
  xmlns:local="clr-namespace:Questions289115"
26
-
27
14
  Width="800"
28
-
29
15
  Height="450">
30
-
31
16
  <Window.DataContext>
32
-
33
17
  <local:ViewModel />
34
-
35
18
  </Window.DataContext>
36
-
37
19
 
38
-
39
20
  <i:Interaction.Behaviors>
40
-
41
21
  <local:ValidationErrorBehavior HasViewError="{Binding HasViewError.Value, Mode=OneWayToSource}" />
42
-
43
22
  </i:Interaction.Behaviors>
44
-
45
23
 
46
-
47
24
  <Window.Resources>
48
-
49
25
  <ControlTemplate x:Key="ValidationTemplate">
50
-
51
26
  <StackPanel>
52
-
53
27
  <ItemsControl HorizontalAlignment="Right" ItemsSource="{Binding AdornedElement.(Validation.Errors), ElementName=validationTarget}">
54
-
55
28
  <ItemsControl.ItemsPanel>
56
-
57
29
  <ItemsPanelTemplate>
58
-
59
30
  <StackPanel Orientation="Horizontal" />
60
-
61
31
  </ItemsPanelTemplate>
62
-
63
32
  </ItemsControl.ItemsPanel>
64
-
65
33
  <ItemsControl.ItemTemplate>
66
-
67
34
  <DataTemplate>
68
-
69
35
  <TextBlock Foreground="Red" Text="{Binding ErrorContent}" />
70
-
71
36
  </DataTemplate>
72
-
73
37
  </ItemsControl.ItemTemplate>
74
-
75
38
  </ItemsControl>
76
-
77
39
  <AdornedElementPlaceholder x:Name="validationTarget" />
78
-
79
40
  </StackPanel>
80
-
81
41
  </ControlTemplate>
82
42
 
83
-
84
-
85
43
  <Style TargetType="TextBox">
86
-
87
44
  <Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplate}" />
88
-
89
45
  </Style>
90
-
91
46
  </Window.Resources>
92
-
93
47
 
94
-
95
48
  <StackPanel>
96
-
97
49
  <HeaderedContentControl Header="Text1">
98
-
99
50
  <TextBox Text="{Binding Text1.Value, UpdateSourceTrigger=PropertyChanged}" />
100
-
51
+ </HeaderedContentControl>
52
+ <HeaderedContentControl Header="Number1">
53
+ <TextBox Text="{Binding Number1.Value, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" />
54
+ </HeaderedContentControl>
55
+ <HeaderedContentControl Header="Text2">
56
+ <TextBox Text="{Binding Text2.Value, UpdateSourceTrigger=PropertyChanged}" />
57
+ </HeaderedContentControl>
58
+ <HeaderedContentControl Header="Number2">
59
+ <TextBox Text="{Binding Number2.Value, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" />
101
60
  </HeaderedContentControl>
102
61
 
103
- <HeaderedContentControl Header="Number1">
104
-
105
- <TextBox Text="{Binding Number1.Value, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" />
106
-
107
- </HeaderedContentControl>
108
-
109
- <HeaderedContentControl Header="Text2">
110
-
111
- <TextBox Text="{Binding Text2.Value, UpdateSourceTrigger=PropertyChanged}" />
112
-
113
- </HeaderedContentControl>
114
-
115
- <HeaderedContentControl Header="Number2">
116
-
117
- <TextBox Text="{Binding Number2.Value, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" />
118
-
119
- </HeaderedContentControl>
120
-
121
-
122
-
123
62
  <Button Command="{Binding Command}" Content="Command" />
124
-
125
63
  </StackPanel>
126
-
127
64
  </Window>
128
-
129
65
  ```
130
66
 
131
-
132
-
133
- ```C#
67
+ ```cs
134
-
135
68
  using System;
136
-
137
69
  using System.ComponentModel;
138
-
139
70
  using System.Diagnostics;
140
-
141
71
  using System.Linq;
142
-
143
72
  using System.Reactive.Linq;
144
-
145
73
  using System.Windows;
146
-
147
74
  using System.Windows.Controls;
148
-
149
75
  using Microsoft.Xaml.Behaviors;
150
-
151
76
  using Reactive.Bindings;
152
-
153
77
  using Reactive.Bindings.Extensions;
154
78
 
155
-
156
-
157
79
  namespace Questions289115
158
-
159
80
  {
160
-
161
81
  public class ViewModel : INotifyPropertyChanged
162
-
163
82
  {
164
-
165
83
  public ReactiveProperty<string> Text1 { get; }
166
-
167
84
  public ReactiveProperty<string> Text2 { get; }
168
-
169
85
  public ReactiveProperty<int> Number1 { get; }
170
-
171
86
  public ReactiveProperty<int> Number2 { get; }
172
-
173
87
  public ReactiveProperty<bool> HasViewError { get; } = new ReactiveProperty<bool>();
174
-
175
-
176
88
 
177
89
  public ReactiveCommand Command { get; }
178
90
 
179
-
180
-
181
91
  public ViewModel()
182
-
183
92
  {
184
-
185
93
  Text1 = new ReactiveProperty<string>()
186
-
187
94
  .SetValidateNotifyError(v =>
188
-
189
95
  {
190
-
191
96
  // 正しく複数エラー対応するならこんなん?(もっとスッキリ書けない?^^;
192
-
193
97
  var errors = new string[] {
194
-
195
98
  string.IsNullOrEmpty(v) ? "何か入力してください。" : null,
196
-
197
99
  v == Text2?.Value ? $"{nameof(Text2)}と同一です。" : null,
198
-
199
100
  }.Where(x => x != null);
200
101
 
102
+ if(0 < errors.Count()) return errors;
103
+ return null;
104
+ });
105
+ Text1.Subscribe(_ => Text2?.ForceValidate()); // もっといいのがありそう??
201
106
 
107
+ Text2 = new ReactiveProperty<string>()
108
+ // 複数エラーとかどうでもいいなら
109
+ .SetValidateNotifyError(v => string.IsNullOrEmpty(v) ? "何か入力してください。"
110
+ : v == Text1?.Value ? $"{nameof(Text1)}と同一です。" : null);
111
+ Text2.Subscribe(_ => Text1?.ForceValidate());
112
+
113
+ Number1 = new ReactiveProperty<int>()
114
+ .SetValidateNotifyError(v =>
115
+ {
116
+ var errors = new string[] {
117
+ v < 0 ? "0以上を入力してください。" : null,
118
+ v == Number2?.Value ? $"{nameof(Number2)}と同一です。" : null,
119
+ }.Where(x => x != null);
202
120
 
203
121
  if(0 < errors.Count()) return errors;
122
+ return null;
123
+ });
124
+ Number1.Subscribe(_ => Number2?.ForceValidate());
204
125
 
205
- return null;
206
-
207
- });
208
-
209
- Text1.Subscribe(_ => Text2?.ForceValidate()); // もっといいのがありそう??
210
-
211
-
212
-
213
- Text2 = new ReactiveProperty<string>()
214
-
215
- // 複数エラーとかどうでもいいなら
216
-
217
- .SetValidateNotifyError(v => string.IsNullOrEmpty(v) ? "何か入力してください。"
218
-
219
- : v == Text1?.Value ? $"{nameof(Text1)}と同一です。" : null);
220
-
221
- Text2.Subscribe(_ => Text1?.ForceValidate());
222
-
223
-
224
-
225
- Number1 = new ReactiveProperty<int>()
126
+ Number2 = new ReactiveProperty<int>()
226
-
227
127
  .SetValidateNotifyError(v =>
228
-
229
128
  {
230
-
231
129
  var errors = new string[] {
232
-
233
130
  v < 0 ? "0以上を入力してください。" : null,
234
-
235
- v == Number2?.Value ? $"{nameof(Number2)}と同一です。" : null,
131
+ v == Number1?.Value ? $"{nameof(Number1)}と同一です。" : null,
236
-
237
132
  }.Where(x => x != null);
238
133
 
239
-
240
-
241
134
  if(0 < errors.Count()) return errors;
242
-
243
135
  return null;
244
-
245
136
  });
246
-
247
- Number1.Subscribe(_ => Number2?.ForceValidate());
248
-
249
-
250
-
251
- Number2 = new ReactiveProperty<int>()
252
-
253
- .SetValidateNotifyError(v =>
254
-
255
- {
256
-
257
- var errors = new string[] {
258
-
259
- v < 0 ? "0以上を入力してください。" : null,
260
-
261
- v == Number1?.Value ? $"{nameof(Number1)}と同一です。" : null,
262
-
263
- }.Where(x => x != null);
264
-
265
-
266
-
267
- if(0 < errors.Count()) return errors;
268
-
269
- return null;
270
-
271
- });
272
-
273
137
  Number2.Subscribe(_ => Number1?.ForceValidate());
274
138
 
275
-
276
-
277
139
  Command = new[]
278
-
279
140
  {
280
-
281
141
  Text1.ObserveHasErrors,
282
-
283
142
  Text2.ObserveHasErrors,
284
-
285
143
  Number1.ObserveHasErrors,
286
-
287
144
  Number2.ObserveHasErrors,
288
-
289
145
  HasViewError,
290
-
291
146
  }
292
-
293
147
  .CombineLatestValuesAreAllFalse()
294
-
295
148
  .ToReactiveCommand();
296
-
297
149
  Command.Subscribe(() => Debug.WriteLine("Execute"));
298
-
299
150
  }
300
151
 
301
-
302
-
303
152
  public event PropertyChangedEventHandler PropertyChanged;
304
-
305
153
  }
306
154
 
307
-
308
-
309
155
  // [MVVMにおけるView層での入力値エラーの有無をViewModelで知る方法 - かずきのBlog@hatena](https://blog.okazuki.jp/entry/20110118/1295338167)
310
-
311
156
  public class ValidationErrorBehavior : Behavior<DependencyObject>
312
-
313
157
  {
314
-
315
158
  public bool HasViewError
316
-
317
159
  {
318
-
319
160
  get => (bool)GetValue(HasViewErrorProperty);
320
-
321
161
  set => SetValue(HasViewErrorProperty, value);
322
-
323
162
  }
324
-
325
163
  public static readonly DependencyProperty HasViewErrorProperty
326
-
327
164
  = DependencyProperty.Register(nameof(HasViewError), typeof(bool),
328
-
329
165
  typeof(ValidationErrorBehavior), new UIPropertyMetadata(false));
330
-
331
166
  private int errroCount;
332
-
333
167
  protected override void OnAttached()
334
-
335
168
  {
336
-
337
169
  base.OnAttached();
338
-
339
170
  Validation.AddErrorHandler(AssociatedObject, ErrorHandler);
340
-
341
171
  }
342
-
343
172
  protected override void OnDetaching()
344
-
345
173
  {
346
-
347
174
  Validation.RemoveErrorHandler(AssociatedObject, ErrorHandler);
348
-
349
175
  base.OnDetaching();
350
-
351
176
  }
352
-
353
177
  private void ErrorHandler(object sender, ValidationErrorEventArgs e)
354
-
355
178
  {
356
-
357
179
  if(e.Action == ValidationErrorEventAction.Added) errroCount++;
358
-
359
180
  else if(e.Action == ValidationErrorEventAction.Removed) errroCount--;
360
-
361
181
  HasViewError = errroCount != 0;
362
-
363
182
  }
364
-
365
183
  }
366
-
367
184
  }
368
-
369
185
  ```
370
186
 
371
-
372
-
373
187
  参考ページ
374
-
375
188
  [ReactiveProperty の Validation は DataAnnotation じゃないと思った? episode: 9 | :: halation ghost ::](https://elf-mission.net/programming/wpf/episode09#ReactivePropertySetValidateNotifyError_Validation)
376
-
377
-
378
189
 
379
190
  [WPFでTextBoxに入力エラーがないときだけ押せるボタンを実現したい - かずきのBlog@hatena](https://blog.okazuki.jp/entry/2016/07/16/144319)
380
191
 
381
-
382
-
383
192
  [MVVMにおけるView層での入力値エラーの有無をViewModelで知る方法 - かずきのBlog@hatena](https://blog.okazuki.jp/entry/20110118/1295338167)