Search code examples
wpfmvvmadorner

Hiding validation adornment when hiding a control


How, in WPF, do you hide the validation error template adornment (red box by default) when you hide a control? When I hide my controls (to facilitate switching between views) the error adornment sticks around.

Even more difficult, how do I do this using MVVM?


Solution

  • The default ControlTemplate for the Validation.ErrorTemplate has an AdornedElementPlaceholder which in turn has a reference to its AdornedElement. It looks like this

    <ControlTemplate>
        <Border BorderBrush="Red" BorderThickness="1">
            <AdornedElementPlaceholder />
        </Border>
    </ControlTemplate>
    

    From here would could bind the Visibility of the Border to the Visibility of the AdornedElementPlaceholder.AdornedElement to link their Visibility. Then we make all the Control's that has this problem use this Validation.ErrorTemplate instead of the default one. Here's an example

    Xaml

    <Window.Resources>
        <ControlTemplate x:Key="ValidationErrorTamplate">
            <Border Visibility="{Binding ElementName=placeHolder,
                                         Path=AdornedElement.Visibility}"
                    BorderBrush="Red"
                    BorderThickness="1">
                <AdornedElementPlaceholder x:Name="placeHolder"/>
            </Border>
        </ControlTemplate>
    </Window.Resources>
    <TextBox ...
             Validation.ErrorTemplate="{StaticResource ValidationErrorTamplate}">
    

    Update
    To reference the parent UserControl in the binding you can

    1.For a specific control you can walk up the logical tree using the Parent Property

    Example: If the TextBox is located in a StackPanel in the UserControl we can reference it with Parent.Parent

    <UserControl ...>
        <StackPanel>
            <TextBox ...
                     Validation.ErrorTemplate="{StaticResource ValidationErrorTamplate2}">
    
    <ControlTemplate x:Key="ValidationErrorTamplate2">
        <Border Visibility="{Binding ElementName=placeHolder,
                                     Path=AdornedElement.Parent.Parent.Visibility}"
                BorderBrush="Red"
                BorderThickness="1">
            <AdornedElementPlaceholder x:Name="placeHolder"/>
        </Border>
    </ControlTemplate>
    

    2.For a more dynamic approach you can use a ResourceDictionary with a code behind file where you make use of the Loaded event for the Border. In it, you walk up the visual tree to find the parent UserControl and use that as the source for the Binding

    ValidationErrorTemplateDictionary.xaml

    <ResourceDictionary x:Class="ValidationErrorVisibility.ValidationErrorTemplateDictionary"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <ControlTemplate x:Key="ValidationErrorTamplate3">
            <Border BorderBrush="Red"
                    BorderThickness="1"
                    Loaded="ValidationAdorner_Loaded">
                <AdornedElementPlaceholder/>
            </Border>
        </ControlTemplate>
    </ResourceDictionary>
    

    ValidationErrorTemplateDictionary.xaml.cs

    public partial class ValidationErrorTemplateDictionary
    {
        private void ValidationAdorner_Loaded(object sender, RoutedEventArgs e)
        {
            Border adornedBorder = sender as Border;
            Binding visibilityBinding = new Binding("Visibility");
            UIElement adornedElement = ((AdornedElementPlaceholder)adornedBorder.Child).AdornedElement;
            UserControl parentUserControl = GetVisualParent<UserControl>(adornedElement);
            visibilityBinding.Source = parentUserControl;
            adornedBorder.SetBinding(Border.VisibilityProperty, visibilityBinding);
        }
    
        public static T GetVisualParent<T>(object childObject) where T : Visual
        {
            DependencyObject child = childObject as DependencyObject;
            while ((child != null) && !(child is T))
            {
                child = VisualTreeHelper.GetParent(child);
            }
            return child as T;
        }
    }
    

    Your UserControl

    <UserControl ...>
        <UserControl.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="ValidationErrorTemplateDictionary.xaml"/>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </UserControl.Resources>
        <StackPanel>
            <TextBox ...
                     Validation.ErrorTemplate="{StaticResource ValidationErrorTamplate3}">