回答編集履歴
1
コードを追加
test
CHANGED
@@ -1 +1,303 @@
|
|
1
1
|
色々考えるより数行で対応できたのでコードビハインドで実装しました
|
2
|
+
|
3
|
+
まず、自作のTextBlockErrorMessageを作成しました
|
4
|
+
``` C#
|
5
|
+
コード
|
6
|
+
using System;
|
7
|
+
using System.Collections.Generic;
|
8
|
+
using System.ComponentModel;
|
9
|
+
using System.Diagnostics;
|
10
|
+
using System.Linq;
|
11
|
+
using System.Text;
|
12
|
+
using System.Threading.Tasks;
|
13
|
+
using System.Windows;
|
14
|
+
using System.Windows.Controls;
|
15
|
+
using System.Windows.Data;
|
16
|
+
using System.Windows.Documents;
|
17
|
+
using System.Windows.Input;
|
18
|
+
using System.Windows.Media;
|
19
|
+
using System.Windows.Media.Imaging;
|
20
|
+
using System.Windows.Navigation;
|
21
|
+
using System.Windows.Shapes;
|
22
|
+
|
23
|
+
namespace SeikouCustomControl
|
24
|
+
{
|
25
|
+
/// <summary>
|
26
|
+
/// このカスタム コントロールを XAML ファイルで使用するには、手順 1a または 1b の後、手順 2 に従います。
|
27
|
+
///
|
28
|
+
/// 手順 1a) 現在のプロジェクトに存在する XAML ファイルでこのカスタム コントロールを使用する場合
|
29
|
+
/// この XmlNamespace 属性を使用場所であるマークアップ ファイルのルート要素に
|
30
|
+
/// 追加します:
|
31
|
+
///
|
32
|
+
/// xmlns:MyNamespace="clr-namespace:SeikouCustomControl"
|
33
|
+
///
|
34
|
+
///
|
35
|
+
/// 手順 1b) 異なるプロジェクトに存在する XAML ファイルでこのカスタム コントロールを使用する場合
|
36
|
+
/// この XmlNamespace 属性を使用場所であるマークアップ ファイルのルート要素に
|
37
|
+
/// 追加します:
|
38
|
+
///
|
39
|
+
/// xmlns:MyNamespace="clr-namespace:SeikouCustomControl;assembly=SeikouCustomControl"
|
40
|
+
///
|
41
|
+
/// また、XAML ファイルのあるプロジェクトからこのプロジェクトへのプロジェクト参照を追加し、
|
42
|
+
/// リビルドして、コンパイル エラーを防ぐ必要があります:
|
43
|
+
///
|
44
|
+
/// ソリューション エクスプローラーで対象のプロジェクトを右クリックし、
|
45
|
+
/// [参照の追加] の [プロジェクト] を選択してから、このプロジェクトを参照し、選択します。
|
46
|
+
///
|
47
|
+
///
|
48
|
+
/// 手順 2)
|
49
|
+
/// コントロールを XAML ファイルで使用します。
|
50
|
+
///
|
51
|
+
/// <MyNamespace:TextBlockErrorMessage/>
|
52
|
+
///
|
53
|
+
/// </summary>
|
54
|
+
public class TextBlockErrorMessage : TextBlock
|
55
|
+
{
|
56
|
+
static TextBlockErrorMessage()
|
57
|
+
{
|
58
|
+
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBlockErrorMessage), new FrameworkPropertyMetadata(typeof(TextBlockErrorMessage)));
|
59
|
+
|
60
|
+
}
|
61
|
+
|
62
|
+
public TextBlockErrorMessage()
|
63
|
+
{
|
64
|
+
var descripter = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
|
65
|
+
this.Visibility = Visibility.Collapsed;
|
66
|
+
descripter.AddValueChanged(this, OnTextChanged);
|
67
|
+
}
|
68
|
+
|
69
|
+
private static void OnTextChanged(object? sender, EventArgs e)
|
70
|
+
{
|
71
|
+
var textBlock = (TextBlock)sender;
|
72
|
+
if (string.IsNullOrEmpty(textBlock.Text))
|
73
|
+
{
|
74
|
+
textBlock.Visibility = Visibility.Collapsed;
|
75
|
+
}
|
76
|
+
else
|
77
|
+
{
|
78
|
+
textBlock.Visibility = Visibility.Visible;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
```
|
86
|
+
自作のTextBlockErrorMessageを配置します
|
87
|
+
|
88
|
+
```WPF XAML
|
89
|
+
<Window
|
90
|
+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
91
|
+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
92
|
+
xmlns:local="clr-namespace:Test" xmlns:sys="http://schemas.microsoft.com/winfx/2009/xaml"
|
93
|
+
xmlns:scc="clr-namespace:SeikouCustomControl;assembly=SeikouCustomControl"
|
94
|
+
x:Class="Test.MainWindow"
|
95
|
+
Title="MainWindow"
|
96
|
+
Width="525"
|
97
|
+
Height="350">
|
98
|
+
<Window.Resources>
|
99
|
+
</Window.Resources>
|
100
|
+
<Window.DataContext>
|
101
|
+
<local:MainWindowViewModel />
|
102
|
+
</Window.DataContext>
|
103
|
+
<Grid>
|
104
|
+
<Grid.ColumnDefinitions>
|
105
|
+
<ColumnDefinition Width="*"/>
|
106
|
+
<ColumnDefinition Width="Auto"/>
|
107
|
+
<ColumnDefinition Width="*"/>
|
108
|
+
<ColumnDefinition Width="*"/>
|
109
|
+
</Grid.ColumnDefinitions>
|
110
|
+
<Grid.RowDefinitions>
|
111
|
+
<RowDefinition Height="auto"/>
|
112
|
+
<RowDefinition Height="auto"/>
|
113
|
+
<RowDefinition Height="auto"/>
|
114
|
+
</Grid.RowDefinitions>
|
115
|
+
|
116
|
+
<Label Grid.Row="0" Grid.Column="1" Content="年齢" />
|
117
|
+
<TextBox Grid.Row="0" Grid.Column="2"
|
118
|
+
Text="{Binding InputString, UpdateSourceTrigger=PropertyChanged}"/>
|
119
|
+
|
120
|
+
<scc:TextBlockErrorMessage Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="100"
|
121
|
+
Text="{Binding InputStringErrorMessage}"
|
122
|
+
/>
|
123
|
+
</Grid>
|
124
|
+
</Window>
|
125
|
+
```
|
126
|
+
|
127
|
+
ViewModelにプロパティを作ります
|
128
|
+
|
129
|
+
```C#
|
130
|
+
using System.ComponentModel.DataAnnotations;
|
131
|
+
using System.Runtime.CompilerServices;
|
132
|
+
using Test;
|
133
|
+
|
134
|
+
namespace Test
|
135
|
+
{
|
136
|
+
public class MainWindowViewModel : ValidateableBase
|
137
|
+
{
|
138
|
+
private string inputString = string.Empty;
|
139
|
+
[Required(ErrorMessage = "年齢 : 何か入力してください")]
|
140
|
+
[StringLength(10, ErrorMessage = "年齢 : \r\n10文字以内で\r\n入力してください")]
|
141
|
+
public string InputString
|
142
|
+
{
|
143
|
+
get { return inputString; }
|
144
|
+
set
|
145
|
+
{
|
146
|
+
SetPropertyWithErrorMessage(ref this.inputString, value);
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
private string _InputStringErrorMessage = string.Empty;
|
151
|
+
public string InputStringErrorMessage
|
152
|
+
{
|
153
|
+
get { return _InputStringErrorMessage; }
|
154
|
+
private set { this.SetProperty(ref _InputStringErrorMessage, value); }
|
155
|
+
}
|
156
|
+
|
157
|
+
public MainWindowViewModel()
|
158
|
+
{
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
```
|
164
|
+
|
165
|
+
次に汎用性を持たせてSetPropertyWithErrorMessageメソッドを作りました
|
166
|
+
これがベストなのかはわかりません。
|
167
|
+
|
168
|
+
```C#
|
169
|
+
using System.Collections;
|
170
|
+
using System.ComponentModel;
|
171
|
+
using System.ComponentModel.DataAnnotations;
|
172
|
+
using System.Reflection;
|
173
|
+
using System.Runtime.CompilerServices;
|
174
|
+
|
175
|
+
namespace Test
|
176
|
+
{
|
177
|
+
public abstract class ValidateableBase : BindableBase, INotifyDataErrorInfo
|
178
|
+
{
|
179
|
+
/// <summary>
|
180
|
+
/// プロパティが既に目的の値と一致しているかどうかを確認します。必要な場合のみ、
|
181
|
+
/// プロパティを設定し、リスナーに通知します。
|
182
|
+
/// その後、プロパティの入力値検証を行います。
|
183
|
+
/// </summary>
|
184
|
+
/// <typeparam name="T">プロパティの型。</typeparam>
|
185
|
+
/// <param name="storage">get アクセス操作子と set アクセス操作子両方を使用したプロパティへの参照。</param>
|
186
|
+
/// <param name="value">プロパティに必要な値。</param>
|
187
|
+
/// <param name="propertyName">リスナーに通知するために使用するプロパティの名前。
|
188
|
+
/// この値は省略可能で、
|
189
|
+
/// CallerMemberName をサポートするコンパイラから呼び出す場合に自動的に指定できます。</param>
|
190
|
+
/// <returns>値が変更された場合は true、既存の値が目的の値に一致した場合は
|
191
|
+
/// false です。</returns>
|
192
|
+
protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
|
193
|
+
{
|
194
|
+
var isChanged = base.SetProperty(ref storage, value, propertyName);
|
195
|
+
if (isChanged)
|
196
|
+
this.ValidateProperty(value, propertyName);
|
197
|
+
|
198
|
+
return isChanged;
|
199
|
+
}
|
200
|
+
|
201
|
+
protected Dictionary<string, PropertyInfo?> _properties = new Dictionary<string, PropertyInfo?>();
|
202
|
+
|
203
|
+
protected void SetPropertyWithErrorMessage<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
|
204
|
+
{
|
205
|
+
this.SetProperty(ref storage, value, propertyName);
|
206
|
+
|
207
|
+
var errorMessage = GetErrorMessage("\r\n", propertyName);
|
208
|
+
|
209
|
+
// プロパティ情報の取得
|
210
|
+
if (!_properties.TryGetValue(propertyName, out var property))
|
211
|
+
{
|
212
|
+
property = this.GetType().GetProperty(propertyName + "ErrorMessage");
|
213
|
+
_properties[propertyName] = property;
|
214
|
+
}
|
215
|
+
|
216
|
+
// インスタンスに値を設定
|
217
|
+
property?.SetValue(this, errorMessage);
|
218
|
+
}
|
219
|
+
private string GetErrorMessage(string? messageSeparator, String propertyName)
|
220
|
+
{
|
221
|
+
var errors = (List<string>)GetErrors(propertyName);
|
222
|
+
if (errors != null)
|
223
|
+
{
|
224
|
+
var ss = new string[errors.Count];
|
225
|
+
errors.CopyTo(ss);
|
226
|
+
return string.Join(messageSeparator, ss);
|
227
|
+
}
|
228
|
+
|
229
|
+
return string.Empty;
|
230
|
+
}
|
231
|
+
|
232
|
+
protected void ValidateProperty(object value, [CallerMemberName] string propertyName = null)
|
233
|
+
{
|
234
|
+
var context = new ValidationContext(this) { MemberName = propertyName };
|
235
|
+
var validationErrors = new List<ValidationResult>();
|
236
|
+
if (!Validator.TryValidateProperty(value, context, validationErrors))
|
237
|
+
{
|
238
|
+
var errors = validationErrors.Select(error => error.ErrorMessage);
|
239
|
+
foreach (var error in errors)
|
240
|
+
{
|
241
|
+
AddError(propertyName, error);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
else
|
245
|
+
{
|
246
|
+
RemoveError(propertyName);
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
#region 発生中のエラーを保持する処理を実装
|
251
|
+
readonly Dictionary<string, List<string>> _currentErrors = new Dictionary<string, List<string>>();
|
252
|
+
|
253
|
+
protected void AddError(string propertyName, string error)
|
254
|
+
{
|
255
|
+
if (!_currentErrors.ContainsKey(propertyName))
|
256
|
+
_currentErrors[propertyName] = new List<string>();
|
257
|
+
|
258
|
+
if (!_currentErrors[propertyName].Contains(error))
|
259
|
+
{
|
260
|
+
_currentErrors[propertyName].Add(error);
|
261
|
+
OnErrorsChanged(propertyName);
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
protected void RemoveError(string propertyName)
|
266
|
+
{
|
267
|
+
if (_currentErrors.ContainsKey(propertyName))
|
268
|
+
_currentErrors.Remove(propertyName);
|
269
|
+
|
270
|
+
OnErrorsChanged(propertyName);
|
271
|
+
}
|
272
|
+
#endregion
|
273
|
+
|
274
|
+
private void OnErrorsChanged(string propertyName)
|
275
|
+
{
|
276
|
+
var h = this.ErrorsChanged;
|
277
|
+
if (h != null)
|
278
|
+
{
|
279
|
+
h(this, new DataErrorsChangedEventArgs(propertyName));
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
#region INotifyDataErrorInfoの実装
|
284
|
+
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
|
285
|
+
|
286
|
+
public IEnumerable GetErrors(string propertyName)
|
287
|
+
{
|
288
|
+
if (string.IsNullOrEmpty(propertyName) ||
|
289
|
+
!_currentErrors.ContainsKey(propertyName))
|
290
|
+
return null;
|
291
|
+
|
292
|
+
return _currentErrors[propertyName];
|
293
|
+
}
|
294
|
+
|
295
|
+
public bool HasErrors
|
296
|
+
{
|
297
|
+
get { return _currentErrors.Count > 0; }
|
298
|
+
}
|
299
|
+
#endregion
|
300
|
+
}
|
301
|
+
}
|
302
|
+
|
303
|
+
```
|