Search code examples
c#wpfbindingcontroltemplateidataerrorinfo

Different ControlTemplates for ErrorTemplate


In my application I have a ControlTemplate which I use to show that the input of a TextBox is invalid. The ControlTemplate is defined as:

<ControlTemplate x:Key="TextBoxErrorTemplate" TargetType="Control">
    <Grid ClipToBounds="False">
        <Border BorderBrush="Red" BorderThickness="1" Margin="-1">
            <AdornedElementPlaceholder Name="adornedElement" />
        </Border>
        <Image HorizontalAlignment="Right" VerticalAlignment="Top"
               Width="16" Height="16" Margin="0,-9,-8,0" Source="pack://application:,,,/UI.Resources;component/Graphics/Error_16_16.png"
               ToolTip="{Binding ElementName=adornedElement, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
    </Grid>
</ControlTemplate>

And the usage is:

<TextBox Grid.Row="1" Margin="0,5"
         Text="{Binding UserGroup.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
         Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"/>

The UserGroup-class implements the IDataErrorInfo-Interface. A small part of the class looks like:

public class UserGroup
{
    public string Name
    {
        get { return Get(() => Name); }
        set { Set(() => Name, value); }
    }

    public bool IsDuplicate
    {
        get { return Get(() => IsDuplicate); }
        set { Set(() => IsDuplicate, value); }
    }

    public bool IsSimilar
    {
        get { return Get(() => IsSimilar); }
        set { Set(() => IsSimilar, value);}
    }
}

And the implementation of the getter which is required by IDataErrorInfo looks like:

public string this[string columnName]
{
    get
    {
        string result = string.Empty;
        string namePropertyName = GetPropertyNameFromExpression(() => Name);
        string isDuplicatePropertyName = GetPropertyNameFromExpression(() => IsDuplicate);
        string isSimilarPropertyName = GetPropertyNameFromExpression(() => IsSimilar);

        if (columnName == namePropertyName)
        {
            if(IsSimilar)
            {
                result = "Be careful with similar group-names!";
                if (!Error.Contains(isSimilarPropertyName))
                {
                    Error += isSimilarPropertyName;
                }
            }
            else
            {
                Error = Error.Replace(isSimilarPropertyName, string.Empty);
            }

            if (IsDuplicate)
            {
                result = "Duplicate names are not allowed!";
                if (!Error.Contains(isDuplicatePropertyName))
                {
                    Error += isDuplicatePropertyName;
                }
            }           
            else
            {
                Error = Error.Replace(isDuplicatePropertyName, string.Empty);
            }
        }       
        return result;
    }
}

So if IsSimilar or IsDuplicate is true the TextBoxErrorTemplate-ControlTemplate will be used.

What I want is the the TextBoxErrorTemplate only should be used if IsDuplicate equals true.

In the case that IsSimilar equals true I want to use the following ControlTemplate:

<ControlTemplate x:Key="TextBoxWarningTemplate" TargetType="Control">
    <Grid ClipToBounds="False">
        <Border BorderBrush="Orange" BorderThickness="1" Margin="-1">
            <AdornedElementPlaceholder Name="adornedElement" />
        </Border>
        <Image HorizontalAlignment="Right" VerticalAlignment="Top"
               Width="16" Height="16" Margin="0,-9,-8,0" Source="pack://application:,,,/UI.Resources;component/Graphics/Warning_16_16.png"
               ToolTip="{Binding ElementName=adornedElement, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
    </Grid>
</ControlTemplate>

Is there a way to achieve this? My first approach was to overlay the TextBox with a Border and only show it if IsSimilar is true, but that doesn't really looks good...

I've read about a TemplateSelector but only in the context of a DataGridTemplateColumn


Solution

  • I solved it by myself. The solution is only one ControlTemplate where the ImageSource and the BorderBrush are bound to a UserGroup-Property and the decision is done in two converters.

    The ControlTemplate looks like:

     <ControlTemplate x:Key="TextBoxErrorTemplate" TargetType="Control">
        <Grid ClipToBounds="False">
            <Border BorderThickness="1" Margin="-1" 
                    BorderBrush="{Binding DataContext.UserGroup, Converter={converters:UserGroupToBrushConverter}, 
                                  RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
                <AdornedElementPlaceholder Name="adornedElement" />
            </Border>
            <Image HorizontalAlignment="Right" VerticalAlignment="Top"
                   Width="16" Height="16" Margin="0,-9,-8,0" 
                   Source="{Binding DataContext.UserGroup, Converter={converters:UserGroupToImageSourceConverter},
                            RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
                   ToolTip="{Binding ElementName=adornedElement, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
        </Grid>
    </ControlTemplate>