2
votes

I am currently working on an MVVM Solution, using WPF Validation.

What I'd like to do is be able to control when the Validation Adorners are shown using a "ShowErrors" Visibility Property in my Context.

I have the following Template for the WPF ComboBox Validation Adorners, contained within my Application.xaml file;

<Style TargetType="{x:Type ComboBox}">
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="Margin" Value="0,2,40,2" />
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel LastChildFill="true">
                            <Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
                            ToolTip="{Binding ElementName=customAdorner1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                                <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
                                </TextBlock>
                            </Border>
                            <AdornedElementPlaceholder Name="customAdorner1" VerticalAlignment="Center" >
                                <Border BorderBrush="red" BorderThickness="1" />
                            </AdornedElementPlaceholder>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

(I have a separate Template for TextBoxes)

After doing some searching on StackOverflow and Google, I tried adding the following to the DockPanel;

Visibility="{Binding DataContext.ShowErrors, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}"

But this doesn't seem to work for me, even though the same method works fine from within the Main XAML.... Any ideas?

EDIT: My UserControl to which I bind my DataContext to has an x:Name of "MainContext"

I found acouple of theads which suggested;

Visibility="{Binding DataContext.ShowErrors, Source={x:Reference Name=MainContext}, Mode=TwoWay}">

Which gives an error of

Unresolved reference 'MainContext'

Aswell as;

Visibility="{Binding DataContext.ShowErrors, ElementName=MainContext, Mode=TwoWay}">

Which just doesn't work.

Edit2: If I move the whole adorner out of the Appliation.xaml and into the UserControl Resources, then use;

Visibility="{Binding DataContext.ShowErrors, ElementName=MainContext, Mode=TwoWay}">

It works... Not ideal though, as I don't want to have to repeat the Template across all my Screens

Edit 3: Ok, so I've found a workaround for now. I used the following for the Visibility Binding...

Visibility="{Binding ElementName=customAdorner1, Path=AdornedElement.Parent.DataContext.ShowErrors, Converter={StaticResource MyBolVisibilityConverter}, Mode=TwoWay}"

I then added a Boolean ShowErrors Property to the context and added a Converter to convert from the Boolean Value to a Visibility Value, mainly because of where the ShowErrors Property actually ended up.

This was slightly more confusing as my Form is a Master Details arrangement, where the Adorners are shown within the details section, which has it's own DataContext. This, of course, doesn't have access to the UserControl's DataContext directly.

This seems like a bit of a hack to me really, so I would appreciate a better solution!

1
When you run it, watch the output for binding errors. Let me know if it says anything.weston
Thanks for the Comment Weston... It didn't show any Binding errors until I actually caused a validation error!... "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='1''. BindingExpression:Path=DataContext.ShowErrors; DataItem=null; target element is 'DockPanel' (Name=''); target property is 'Visibility' (type 'Visibility')"PGallagher
Maybe you don't have a usercontrol in the visual tree. You should be able to use combobox for the ancester type though.weston
Thanks again for your help @weston. I tried "x:Type ComboBox", no luck I'm afraid.PGallagher

1 Answers

2
votes

I solved this by adding a ShowErrors Boolean Property to my DataContext, then Binding the Adorner Visibility to the AdornedElement's Parents DataContext, which of course if my known DataContext.

I used the following XAML in my Application.xaml file;

<Converters:BolVisibilityConverter x:Key="MyBolVisibilityConverter"/>
<Style TargetType="{x:Type TextBox}">
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Margin" Value="0,2,40,2" />
      <Setter Property="Validation.ErrorTemplate">
          <Setter.Value>
              <ControlTemplate>
                        <DockPanel LastChildFill="true" Visibility="{Binding ElementName=customAdorner, Path=AdornedElement.Parent.DataContext.ShowErrors, Converter={StaticResource MyBolVisibilityConverter}, Mode=TwoWay}">
                            <Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
                            ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                                <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
                                </TextBlock>
                          </Border>
                          <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                         <Border BorderBrush="red" BorderThickness="1" />
                       </AdornedElementPlaceholder>
                   </DockPanel>
               </ControlTemplate>
           </Setter.Value>
      </Setter>
</Style>

I used the following Converter Code;

Namespace Converters

    Public Class BolVisibilityConverter
        Implements IValueConverter

        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert

            If value Is Nothing OrElse value = False Then

                Return Visibility.Hidden

            Else

                Return Visibility.Visible

            End If

        End Function

        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
            Return DirectCast(value, Boolean)

        End Function
    End Class

End Namespace

I then simply set the ShowErrors Property in my DataContext, to either True or False in order to Show or Hide the Adorners.

This was very useful, as the Adorners always appear on the very top layer, and so show up over the top of Custom Dialog boxes etc.