7

I have a UserControl that will be reused throughout an application we are developing. We are using a framework based on MVVMLight.

For the sake of simplicity lets say the user control contains only one textbox and exposes one dependency property named "Quantity". The textbox on the user control is databound to the dependency property "Quantity".

When the user control is used on a view, the "Quantity" dependency property of the usercontrol is databound to a property in a ViewModel. (This ViewModel is the datacontext of our view by way of the MVVMLight ViewModelLocator).

This all works great! The bindings work, properties are set like I would expect. All is well until it comes to validation.

We are using DataAnnotations to decorate our ViewModel properties. The ViewModel contains a custom implementation of INotifyDataErrorInfo. We have implemented custom styles for most input controls to show a red border around the control, and a message next to the control displaying the validation error message. All of this works great in a normal case (eg. Textbox on a View bound to a property in a view model).

When I attempt the same approach using this user control, what I end up with is a red border around the entire user control and no error indication on the actual textbox. It appears that the fact that there is an error is being reflected in the UI, but it's just not making it to the control I want it to.

I've searched on stackoverflow for this problem, of those questions with solutions, none seem to work for my situation.

My first guess is that because the actual textbox is bound directly to the dependency property itself and not the property on my view model, it is not being notified properly of the errors generated. Is there some way to propogate those errors generated in the viewmodel through the usercontrol and then to the textbox?

Any help or suggestions you can give would be great, thanks.

Here is the UserControl xaml.

<UserControl x:Class="SampleProject.UserControls.SampleControl"
         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" 
         mc:Ignorable="d"  x:Name="sampleControl"
         d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=sampleControl}">
        <TextBox Text="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Width="100" Height="30" />
</Grid>
</UserControl>

The UserControl code behind.

public partial class SampleControl : UserControl
{
    public SampleControl()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty QuantityProperty = 
         DependencyProperty.Register("Quantity", typeof(int?), typeof(SampleControl), 
    new FrameworkPropertyMetadata{DefaultValue=null, BindsTwoWayByDefault = true});

    public int? Quantity
    {
        get { return (int?)GetValue(QuantityProperty); }
        set { SetValue(QuantityProperty, value); }
    }
}

Used on a view.

<userControls:SampleControl Grid.Row="1" Quantity="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Height="60" Width="300"/>

The ViewModel property.

[Required(ErrorMessage = "Is Required")]
[Range(5, 10, ErrorMessage = "Must be greater than 5")]
public int? Quantity
{
    get { return _quantity; }
    set { Set(() => Quantity, ref _quantity, value); }
}
private int? _quantity;

(*Note, The Set method in the setter is just a helper method in the base viewmodel that sets the backing property and raises the PropertyChanged event for it.)

thornhill
  • 632
  • 7
  • 17
  • When the code is working, the Error Message appears at the same TextBox? – trinaldi Oct 07 '13 at 01:29
  • Yes. If that textbox were on the view itself and bound directly to the Quantity property on the ViewModel then the validation error appears on the textbox. But when the textbox is within the usercontrol and the binding goes through the dependency property of the user control, the validation error is lost. – thornhill Oct 07 '13 at 03:22
  • Even if you put a breakpoint? – trinaldi Oct 07 '13 at 03:28
  • I'm not sure where you want me to set a breakpoint... But I have verified that the property on the view model is being set and the validation errors are being generated. – thornhill Oct 07 '13 at 14:00
  • Did you find the solution to this problem? I'm also facing the similar problem – Anup Sharma May 06 '17 at 11:34

2 Answers2

0

Try removing the DataContext from the UserControl. Instead of setting that, Bind directly from the TextBox to the actual property using a RelativeSource Binding:

<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor, 
    AncestorType={x:Type YourControlNamespace:SampleControl, 
    ValidatesOnDataErrors=True}}}" Width="100" Height="30" />

UPDATE >>>

Failing that, as long as the view models that are bound to this property will always have a property of the same name to bind to, you can get this Binding to search through parents' DataContexts like this:

<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor, 
    AncestorLevel=2, ValidatesOnDataErrors=True}}}" Width="100" Height="30" />

You will need to change the 2 to be the correct number of parent elements that the TextBox has before reaching the control with access to the correct property. For example, using a level of 2 means that the Framework will try to find a property named Quantity to Bind to in the DataContext of the TextBoxs parent's parent control. It is trickier getting this working with AncestorLevel though as I believe that 'hidden' elements like Grids are not included as parents.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • No, that does not fix anything. The behavior is exactly the same as with the datacontext being set on the LayoutRoot, and the binding not using the RelativeSource binding. – thornhill Oct 07 '13 at 13:59
  • Thanks for the suggestion,that would work, although I could achieve what you have above without using the relative source binding by just not specifying a datacontext on the user control at all so it would inherit the parents datacontext (which is my viewmodel). Then the binding on the control would just have to be 'Text="{Binding Quantity}"'. However, I would prefer to not force users of this control to have to name the properties on their viewmodel a certain way so that they can use a UI component. This (http://goo.gl/zR2zN6) is a pattern for creating user controls that avoids that issue. – thornhill Oct 07 '13 at 14:38
  • well, then somehow you would have to add yet another DependencyProperty where you could inject the Data Annotations into the Value field... This is beginning to get over engineered for something that is supposed to be a helpful. – Kevin Cook Nov 29 '18 at 19:20
0

You need to pick up the bindings set on the usercontrol and place them on the controls, there is no need to bind the usercontrol to it's own DataContext. This can be done after the usercontrol is loaded.

To prevent a red border round the user control, remove the default error template:

Validation.ErrorTemplate="{x:Null}"

Sample user control XAML:

UserControl x:Class="DxUserControlValidation.MyUserControl"
         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" 
         mc:Ignorable="d" 
         Validation.ErrorTemplate="{x:Null}"
         d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Vertical">
    <TextBlock Text="Value 1:" Margin="2"/>
    <TextBox Name="txtBox1" Margin="2"/>
    <TextBlock Text="Value 2:" Margin="2"/>
    <TextBox Name="txtBox2" Margin="2"/>
</StackPanel>

public partial class MyUserControl : UserControl
{
    public static readonly DependencyProperty Value1Property;
    public static readonly DependencyProperty Value2Property;

    static MyUserControl()
    {
        Value1Property = DependencyProperty.Register("Value1", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
        Value2Property = DependencyProperty.Register("Value2", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
    }

    public MyUserControl()
    {
        InitializeComponent();

        Loaded += (s, e) =>
        { 
            Binding value1Binding = BindingOperations.GetBinding(this, Value1Property);
            if (value1Binding != null) txtBox1.SetBinding(TextBox.TextProperty, value1Binding);
            Binding value2Binding = BindingOperations.GetBinding(this, Value2Property);
            if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
        };
    }

    public string Value1
    {
        get { return (string)GetValue(Value1Property); }
        set { SetValue(Value1Property, value); }
    }

    public string Value2
    {
        get { return (string)GetValue(Value2Property); }
        set { SetValue(Value2Property, value); }
    }
}

If there is no binding, you van assign the value directly to the control:

if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
else txtBox2.Text = Value2;
pexxxy
  • 489
  • 1
  • 6
  • 17