4

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?

vortexwolf
  • 13,967
  • 2
  • 54
  • 72
Jordan
  • 9,642
  • 10
  • 71
  • 141
  • For switching views I use a list of usercontrols in a viewmodel and bind a current view to the content property of a contentcontrol. And I haven't found yet a clear example of switching different views for a single model. – vortexwolf Jan 31 '11 at 20:11
  • @vorrtex, I just bind a whole bunch of views to a single view model and let the region manager sort it out. My problem is not one of view-to-view model binding, but rather the effect of switching the UI. When I make my control invisible the rectangular box stays around. – Jordan Jan 31 '11 at 21:41

2 Answers2

7

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}">
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • @Maleak, This doesn't work, the adorned element's visibility is not being directly set, its parent `UserControl` is what is being collapsed. – Jordan Feb 01 '11 at 14:29
  • @Jordan: Updated my answer with 2 new approaches of the same solution. It'll require a pretty specific solution, so it depends on your specific scenario. Let me know if this works better for you – Fredrik Hedblad Feb 01 '11 at 15:34
  • @Jordan: Did my latest update improve the situation? Otherwise, can you please post some code reproducing your problem, or maybe refrase the question a bit to make it more clear what your problem is? – Fredrik Hedblad Feb 02 '11 at 10:53
  • I believe it is a bug with the adornment system; it is something Microsoft should fix. This seems to be the best option, but I really don't like it. Thanks. – Jordan Feb 02 '11 at 16:36
0

I've just had to solve this very problem, for visibility AND opacity.

I did it by creating an inherited attached property which I data bind the ErrorTemplate visibility and opacity to. On the parent element (the actual element that is fading in and out / being collapsed) I simply bind the new attached properties to visibility and opacity respectively.

This method uses WPF's logical tree and existing property value inheritance to solve the problem without code behind, or specific knowledge by your template of what the visibility-controlling parent will be.

In hindsight, I could have created a single attached property of type FrameWorkElement which I can then use to bind on any property on the parent element. This approach would involve less binding and less code to achieve while providing a little more flexibility. Perhaps an attached property already exists to let you do the same thing.

You can read all about attached properties right here: http://msdn.microsoft.com/en-us/library/ms749011.aspx

Alternatively, this is a good stack: How exactly do Attached Properties work in WPF?

Community
  • 1
  • 1
Gusdor
  • 14,001
  • 2
  • 52
  • 64