I am trying to understand how to add an error template to a child of a UserControl
, and I saw other similar questions but I still could not wrap my head around this.
My goal is to create a UserControl
that has a TextBox
and a Label
, with an optional readonly TextBox
. I already implemented a lot of features and everything is working fine, except that the whole UserControl
gets a red border in case of validation errors.
I tried adding Validation.ValidationAdornerSite
to UserControl
's root element, but it didn't work.
Also, the idea is that the validation should be done inside Model classes, so I don't want to put any validation mechanism inside the UserControl
.
In this case, I don't really understand how the binding system works. It makes sense that the whole UserControl
is highlighted on errors, since it is a UserControl
's property that is bound to the Model
's property, but I expected to be able to "redirect" the error to a child with Validation.ValidationAdornerSite
. What am I missing or misunderstanding? For example, how can I highlight the TextBox
"txt1"?
Thanks in advance!
This is the smallest example I could come up with:
MyControl.xaml
<UserControl x:Class="MyNamespace.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel x:Name="ctRoot">
<Label x:Name="lbTitle" Content="MyLabel"/>
<TextBox x:Name="txt1" Text="{Binding Text}"/>
<TextBox x:Name="txt2"/>
</StackPanel>
</UserControl>
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
ctRoot.DataContext = this;
}
public static readonly DependencyProperty TextProperty = DependencyProperty
.Register("Text", typeof(string), typeof(MyControl), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Model.cs
public class Model : INotifyDataErrorInfo, INotifyPropertyChanged
{
private string m_prop;
public string MyProp
{
get => m_prop;
set
{
m_errors.Clear();
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(MyProp)));
if (value.Length == 0)
{
m_errors.Add("Cannot be empty");
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(MyProp)));
}
m_prop = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyProp)));
}
}
private List<string> m_errors = new List<string>();
public bool HasErrors => m_errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable GetErrors(string propertyName)
{
return m_errors;
}
}
MyWindow.xaml
<Window x:Class="MyNamespace.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
Title="MyWindow" Width="200" Height="200">
<StackPanel>
<local:MyControl Text="{Binding MyProp}" Margin="10"/>
</StackPanel>
</Window>
MyWindow.xaml.cs
public partial class MyWindow : Window
{
public Model model = new Model();
public MyWindow()
{
InitializeComponent();
DataContext = model;
}
}
CodePudding user response:
You can use Validation.ValidationAdornerSiteFor
to change which element appears to indicate that an error occurred.
<UserControl x:Class="WpfApp1.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="myControl">
<StackPanel x:Name="ctRoot">
<Label x:Name="lbTitle" Content="MyLabel"/>
<TextBox x:Name="txt1" Text="{Binding Text, ElementName=myControl}"
Validation.ValidationAdornerSiteFor="{Binding ElementName=myControl}"/>
<TextBox x:Name="txt2"/>
</StackPanel>
</UserControl>