Провести валидацию в UserControl
У меня есть следующий пользовательский элемент управления (действительно элемент управления TextBox сейчас):
<TextBox:Class="IM.Common.UIControls.IMTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
>
<Validation.ErrorTemplate>
<ControlTemplate>
<!--Show this if there is a validation error-->
<StackPanel Orientation="Horizontal" ToolTip="{Binding [0].ErrorContent}" >
<Border BorderThickness="2" BorderBrush="Orange" >
<AdornedElementPlaceholder Margin="-1" />
</Border>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Код позади:
namespace IM.Common.UIControls
{
public partial class IMTextBox
{
public IMTextBox()
{
InitializeComponent();
}
}
}
У меня есть следующая модель:
public class User : IDataErrorInfo, INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
// used just to know if passwords match
public string Password2
{
get { return _password2; }
set
{
_password2 = value;
OnPropertyChanged("Password2");
}
}
private string _password2;
public string Error
{
get
{
throw new NotImplementedException();
}
}
public string this[string columnName]
{
get
{
if (columnName == "Password2")
{
if (string.IsNullOrEmpty(Password2))
return "required";
if (Regex.Match(Password2, "\\s").Success)
return "Password cannot contain spaces";
}
return null;
}
}
}
Когда я использую этот usercontrol как:
<myControls:IMTextBox Text="{Binding SomeUser.Password2, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
Это работает потрясающе! Ошибки проверки показывают, и это работает как ожидалось.
Теперь вот моя проблема:/
Я хочу добавить метку в этот пользовательский элемент управления, и проверки по-прежнему работают. В результате корнем моего usercontrol больше не может быть сам TextBox. В результате я изменил usercontrol, чтобы он выглядел так:
<UserControl:Class="IM.Common.UIControls.IMTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
>
<StackPanel>
<TextBlock Text="{Binding LabelTxt}" />
<TextBox Text="{Binding Txt, ValidatesOnDataErrors=true, NotifyOnValidationError=true}">
<Validation.ErrorTemplate>
<ControlTemplate>
<!--Show this if there is a validation error-->
<StackPanel Orientation="Horizontal" ToolTip="{Binding [0].ErrorContent}" >
<Border BorderThickness="2" BorderBrush="Orange" >
<AdornedElementPlaceholder Margin="-1" />
</Border>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
</StackPanel>
</UserControl>
Код позади теперь выглядит так:
namespace IM.Common.UIControls
{
public partial class IMTextBox : UserControl
{
public IMTextBox()
{
InitializeComponent();
this.DataContext = this;
}
public string Txt
{
get
{
return (string)GetValue(TxtProperty);
}
set
{
SetValue(TxtProperty, value);
}
}
public static DependencyProperty TxtProperty = DependencyProperty.Register(
name: "Txt",
propertyType: typeof(string),
ownerType: typeof(IMTextBox),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: string.Empty
)
);
}
}
Теперь, когда я пытаюсь использовать usercontrol, я могу сделать:
<myControls:IMTextBox Txt="{Binding SomeUser.Password2, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
Но ошибка проверки больше не генерируется:( . Другими словами, если я введу "foo foo", текстовое поле станет оранжевым в первом примере, но не в последнем примере, где корневым элементом управления является UserControl вместо TextBox.
Как я могу все еще сделать проверку работы?
редактировать
Благодаря ответу alek kowalczyk я погуглил его решение, потому что не понял его ответа и придумал это решение:
2 ответа
Ваша проблема в привязке UserControl.
<TextBox Text="{Binding Txt, Mode=TwoWay, NotifyOnValidationError=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:IMTextBox}}, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}">
и в объявлении свойства зависимостей.
public static DependencyProperty TxtProperty = DependencyProperty.Register("Txt", typeof(string), typeof(IMTextBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, null , false, UpdateSourceTrigger.PropertyChanged)
Когда вы связываете свойство Txt со свойством TextBox.Text - TextBox не знает контекст, в котором он должен найти свойство Txt. Вы должны сказать, что это свойство существует в родительском элементе типа IMTextBox. Кроме того, свойство Txt имеет привязку по умолчанию OneWay и будет обновлено в "Оставить фокус". Вам нужно переопределить его в метаданных.
В Binding Txt to Text - скажите, что эта привязка является TwoWay и будет обновляться при каждом изменении.
UPD: рабочий пример: xaml:
<UserControl x:Class="IM.Common.UIControls.IMTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:IM.Common.UIControls">
<StackPanel>
<TextBox Name="tb" Text="{Binding Txt, Mode=TwoWay, NotifyOnValidationError=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:IMTextBox}}, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Validation.ErrorTemplate="{x:Null}">
</TextBox>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Path=(Validation.Errors), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:IMTextBox}}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type ValidationError}">
<Border BorderThickness="2" BorderBrush="Green" >
<TextBlock Text="{Binding ErrorContent}"></TextBlock>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Background="Green"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ContentPresenter></ContentPresenter>
</StackPanel>
</StackPanel>
CS:
namespace IM.Common.UIControls
{
public partial class IMTextBox : UserControl
{
public IMTextBox()
{
InitializeComponent();
}
public string Txt
{
get
{
return (string)GetValue(TxtProperty);
}
set
{
SetValue(TxtProperty, value);
}
}
public static DependencyProperty TxtProperty = DependencyProperty.Register("Txt", typeof(string), typeof(IMTextBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, null, false, UpdateSourceTrigger.PropertyChanged));
}
}
DataContext вашего UserControl отличается от вашего Window, поэтому ошибка проверки не попадает в текстовое поле, я бы предложил создать пользовательский элемент управления, полученный из TextBox, вместо пользовательского элемента управления.
Здесь у вас есть шаблон элемента управления для текстового поля с меткой, вы можете сохранить шаблон элемента управления в словаре ресурсов, если вы хотите использовать его в нескольких текстовых полях:
<TextBox Text="{Binding txt}">
<TextBox.Template>
<ControlTemplate>
<StackPanel>
<TextBlock Text="{Binding labelTxt}" />
<ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
</StackPanel>
</ControlTemplate>
</TextBox.Template>
</TextBox>