Reputation: 36048
I have the following user control (Realy a TextBox control now):
<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>
Code Behind:
namespace IM.Common.UIControls
{
public partial class IMTextBox
{
public IMTextBox()
{
InitializeComponent();
}
}
}
I have the Following Model:
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;
}
}
}
When I use that "usercontrol" as:
<myControls:IMTextBox Text="{Binding SomeUser.Password2, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
It works amazing! Validation errors show and it works as expected.
I want to add a label to that user control and have validations still work. As a result the root of my usercontrol can no longer be the TextBox itself. As a result I modified the usercontrol to look like:
<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>
The code behind now looks like:
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
)
);
}
}
Now when I try to use the usercontrol I am able to do:
<myControls:IMTextBox Txt="{Binding SomeUser.Password2, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
But the validation error no longer fires :( . In other words if I where to enter "foo foo" the textbox will turn orange on the first example but not on the last example where the root control is a UserControl instead of a TextBox.
How can I still make validation work?
Thanks to the answer from alek kowalczyk I googled his solution because I did not understood his answer and came up with this solution:
http://dutton.me.uk/tag/xnamepart_contenthost/
Upvotes: 2
Views: 2525
Reputation: 4936
The DataContext of your UserControl is different from the one of your Window, so the validation error doesn't get to the textbox, I would suggest to do an custom control derived from TextBox instead of an user control.
Here you have a control template for a textbox with a label, you can store the control template in a resource dictionary if you want to reuse it on several textboxes:
<TextBox Text="{Binding txt}">
<TextBox.Template>
<ControlTemplate>
<StackPanel>
<TextBlock Text="{Binding labelTxt}" />
<ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
</StackPanel>
</ControlTemplate>
</TextBox.Template>
</TextBox>
Upvotes: 1
Reputation: 609
Your issue is in UserControl binding.
<TextBox Text="{Binding Txt, Mode=TwoWay, NotifyOnValidationError=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:IMTextBox}}, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}">
and in dependency property declaration.
public static DependencyProperty TxtProperty = DependencyProperty.Register("Txt", typeof(string), typeof(IMTextBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, null , false, UpdateSourceTrigger.PropertyChanged)
When you're binding Txt property to TextBox.Text property - TextBox does not know the context, where it should find Txt property. You should tell that this property exists in parent element of IMTextBox type. Also, Txt property has default binding OneWay, and will be updated on "Focus Leave". You need to override it in Metadata.
In Binding Txt to Text - tell that this binding is TwoWay and will be updated on each changing.
UPD: working example: 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));
}
}
Upvotes: 3