【发布时间】:2020-06-27 22:03:34
【问题描述】:
我创建了自己的UserControl,称为PersonNameControl,旨在被重复使用。
该控件具有三个TextBox 字段,并在其类文件中具有三个依赖属性。
- 名字
- 插入
- 姓氏
每个依赖属性值都绑定到一个字段,因此依赖属性 Firstname 绑定到 Firstname TextBox,依此类推。
我有意识地没有明确设置 UserControl 的 DataContext。 控制应尽可能松散。它应该只通过其依赖属性获取它的值(对于字段)。它甚至不应该寻找像 DataContext 这样的东西。
<UserControl x:Class="WpfApplication1.PersonNameControl">
<StackPanel>
<Label>Firstname:</Label>
<TextBox Text="{Binding Firstname, Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
</TextBox>
<Label>Insertion:</Label>
<TextBox Text="{Binding Insertion, Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
</TextBox>
<Label>Lastname:</Label>
<TextBox Text="{Binding Lastname, Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
</TextBox>
</StackPanel>
</UserControl>
还有控制类:
public partial class PersonNameControl : UserControl
{
public PersonNameControl()
{
InitializeComponent();
}
public string Firstname
{
get { return (string)GetValue(FirstnameProperty); }
set { SetValue(FirstnameProperty, value); }
}
public static readonly DependencyProperty FirstnameProperty =
DependencyProperty.Register("Firstname", typeof(string), typeof(PersonNameControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Insertion
{
get { return (string)GetValue(InsertionProperty); }
set { SetValue(InsertionProperty, value); }
}
public static readonly DependencyProperty InsertionProperty =
DependencyProperty.Register("Insertion", typeof(string), typeof(PersonNameControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Lastname
{
get { return (string)GetValue(LastnameProperty); }
set { SetValue(LastnameProperty, value); }
}
public static readonly DependencyProperty LastnameProperty =
DependencyProperty.Register("Lastname", typeof(string), typeof(PersonNameControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
该控件应该在另一个视图中使用如下:
<!--
Here we are inside a view or some other control.
The bindings here provide the dependency properties of the UserControl with a value.
The DataContext of the view where my UserControl is used, is a ViewModel that implements INotifyDataErrorInfo
-->
<myControls:PersonNameControl
Firstname="{Binding SomeFirstnameFromVM, Mode=TwoWay}"
Insertion="{Binding SomeInsertionFromVM, Mode=TwoWay}"
Lastname="{Binding SomeLastnameFromVM, Mode=TwoWay}">
</myControls:PersonNameControl>
当 ViewModel(实现 INotifyDataErrorInfo)创建验证错误时,我的 PersonNameControl UserControl 没有任何反应。
我设法制作了一个独立的控件,因为它不依赖特定的 DataContext,不在其代码隐藏文件中设置自己的 DataContext,而只是通过依赖属性获取其值。这些值通过绑定交换并显示,但不显示验证错误。
我想要的是将验证错误传递给 UserControl。
互联网上的一些解决方案使用ValidationAdornerSite,我尝试了这个。但这仅适用于一个TextBox。
如果不使我的控制依赖于外部世界或引入丑陋的额外属性来解决它很麻烦,我看不到任何解决方案。我认为错误像一条信息一样通过所有绑定“隧道化”到值到达的最后一个级别。但这似乎不是正确的考虑。
编辑:
我添加了我的 ViewModel 类。
public class CustomerFormViewModel : ViewModelBase, INotifyDataErrorInfo
{
protected string _clientNumber;
protected DateTime _date;
protected string _firstname;
protected string _insertion;
protected string _lastname;
protected Address _address;
protected ObservableCollection<Email> _emails;
protected ObservableCollection<PhoneNumber> _phoneNumbers;
protected string _note;
protected bool _hasErrors;
protected IList<ValidationFailure> _validationErrors;
public IList<ValidationFailure> ValidationErrors
{
get { return _validationErrors; }
set { _validationErrors = value; OnPropertyChanged("ValidationErrors"); }
}
public string ClientNumber
{
get { return _clientNumber; }
set { _clientNumber = value; OnPropertyChanged("ClientNumber"); }
}
public DateTime Date
{
get { return _date; }
set { _date = value; OnPropertyChanged("Date"); }
}
public string Firstname
{
get { return _firstname; }
set { _firstname = value; OnPropertyChanged("Firstname"); }
}
public string Insertion
{
get { return _insertion; }
set { _insertion = value; OnPropertyChanged("Insertion"); }
}
public string Lastname
{
get { return _lastname; }
set { _lastname = value; OnPropertyChanged("Lastname"); }
}
public Address Address
{
get { return _address; }
set { _address = value; OnPropertyChanged("Address"); }
}
public ObservableCollection<Email> Emails
{
get { return _emails; }
set { _emails = value; OnPropertyChanged("Emails"); }
}
public ObservableCollection<PhoneNumber> PhoneNumbers
{
get { return _phoneNumbers; }
set { _phoneNumbers = value; OnPropertyChanged("PhoneNumbers"); }
}
public string Note
{
get { return _note; }
set { _note = value; OnPropertyChanged("Note"); }
}
private DelegateCommand _saveCustomerCommand;
public DelegateCommand SaveCustomerCommand
{
get { return _saveCustomerCommand; }
private set { _saveCustomerCommand = value; OnPropertyChanged("SaveCustomerCommand"); }
}
public CustomerFormViewModel()
{
ValidationErrors = new List<ValidationFailure>();
SaveCustomerCommand = new DelegateCommand(SaveCustomer, CanSaveCustomer);
}
protected void ValidateInput()
{
ValidationErrors.Clear();
CustomerFormValidator validator = new CustomerFormValidator();
FluentValidation.Results.ValidationResult result = validator.Validate(this);
ValidationErrors = result.Errors;
foreach (ValidationFailure f in ValidationErrors)
{
Console.WriteLine(f.ErrorMessage);
}
_hasErrors = result.Errors.Count != 0;
List<string> vmProperties = new List<string>() { "Firstname", "Lastname", "Address", "ClientNumber", "Date" };
foreach (string propertyName in vmProperties)
{
OnErrorsChanged(propertyName);
}
}
public bool HasErrors
{
get { return _hasErrors; }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
protected void OnErrorsChanged(string name)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(name));
}
public IEnumerable GetErrors(string propertyName)
{
return ValidationErrors.Where<ValidationFailure>(x => x.PropertyName == propertyName);
}
public void SaveCustomer(object parameter)
{
this.ValidateInput();
if( ! HasErrors)
{
Customer customer = new Customer(-1, ClientNumber, Date, Firstname, Insertion, Lastname, Address);
ICustomerRepository repo = new CustomerRepository();
bool res = repo.SaveCustomer(customer);
if(res) {
// ...
}
// ...
} else
{
MessageBox.Show("One or more fields are not filled in correctly.", "Invalid input", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public bool CanSaveCustomer(object parameter)
{
return true;
}
}
【问题讨论】:
-
我也一直在处理这个问题。没有太多选项,您可能正在考虑将 INotifyDataErrorInfo 与 UserControl 一起使用。您可以将您的视图模型注册为 INotifyDataErrorInfo 以从 VM 获取逻辑。
-
@Ugur 你按照你说的方式解决了吗?如果是这样,你能告诉我更多关于它的信息吗?如果这是最好的选择,甚至可以给出答案。
-
你能发布你的视图模型与
INotifyDataErrorInfo实现吗? -
我在问题中添加了我的 ViewModel 类。
-
ValidateInput在哪里被调用?
标签: c# wpf validation data-binding wpf-controls