您可以使用INotifyDataErrorInfo
注意INotifyDataErrorInfo
作品与自定义规则添加到绑定。自定义规则和RelayCommand的代码不包含在此答案中。
样品实施:
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
public class PropertyErrors : INotifyDataErrorInfo
{
private static readonly IReadOnlyList<object> EmptyErrors = new object[0];
private readonly Action<DataErrorsChangedEventArgs> ownerOnErrorsChanged;
private readonly Type type;
private readonly ConcurrentDictionary<string, List<object>> propertyErrors = new ConcurrentDictionary<string, List<object>>();
public PropertyErrors(INotifyDataErrorInfo owner, Action<DataErrorsChangedEventArgs> ownerOnErrorsChanged)
{
this.ownerOnErrorsChanged = ownerOnErrorsChanged;
this.type = owner.GetType();
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => this.propertyErrors.Count > 0;
public IEnumerable GetErrors(string propertyName)
{
Debug.Assert(this.type.GetProperty(propertyName) != null, $"The type {this.type.Name} does not have a property named {propertyName}");
List<object> errors;
return this.propertyErrors.TryGetValue(propertyName, out errors)
? errors
: EmptyErrors;
}
public void Add(string propertyName, object error)
{
Debug.Assert(this.type.GetProperty(propertyName) != null, $"The type {this.type.Name} does not have a property named {propertyName}");
this.propertyErrors.AddOrUpdate(
propertyName,
_ => new List<object> { error },
(_, errors) => UpdateErrors(error, errors));
this.OnErrorsChanged(new DataErrorsChangedEventArgs(propertyName));
}
public void Remove(string propertyName, Predicate<object> filter)
{
Debug.Assert(this.type.GetProperty(propertyName) != null, $"The type {this.type.Name} does not have a property named {propertyName}");
List<object> errors;
if (this.propertyErrors.TryGetValue(propertyName, out errors))
{
errors.RemoveAll(filter);
if (errors.Count == 0)
{
this.Clear(propertyName);
}
}
}
public void Clear(string propertyName)
{
Debug.Assert(this.type.GetProperty(propertyName) != null, $"The type {this.type.Name} does not have a property named {propertyName}");
List<object> temp;
if (this.propertyErrors.TryRemove(propertyName, out temp))
{
this.OnErrorsChanged(new DataErrorsChangedEventArgs(propertyName));
}
}
protected virtual void OnErrorsChanged(DataErrorsChangedEventArgs e)
{
this.ErrorsChanged?.Invoke(this, e);
this.ownerOnErrorsChanged(e);
}
private static List<object> UpdateErrors(object error, List<object> errors)
{
if (!errors.Contains(error))
{
errors.Add(error);
}
return errors;
}
}
using System;
using System.Collections;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
private readonly PropertyErrors errors;
private string searchText;
public ViewModel()
{
this.SearchCommand = new RelayCommand(this.Search);
this.errors = new PropertyErrors(this, this.OnErrorsChanged);
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public event PropertyChangedEventHandler PropertyChanged;
public string SearchText
{
get { return this.searchText; }
set
{
if (value == this.searchText)
{
return;
}
this.searchText = value;
this.errors.Clear(nameof(this.SearchText));
this.OnPropertyChanged();
}
}
public bool HasErrors => this.errors.HasErrors;
public ICommand SearchCommand { get; }
public IEnumerable GetErrors(string propertyName) => this.errors.GetErrors(propertyName);
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Search()
{
if (string.IsNullOrEmpty(this.searchText))
{
this.errors.Add(nameof(this.SearchText), "Search text cannot be empty");
return;
}
MessageBox.Show("searching");
}
protected virtual void OnErrorsChanged(DataErrorsChangedEventArgs e)
{
this.ErrorsChanged?.Invoke(this, e);
}
}
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="Search text" />
<TextBox x:Name="SearchTextBox"
Grid.Row="0"
Grid.Column="1">
<TextBox.Text>
<Binding Path="SearchText"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MustStartWithValidationRule StartsWith="a" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<ItemsControl Grid.Row="0"
Grid.Column="2"
Margin="6,0,0,0"
ItemsSource="{Binding Path=(Validation.Errors),
ElementName=SearchTextBox}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type ValidationError}">
<TextBlock Foreground="Red"
Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="1"
Grid.Column="1"
Command="{Binding SearchCommand}"
Content="Search" />
</Grid>
我没有看到你所张贴的全部代码,但我想你应该简单地使用'禁用搜索按钮ICommand.CanExecute()'函数(当你的验证条件失败时返回false) - 你也应该为你使用的属性订阅viewmodel的更改并相应地提升'ICommand.CanExecuteChanged' – Maverik
谢谢,@Maverik。去查看这些语法,看看如何正确地做到这一点:) – Alex
@Maverik我认为OP要求相反。如果验证失败,他不想禁用该按钮。他希望验证错误消息仅在单击该按钮时才显示。 –