Search code examples
c#wpfresourcedictionarydynamicresource

Wpf dynamic resource lookup for Validation.ErrorTemplate


in my App.xaml I defined a resource for Validation.ErrorTemplate, which depends on dynamic BorderBrush resource. I intend to define unique BorderBrush in each window I have and also within different blocks inside window.

<!--validation error template-->
<ControlTemplate x:Key="NonValid">
    <Border BorderBrush="{DynamicResource BorderBrush}" BorderThickness="2" Margin="5">
        <AdornedElementPlaceholder x:Name="ui"/>
    </Border>
</ControlTemplate>

and this one to demonstrate my problem (also with dynamic brush resource)

<!--test template-->
<ControlTemplate x:Key="ButtonRes" TargetType="Button">
    <Border BorderBrush="{DynamicResource BorderBrush}" BorderThickness="2" Background="Khaki">
        <ContentPresenter />
    </Border>
</ControlTemplate>

and now window, where I use these templates, can resolve brush resource for normal template, but not for Validation.ErrorTemplate!

how it look like

<Window x:Class="MyApp.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Test" Height="300" Width="300">
    <Window.Resources>
        <!-- window overrides resource-->
        <SolidColorBrush x:Key="BorderBrush" Color="Blue"/>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <!-- button can see window resource-->
        <Button Template="{StaticResource ButtonRes}"/>        

        <Grid Grid.Row="1">
            <Grid.Resources>
                <!-- grid overrides resource-->
                <SolidColorBrush x:Key="BorderBrush" Color="Red"/>
            </Grid.Resources>

            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <!-- button can see grid resource-->
            <Button Template="{StaticResource ButtonRes}"/>

            <!-- errorTemplate CAN     SEE window resource-->
            <!-- errorTemplate CAN NOT SEE grid   resource-->
            <TextBox Grid.Row="1" VerticalAlignment="Center" Text="{Binding Name}" 
                 Validation.ErrorTemplate="{StaticResource NonValid}"/>
        </Grid>
    </Grid>
</Window>

what should I do to get RED border around TextBox?


Solution

  • I have implemented a binding converter which can find resources from Validation.ErrorTemplate. It needs an instance of FrameworkElement (element which shows ErrorTemplate) and resource key:

    public class ResourceProviderConverter : IValueConverter
    {
        /// <summary>
        /// Returns requested resource from element visual tree
        /// </summary>
        /// <param name="value">Should contain FrameworkElement which has access to resource</param>
        /// <param name="targetType"></param>
        /// <param name="parameter">Resource key</param>
        /// <param name="culture"></param>
        /// <returns>Resource value if resource was found</returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {            
            if (parameter != null && (value is FrameworkElement element))
            {
                var result = element.TryFindResource(parameter);
                if (result != null)
                    return result;
            }
    
            return Binding.DoNothing;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    modified ErrorTemplate:

    <local:ResourceProviderConverter x:Key="ResourceProvider"/>
    
    <ControlTemplate x:Key="NonValid">
        <Border BorderBrush="{Binding ElementName=ui, Path=AdornedElement, 
                                      Converter={StaticResource ResourceProvider}, 
                                      ConverterParameter='BorderBrush', 
                                      FallbackValue={x:Static Brushes.Purple}}" 
                BorderThickness="2" Margin="5">
            <AdornedElementPlaceholder x:Name="ui"/>
        </Border>
    </ControlTemplate>
    

    Binding and therefore converter are triggered every time when ErrorTemplate is shown. So it is possible to update resources and see changes in ErrorTemplate. But unlike DynamicResource it doesn't happen immediately. Still resource which is resolved by binding allows customization per instance.