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

回答編集履歴

1

見直しキャンペーン中

2023/07/23 05:10

投稿

TN8001
TN8001

スコア10108

answer CHANGED
@@ -1,192 +1,192 @@
1
- 一応できましたが、地味に難しいですね^^;
2
- 普段`ReactiveProperty`使ってないので間違いや、もっといい方法があるかもしれません。
3
- 特にViewでのエラーをどうするか(`HasViewError`)は、考慮がいるかもしれませんね(`ReactiveProperty<string> Number1`としてしまってVM側でintにするとか)
4
-
5
- `NuGet`で`Microsoft.Xaml.Behaviors.Wpf`を入れてください。
6
-
7
- ```xaml
8
- <Window
9
- x:Class="Questions289115.MainWindow"
10
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
11
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
12
- xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
13
- xmlns:local="clr-namespace:Questions289115"
14
- Width="800"
15
- Height="450">
16
- <Window.DataContext>
17
- <local:ViewModel />
18
- </Window.DataContext>
19
-
20
- <i:Interaction.Behaviors>
21
- <local:ValidationErrorBehavior HasViewError="{Binding HasViewError.Value, Mode=OneWayToSource}" />
22
- </i:Interaction.Behaviors>
23
-
24
- <Window.Resources>
25
- <ControlTemplate x:Key="ValidationTemplate">
26
- <StackPanel>
27
- <ItemsControl HorizontalAlignment="Right" ItemsSource="{Binding AdornedElement.(Validation.Errors), ElementName=validationTarget}">
28
- <ItemsControl.ItemsPanel>
29
- <ItemsPanelTemplate>
30
- <StackPanel Orientation="Horizontal" />
31
- </ItemsPanelTemplate>
32
- </ItemsControl.ItemsPanel>
33
- <ItemsControl.ItemTemplate>
34
- <DataTemplate>
35
- <TextBlock Foreground="Red" Text="{Binding ErrorContent}" />
36
- </DataTemplate>
37
- </ItemsControl.ItemTemplate>
38
- </ItemsControl>
39
- <AdornedElementPlaceholder x:Name="validationTarget" />
40
- </StackPanel>
41
- </ControlTemplate>
42
-
43
- <Style TargetType="TextBox">
44
- <Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplate}" />
45
- </Style>
46
- </Window.Resources>
47
-
48
- <StackPanel>
49
- <HeaderedContentControl Header="Text1">
50
- <TextBox Text="{Binding Text1.Value, UpdateSourceTrigger=PropertyChanged}" />
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}" />
60
- </HeaderedContentControl>
61
-
62
- <Button Command="{Binding Command}" Content="Command" />
63
- </StackPanel>
64
- </Window>
65
- ```
66
-
67
- ```C#
68
- using System;
69
- using System.ComponentModel;
70
- using System.Diagnostics;
71
- using System.Linq;
72
- using System.Reactive.Linq;
73
- using System.Windows;
74
- using System.Windows.Controls;
75
- using Microsoft.Xaml.Behaviors;
76
- using Reactive.Bindings;
77
- using Reactive.Bindings.Extensions;
78
-
79
- namespace Questions289115
80
- {
81
- public class ViewModel : INotifyPropertyChanged
82
- {
83
- public ReactiveProperty<string> Text1 { get; }
84
- public ReactiveProperty<string> Text2 { get; }
85
- public ReactiveProperty<int> Number1 { get; }
86
- public ReactiveProperty<int> Number2 { get; }
87
- public ReactiveProperty<bool> HasViewError { get; } = new ReactiveProperty<bool>();
88
-
89
- public ReactiveCommand Command { get; }
90
-
91
- public ViewModel()
92
- {
93
- Text1 = new ReactiveProperty<string>()
94
- .SetValidateNotifyError(v =>
95
- {
96
- // 正しく複数エラー対応するならこんなん?(もっとスッキリ書けない?^^;
97
- var errors = new string[] {
98
- string.IsNullOrEmpty(v) ? "何か入力してください。" : null,
99
- v == Text2?.Value ? $"{nameof(Text2)}と同一です。" : null,
100
- }.Where(x => x != null);
101
-
102
- if(0 < errors.Count()) return errors;
103
- return null;
104
- });
105
- Text1.Subscribe(_ => Text2?.ForceValidate()); // もっといいのがありそう??
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);
120
-
121
- if(0 < errors.Count()) return errors;
122
- return null;
123
- });
124
- Number1.Subscribe(_ => Number2?.ForceValidate());
125
-
126
- Number2 = new ReactiveProperty<int>()
127
- .SetValidateNotifyError(v =>
128
- {
129
- var errors = new string[] {
130
- v < 0 ? "0以上を入力してください。" : null,
131
- v == Number1?.Value ? $"{nameof(Number1)}と同一です。" : null,
132
- }.Where(x => x != null);
133
-
134
- if(0 < errors.Count()) return errors;
135
- return null;
136
- });
137
- Number2.Subscribe(_ => Number1?.ForceValidate());
138
-
139
- Command = new[]
140
- {
141
- Text1.ObserveHasErrors,
142
- Text2.ObserveHasErrors,
143
- Number1.ObserveHasErrors,
144
- Number2.ObserveHasErrors,
145
- HasViewError,
146
- }
147
- .CombineLatestValuesAreAllFalse()
148
- .ToReactiveCommand();
149
- Command.Subscribe(() => Debug.WriteLine("Execute"));
150
- }
151
-
152
- public event PropertyChangedEventHandler PropertyChanged;
153
- }
154
-
155
- // [MVVMにおけるView層での入力値エラーの有無をViewModelで知る方法 - かずきのBlog@hatena](https://blog.okazuki.jp/entry/20110118/1295338167)
156
- public class ValidationErrorBehavior : Behavior<DependencyObject>
157
- {
158
- public bool HasViewError
159
- {
160
- get => (bool)GetValue(HasViewErrorProperty);
161
- set => SetValue(HasViewErrorProperty, value);
162
- }
163
- public static readonly DependencyProperty HasViewErrorProperty
164
- = DependencyProperty.Register(nameof(HasViewError), typeof(bool),
165
- typeof(ValidationErrorBehavior), new UIPropertyMetadata(false));
166
- private int errroCount;
167
- protected override void OnAttached()
168
- {
169
- base.OnAttached();
170
- Validation.AddErrorHandler(AssociatedObject, ErrorHandler);
171
- }
172
- protected override void OnDetaching()
173
- {
174
- Validation.RemoveErrorHandler(AssociatedObject, ErrorHandler);
175
- base.OnDetaching();
176
- }
177
- private void ErrorHandler(object sender, ValidationErrorEventArgs e)
178
- {
179
- if(e.Action == ValidationErrorEventAction.Added) errroCount++;
180
- else if(e.Action == ValidationErrorEventAction.Removed) errroCount--;
181
- HasViewError = errroCount != 0;
182
- }
183
- }
184
- }
185
- ```
186
-
187
- 参考ページ
188
- [ReactiveProperty の Validation は DataAnnotation じゃないと思った? episode: 9 | :: halation ghost ::](https://elf-mission.net/programming/wpf/episode09#ReactivePropertySetValidateNotifyError_Validation)
189
-
190
- [WPFでTextBoxに入力エラーがないときだけ押せるボタンを実現したい - かずきのBlog@hatena](https://blog.okazuki.jp/entry/2016/07/16/144319)
191
-
1
+ 一応できましたが、地味に難しいですね^^;
2
+ 普段`ReactiveProperty`使ってないので間違いや、もっといい方法があるかもしれません。
3
+ 特にViewでのエラーをどうするか(`HasViewError`)は、考慮がいるかもしれませんね(`ReactiveProperty<string> Number1`としてしまってVM側でintにするとか)
4
+
5
+ `NuGet`で`Microsoft.Xaml.Behaviors.Wpf`を入れてください。
6
+
7
+ ```xml
8
+ <Window
9
+ x:Class="Questions289115.MainWindow"
10
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
11
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
12
+ xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
13
+ xmlns:local="clr-namespace:Questions289115"
14
+ Width="800"
15
+ Height="450">
16
+ <Window.DataContext>
17
+ <local:ViewModel />
18
+ </Window.DataContext>
19
+
20
+ <i:Interaction.Behaviors>
21
+ <local:ValidationErrorBehavior HasViewError="{Binding HasViewError.Value, Mode=OneWayToSource}" />
22
+ </i:Interaction.Behaviors>
23
+
24
+ <Window.Resources>
25
+ <ControlTemplate x:Key="ValidationTemplate">
26
+ <StackPanel>
27
+ <ItemsControl HorizontalAlignment="Right" ItemsSource="{Binding AdornedElement.(Validation.Errors), ElementName=validationTarget}">
28
+ <ItemsControl.ItemsPanel>
29
+ <ItemsPanelTemplate>
30
+ <StackPanel Orientation="Horizontal" />
31
+ </ItemsPanelTemplate>
32
+ </ItemsControl.ItemsPanel>
33
+ <ItemsControl.ItemTemplate>
34
+ <DataTemplate>
35
+ <TextBlock Foreground="Red" Text="{Binding ErrorContent}" />
36
+ </DataTemplate>
37
+ </ItemsControl.ItemTemplate>
38
+ </ItemsControl>
39
+ <AdornedElementPlaceholder x:Name="validationTarget" />
40
+ </StackPanel>
41
+ </ControlTemplate>
42
+
43
+ <Style TargetType="TextBox">
44
+ <Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplate}" />
45
+ </Style>
46
+ </Window.Resources>
47
+
48
+ <StackPanel>
49
+ <HeaderedContentControl Header="Text1">
50
+ <TextBox Text="{Binding Text1.Value, UpdateSourceTrigger=PropertyChanged}" />
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}" />
60
+ </HeaderedContentControl>
61
+
62
+ <Button Command="{Binding Command}" Content="Command" />
63
+ </StackPanel>
64
+ </Window>
65
+ ```
66
+
67
+ ```cs
68
+ using System;
69
+ using System.ComponentModel;
70
+ using System.Diagnostics;
71
+ using System.Linq;
72
+ using System.Reactive.Linq;
73
+ using System.Windows;
74
+ using System.Windows.Controls;
75
+ using Microsoft.Xaml.Behaviors;
76
+ using Reactive.Bindings;
77
+ using Reactive.Bindings.Extensions;
78
+
79
+ namespace Questions289115
80
+ {
81
+ public class ViewModel : INotifyPropertyChanged
82
+ {
83
+ public ReactiveProperty<string> Text1 { get; }
84
+ public ReactiveProperty<string> Text2 { get; }
85
+ public ReactiveProperty<int> Number1 { get; }
86
+ public ReactiveProperty<int> Number2 { get; }
87
+ public ReactiveProperty<bool> HasViewError { get; } = new ReactiveProperty<bool>();
88
+
89
+ public ReactiveCommand Command { get; }
90
+
91
+ public ViewModel()
92
+ {
93
+ Text1 = new ReactiveProperty<string>()
94
+ .SetValidateNotifyError(v =>
95
+ {
96
+ // 正しく複数エラー対応するならこんなん?(もっとスッキリ書けない?^^;
97
+ var errors = new string[] {
98
+ string.IsNullOrEmpty(v) ? "何か入力してください。" : null,
99
+ v == Text2?.Value ? $"{nameof(Text2)}と同一です。" : null,
100
+ }.Where(x => x != null);
101
+
102
+ if(0 < errors.Count()) return errors;
103
+ return null;
104
+ });
105
+ Text1.Subscribe(_ => Text2?.ForceValidate()); // もっといいのがありそう??
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);
120
+
121
+ if(0 < errors.Count()) return errors;
122
+ return null;
123
+ });
124
+ Number1.Subscribe(_ => Number2?.ForceValidate());
125
+
126
+ Number2 = new ReactiveProperty<int>()
127
+ .SetValidateNotifyError(v =>
128
+ {
129
+ var errors = new string[] {
130
+ v < 0 ? "0以上を入力してください。" : null,
131
+ v == Number1?.Value ? $"{nameof(Number1)}と同一です。" : null,
132
+ }.Where(x => x != null);
133
+
134
+ if(0 < errors.Count()) return errors;
135
+ return null;
136
+ });
137
+ Number2.Subscribe(_ => Number1?.ForceValidate());
138
+
139
+ Command = new[]
140
+ {
141
+ Text1.ObserveHasErrors,
142
+ Text2.ObserveHasErrors,
143
+ Number1.ObserveHasErrors,
144
+ Number2.ObserveHasErrors,
145
+ HasViewError,
146
+ }
147
+ .CombineLatestValuesAreAllFalse()
148
+ .ToReactiveCommand();
149
+ Command.Subscribe(() => Debug.WriteLine("Execute"));
150
+ }
151
+
152
+ public event PropertyChangedEventHandler PropertyChanged;
153
+ }
154
+
155
+ // [MVVMにおけるView層での入力値エラーの有無をViewModelで知る方法 - かずきのBlog@hatena](https://blog.okazuki.jp/entry/20110118/1295338167)
156
+ public class ValidationErrorBehavior : Behavior<DependencyObject>
157
+ {
158
+ public bool HasViewError
159
+ {
160
+ get => (bool)GetValue(HasViewErrorProperty);
161
+ set => SetValue(HasViewErrorProperty, value);
162
+ }
163
+ public static readonly DependencyProperty HasViewErrorProperty
164
+ = DependencyProperty.Register(nameof(HasViewError), typeof(bool),
165
+ typeof(ValidationErrorBehavior), new UIPropertyMetadata(false));
166
+ private int errroCount;
167
+ protected override void OnAttached()
168
+ {
169
+ base.OnAttached();
170
+ Validation.AddErrorHandler(AssociatedObject, ErrorHandler);
171
+ }
172
+ protected override void OnDetaching()
173
+ {
174
+ Validation.RemoveErrorHandler(AssociatedObject, ErrorHandler);
175
+ base.OnDetaching();
176
+ }
177
+ private void ErrorHandler(object sender, ValidationErrorEventArgs e)
178
+ {
179
+ if(e.Action == ValidationErrorEventAction.Added) errroCount++;
180
+ else if(e.Action == ValidationErrorEventAction.Removed) errroCount--;
181
+ HasViewError = errroCount != 0;
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ 参考ページ
188
+ [ReactiveProperty の Validation は DataAnnotation じゃないと思った? episode: 9 | :: halation ghost ::](https://elf-mission.net/programming/wpf/episode09#ReactivePropertySetValidateNotifyError_Validation)
189
+
190
+ [WPFでTextBoxに入力エラーがないときだけ押せるボタンを実現したい - かずきのBlog@hatena](https://blog.okazuki.jp/entry/2016/07/16/144319)
191
+
192
192
  [MVVMにおけるView層での入力値エラーの有無をViewModelで知る方法 - かずきのBlog@hatena](https://blog.okazuki.jp/entry/20110118/1295338167)