[英]How to pass validation error to child control in reusable UserControl
我創建了自己的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;
}
}
所以,我准備了一個演示用戶控件。 它是一個子用戶控件,從其 MainViewModel 獲取所有驗證信息
主窗口
<Window
x:Class="ValidationSubUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ValidationSubUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Name="MyWindow"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<local:SubUserControl
FirstName="{Binding FirstName, Mode=TwoWay}"
LastName="{Binding LastName, Mode=TwoWay}"
ValidationSource="{Binding ElementName=MyWindow, Path=DataContext}" />
</Grid>
</Window>
主視圖模型
using GalaSoft.MvvmLight;
using System.ComponentModel;
namespace ValidationSubUI
{
public class MainViewModel : ViewModelBase, IDataErrorInfo
{
public string Error
{
get
{
return string.Empty;
}
}
private string m_FirstName;
public string FirstName
{
get { return m_FirstName; }
set
{
m_FirstName = value;
RaisePropertyChanged();
}
}
private string m_LastName;
public string LastName
{
get { return m_LastName; }
set
{
m_LastName = value;
RaisePropertyChanged();
}
}
public string this[string columnName]
{
get
{
if (columnName == nameof(FirstName))
{
return GetFirstNameError();
}
else if (columnName == nameof(LastName))
{
return GetLastNameError();
}
return null;
}
}
private string GetFirstNameError()
{
string result = string.Empty;
if (string.IsNullOrEmpty(FirstName))
{
result = "First name required";
}
return result;
}
private string GetLastNameError()
{
string result = string.Empty;
if (string.IsNullOrEmpty(LastName))
{
result = "Last name required";
}
return result;
}
}
}
SubUserControl 從 MainViewModel 獲取所有驗證邏輯
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace ValidationSubUI
{
/// <summary>
/// Interaction logic for SubUserControl.xaml
/// </summary>
public partial class SubUserControl : UserControl, IDataErrorInfo
{
public SubUserControl()
{
InitializeComponent();
}
public IDataErrorInfo ValidationSource
{
get { return (IDataErrorInfo)GetValue(ValidationSourceProperty); }
set { SetValue(ValidationSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ValidationSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValidationSourceProperty =
DependencyProperty.Register("ValidationSource", typeof(IDataErrorInfo), typeof(SubUserControl), new PropertyMetadata(null));
public string FirstName
{
get { return (string)GetValue(FirstNameProperty); }
set { SetValue(FirstNameProperty, value); }
}
// Using a DependencyProperty as the backing store for FirstName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register("FirstName", typeof(string), typeof(SubUserControl), new PropertyMetadata(string.Empty));
public string LastName
{
get { return (string)GetValue(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
// Using a DependencyProperty as the backing store for LastName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register("LastName", typeof(string), typeof(SubUserControl), new PropertyMetadata(string.Empty));
public string Error
{
get
{
return string.Empty;
}
}
public string this[string columnName]
{
get
{
if (ValidationSource != null)
{
return ValidationSource[columnName];
}
return null;
}
}
}
}
和子用戶控件
<UserControl
x:Class="ValidationSubUI.SubUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="CustomControl"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder x:Name="controlWithError" />
</Border>
<TextBlock
Margin="5,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
FontWeight="DemiBold"
Foreground="Red"
Text="{Binding ElementName=controlWithError, Path=AdornedElement.ToolTip, Mode=OneWay}" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid DataContext="{x:Reference Name=CustomControl}">
<StackPanel>
<TextBox
Width="120"
Height="30"
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}"
TextWrapping="Wrap" />
<TextBox
Width="120"
Height="30"
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</UserControl>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.