Search code examples
wpfdatatemplate

Control template triggers cannot set value when used with StaticResource or x:Static


Strange issues I faced.

When trying to use StaticResource or x:Static with a converter from ControlTemplate.Trigger the converter value is always NULL.

In the below example the different usages are shown without any issues:

<StackPanel Orientation="Horizontal">
    <ContentControl Content="{DynamicResource Plus}"/>
    <ContentControl Content="{DynamicResource Minus}"/>
    <ContentControl Content="{Binding Source={StaticResource Plus}}"/>
    <ContentControl Content="{Binding Source={StaticResource Minus}}"/>
    <ContentControl Content="{Binding Source={StaticResource Plus}, Converter={StaticResource ToRed}}"/>
    <ContentControl Content="{Binding Source={StaticResource Minus}, Converter={StaticResource ToRed}}"/>
</StackPanel>

<StackPanel Orientation="Horizontal">
    <ContentControl Content="{x:Static icon:Icons.Plus}"/>
    <ContentControl Content="{x:Static icon:Icons.Minus}"/>
    <ContentControl Content="{Binding Source={x:Static icon:Icons.Plus}}"/>
    <ContentControl Content="{Binding Source={x:Static icon:Icons.Minus}}"/>
    <ContentControl Content="{Binding Source={x:Static icon:Icons.Plus}, Converter={StaticResource ToRed}}"/>
    <ContentControl Content="{Binding Source={x:Static icon:Icons.Minus}, Converter={StaticResource ToRed}}"/>
</StackPanel>

The above code results in:

enter image description here

so far all good, both the StaticResouce and x:Static works fine, but the same examples when used within a ControlTemplate.Triggers then the converter gets NULL as VALUE

This is tested in .NET 4.5, 4.7.2 and 4.8 in VS 16.11.15 (2019)

To reproduce:

MyResources.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Viewbox x:Key="Minus" x:Shared="False" Stretch="Uniform">
        <Canvas Width="32" Height="32" Clip="F1 M 0,0L 32,0L 32,32L 0,32L 0,0">
            <Rectangle Width="19" Height="19" Canvas.Left="6.49999" Canvas.Top="6.5" Stretch="Fill" StrokeMiterLimit="2.75" Stroke="#FF575756"/>
            <Rectangle Width="9" Height="2" Canvas.Left="11.5" Canvas.Top="15" Stretch="Fill" Fill="#FF1E5AA0"/>
        </Canvas>
    </Viewbox>

    <Viewbox x:Key="Plus" x:Shared="False" Stretch="Uniform">
        <Canvas Width="32" Height="32" Clip="F1 M 0,0L 32,0L 32,32L 0,32L 0,0">
            <Rectangle Width="19" Height="19" Canvas.Left="6.49999" Canvas.Top="6.5" Stretch="Fill" StrokeMiterLimit="2.75" Stroke="#FF575756"/>
            <Path Width="9" Height="9" Canvas.Left="11.5" Canvas.Top="11.5" Stretch="Fill" Fill="#FF1E5AA0" Data="F1 M 20.5,15L 17,15L 17,11.5L 15,11.5L 15,15L 11.5,15L 11.5,17L 15,17L 15,20.5L 17,20.5L 17,17L 20.5,17L 20.5,15 Z "/>
        </Canvas>
    </Viewbox>
</ResourceDictionary>

Static classes

//STATIC CLASS TO GET ICONS via x:Static
public static class Icons
{
    public static UIElement Minus => GetIconByName("Minus");
    public static UIElement Plus => GetIconByName("Plus");

    private static UIElement GetIconByName(string name)
    {
        try
        {
           return Application.Current.FindResource(name) as UIElement;
        }
        catch
        {
           return null;
        }
   }
}

//STATIC CLASS TO WRITE TO THE CONSOLE TEXTBOX
public static class Console
{
    public static void WriteLine(string message)
    {
        var console = ((MainWindow) Application.Current.MainWindow).Console;
        if (console == null)
        {
            Debug.WriteLine(message);
            return;
        }
        console.Text = $"{message}\n{console.Text}";
    }
}

//STATIC EXTENSION CLASS FOR CHANGING COLORS
public static class Extensions
{
    public static UIElement ToRed(this UIElement element)
    {
        if (element == null) return null;
        var result = (UIElement)System.Windows.Markup.XamlReader.Parse(System.Windows.Markup.XamlWriter.Save(element));

        result.ReColorAll(new SolidColorBrush(Colors.Red));
        return result;
    }
    
    private static void ReColorAll(this DependencyObject element, Brush newColor)
    {
        if (element is null) return;
        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
        {
            // Retrieve child visual at specified index value.
            var childVisual = VisualTreeHelper.GetChild(element, i);
            switch (childVisual)
            {
                case Shape shape:
                    {
                        if (shape.Fill != null) shape.Fill = newColor;
                        if (shape.Stroke != null) shape.Stroke = newColor;
                        break;
                    }
                case GeometryDrawing drawing:
                    {
                        if (drawing.Brush != null) drawing.Brush = newColor;
                        break;
                    }
            }
            
            childVisual.ReColorAll(newColor);
        }
    }
}

Converter class

//CONVERTER CLASS TO CONVERTER ICONS TO RED
public class ToRedConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Console.WriteLine($"PARAMETER: {parameter} - VALUE: {value}");
        var result = !(value is UIElement element) ? value : element.ToRed();

        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

MainWindow.xaml

<Window x:Class="WpfAllPurposesTest.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:icon="clr-namespace:WpfAllPurposesTest"
        Title="InfragisticsWindow" Width="700" Height="800">

    <Window.Resources>
        <icon:ToRedConverter x:Key="ToRed"/>

        <!--STYLE/TEMPLATE FOR TOGGLE BUTTON-->
        <Style x:Key="TreeToggleButtonStyle" TargetType="ToggleButton" x:Shared="False">
            <Setter Property="Focusable" Value="False"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ToggleButton">
                        <Grid Width="20" Height="20" Background="{TemplateBinding Background}">
                            <ContentControl x:Name="ExpandPath" Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!--STYLE/TEMPLATE FOR TREEVIEWITEM-->
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="OverridesDefaultStyle" Value="True"/>
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TreeViewItem}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="25"/>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition x:Name="RowToHide" Height="20"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Border Name="BrdBackground" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Gray" BorderThickness="0,0,0,0"/>
                            <!--VERTICAL LINE-->
                            <Border Name="VerticalLine" Grid.Row="0" Grid.RowSpan="2" Grid.Column="0">
                                <Path Data="M 5 1 L 5 9" StrokeThickness="0.5" Stroke="Black" Stretch="Fill" VerticalAlignment="Stretch" HorizontalAlignment="Center"/>
                            </Border>
                            <!--HORIZONTAL LINE-->
                            <Border Grid.Row="0" Grid.Column="0" Width="25">
                                <Path Data="M 12 12 L 25 12" StrokeThickness="0.5" Stroke="Black" Stretch="None"/>
                            </Border>
                            <!--EXPANDER / PLUS / MINUS ICON-->
                            <ToggleButton x:Name="Expander" Grid.Column="0" Grid.Row="0" Background="White" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" 
                                 ClickMode="Press" Content="{Binding Source={StaticResource Plus}}" Style="{DynamicResource TreeToggleButtonStyle}" Visibility="Visible" Margin="0,0,2,0"/>

                            <!--TREE VIEW ITEM-->
                            <ContentPresenter x:Name="PART_Header" Grid.Column="1" Grid.Row="0" ContentSource="Header" HorizontalAlignment="Left"/>
                            <!--TREE VIEW ITEM CHILDREN HOST-->
                            <ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="2" />
                            <!--DUMMY BORDER TO MAKE ITEM AVAILABLE FOR MOUSE OVER AND SELECTION-->
                            <Border x:Name="TopBorder" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" BorderThickness="0" Background="Transparent"/>
                        </Grid>
                        <!--TRIGGERS-->
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasItems" Value="false">
                                <Setter TargetName="Expander" Property="Visibility" Value="Collapsed"/>
                            </Trigger>
                            <Trigger Property="IsExpanded" Value="false">
                                <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
                            </Trigger>

                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition SourceName="TopBorder" Property="IsMouseOver" Value="true"/>
                                    <Condition Property="IsSelected" Value="false"/>
                                    <Condition Property="IsExpanded" Value="false"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="Expander" Property="Content" Value="{Binding Source={StaticResource Plus}, Converter={StaticResource ToRed}, ConverterParameter=CONVERTtview1}"/>
                                <Setter TargetName="Expander" Property="Background" Value="LightSkyBlue"/>
                                <Setter TargetName="BrdBackground" Property="Background" Value="LightSkyBlue"/>
                            </MultiTrigger>

                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition SourceName="TopBorder" Property="IsMouseOver" Value="true"/>
                                    <Condition Property="IsSelected" Value="false"/>
                                    <Condition Property="IsExpanded" Value="true"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="Expander" Property="Content" Value="{Binding Source={StaticResource Minus}, Converter={StaticResource ToRed}, ConverterParameter=CONVERTtview2}"/>
                                <Setter TargetName="Expander" Property="Background" Value="LightSkyBlue"/>
                                <Setter TargetName="BrdBackground" Property="Background" Value="LightSkyBlue"/>
                            </MultiTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!--STYLE/TEMPLATE FOR BUTTON-->
        <Style TargetType="{x:Type Button}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <StackPanel>
                            <ContentControl Name="Temp_Content" Width="50" Height="50" Content="{StaticResource Plus}"/>
                            <ContentPresenter />
                        </StackPanel>

                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Temp_Content" Property="Content" Value="{Binding Source={StaticResource Minus}, Converter={StaticResource ToRed}, ConverterParameter=CONVERTbtn}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal">
            <ContentControl Content="{DynamicResource Plus}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{DynamicResource Minus}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={StaticResource Plus}}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={StaticResource Minus}}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={StaticResource Plus}, Converter={StaticResource ToRed}}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={StaticResource Minus}, Converter={StaticResource ToRed}}" Width="50" Height="50" Margin="10"/>
        </StackPanel>


        <StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal">
            <ContentControl Content="{x:Static icon:Icons.Plus}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{x:Static icon:Icons.Minus}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={x:Static icon:Icons.Plus}}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={x:Static icon:Icons.Minus}}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={x:Static icon:Icons.Plus}, Converter={StaticResource ToRed}}" Width="50" Height="50" Margin="10"/>
            <ContentControl Content="{Binding Source={x:Static icon:Icons.Minus}, Converter={StaticResource ToRed}}" Width="50" Height="50" Margin="10"/>
        </StackPanel>

        <TreeView Grid.Row="2" Grid.Column="0" Width="200">
            <TreeViewItem Header="Parent 1">
                <TreeViewItem Header="Child 1"/>
                <TreeViewItem Header="Child 2"/>
                <TreeViewItem Header="Child 3"/>
                <TreeViewItem Header="Child 4"/>
            </TreeViewItem>

            <TreeViewItem Header="Parent 2">
                <TreeViewItem Header="Child 1"/>
                <TreeViewItem Header="Child 2"/>
                <TreeViewItem Header="Child 3"/>
                <TreeViewItem Header="Child 4"/>
            </TreeViewItem>

            <TreeViewItem Header="Parent 3">
                <TreeViewItem Header="Child 1"/>
                <TreeViewItem Header="Child 2"/>
                <TreeViewItem Header="Child 3"/>
                <TreeViewItem Header="Child 4"/>
            </TreeViewItem>

            <TreeViewItem Header="Parent 4">
                <TreeViewItem Header="Child 1"/>
                <TreeViewItem Header="Child 2"/>
                <TreeViewItem Header="Child 3"/>
                <TreeViewItem Header="Child 4"/>
            </TreeViewItem>

            <TreeViewItem Header="Parent 5">
                <TreeViewItem Header="Child 1"/>
                <TreeViewItem Header="Child 2"/>
                <TreeViewItem Header="Child 3"/>
                <TreeViewItem Header="Child 4"/>
            </TreeViewItem>
        </TreeView>

        <StackPanel Grid.Row="2" Grid.Column="1" HorizontalAlignment="Center">
            <Button Content="Test"/>
            <Button Content="Test 1"/>
        </StackPanel>
         
        <TextBox  x:Name="Console" Grid.Row="2" Grid.Column="2" IsReadOnly="True"/>
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow
{
    public MainWindow()
    {
        Application.Current.Resources.MergedDictionaries.Add(
            Application.LoadComponent(new Uri("WpfAllPurposesTest;component/MyResources.xaml", UriKind.Relative)
            ) as ResourceDictionary);

        InitializeComponent();
    }
}

UPDATE: While I was writing this question, I realized that this issue somehow is time dependent ?!?

If I immediately use the triggers somehow they work, but waiting a couple of seconds they don't

In the below example I waited a couple of second (finding shortcuts for recording):

enter image description here

When pointing at the TreeViewItems and the Background turns blue, the icon disappears. Also the buttons in the middle does not have the icon from the trigger.

On the right side is the message from the Converter shown, and it shows that the correct trigger is triggered but the converter does not get the value.

In the below example, I was a little bit quicker with the recording short-cut:

enter image description here

In this example, the parent tree view items are shown correctly and icon is also changing, the message from converter shows also that the value is not null.

however for the second trigger which is triggered a little bit later and for the buttons it doesn't work.

Does any one know why this is happening or how to solve it ?

UPDATE

To remove all unnecessary focus and confusion.

This question is about the part:

 <ControlTemplate.Triggers>
      <Trigger Property="IsMouseOver" Value="True">
          <Setter ... Value="{Binding Source={StaticResource PlusRed}}"/>
      </Trigger>
 </ControlTemplate.Triggers>

In a ControlTemplate.Trigger when using the Setter with {Binding Source={StaticResource... It only reads the StaticResource for a brief moment after the compiling, if you wait a couple of seconds. it will not read it.

I removed all of the converters and the issue is still there...

Conclusion:

After some comments, the following comment of @BionicCode cleared the confusion.

The binding you are referring to is static. Binding is intended to bind to a value that can change. Binding will update the target of the source raiders a PropertyChanged event. Binding is not meant to set a referenced to an object. You use StaticResource for this purpose (see my answer)

In order to use the Converter I was creating a binding to a StaticResource which of course was not the intention.


Solution

  • You are doing it completely wrong and too expensive when it comes to performance. Also your styles contain redundant elements like the "TopBorder" in your TreeViewItem template and wrong trigger logic.

    The proper way would be to define all resources in a XAML ResourceDictionary and reference them using the StaticResource markup. Don't use x:Static in this context.

    Your algorithm to make elements red or change their color in general is very inefficient. For example you should not traverse the visual tree of the icon to change the Background of each element - even based on element type (which requires to implement a smelly type switch). Your current algorithm even requires XAML element de-/serialization. In other words, the overall performance is bad, which is totally unnecessary at this point.

    You can replace this logic alongside the converter and simplify it by using data binding properly. This will also improve the performance.

    I assume the desired behavior for the TreeViewItem expander is

    • show "plus" icon when collapsed (to indicate can-be-expanded)
    • show "minus" icon when expanded (to indicate can-be-collapsed)
    • change icon background on mouse over to LightSkyBlue
    • change icon stroke on mouse over to Red

    When implementing triggers, you must give the templated element a default state.
    Since the default of a boolean variable and therefore for the ToggleButton.IsChecked property of the expander is false, you should design the template based on the collapsed state. Then define triggers to change the appearance of elements when the state changes to expanded. This reduces the number of triggers and improves readability.

    Step 1: Implement dynamic Icons

    Define your icons to allow dynamic coloring i.e. fill and stroke. This way we enable to set those attributes via data binding.
    The following example expects the drawings to be hosted in a ContentControl (or a derived type like a Button). It uses the ContentControl.Background to color the icon's background (in this case the Canvas.Background) and the ContentControl.Foreground to color the strokes of the actual icon drawings.

    To achieve this behavior we use Bindig.RelativeSource to bind to the ContentControl (the parent that will be added later in a different scope):

    <Viewbox x:Key="MinusIcon"
             x:Shared="False"
             Stretch="Uniform">
      <Canvas Background="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Background}"
              Width="32"
              Height="32"
              Clip="F1 M 0,0L 32,0L 32,32L 0,32L 0,0">
        <Rectangle Width="19"
                   Height="19"
                   Canvas.Left="6.49999"
                   Canvas.Top="6.5"
                   Stretch="Fill"
                   StrokeMiterLimit="2.75"
                   Stroke="#FF575756" /> <!-- Static border color (gray) -->
        <Rectangle Fill="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Foreground}"
                   Width="9"
                   Height="2"
                   Canvas.Left="11.5"
                   Canvas.Top="15"
                   Stretch="Fill" />
      </Canvas>
    </Viewbox>
    
    <Viewbox x:Key="PlusIcon"
             x:Shared="False"
             Stretch="Uniform">
      <Canvas Background="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Background}"
              Width="32"
              Height="32"
              Clip="F1 M 0,0L 32,0L 32,32L 0,32L 0,0">
        <Rectangle Width="19"
                   Height="19"
                   Canvas.Left="6.49999"
                   Canvas.Top="6.5"
                   Stretch="Fill"
                   StrokeMiterLimit="2.75"
                   Stroke="#FF575756" /> <!-- Static border color (gray) -->
        <Path Fill="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Foreground}"
              Width="9"
              Height="9"
              Canvas.Left="11.5"
              Canvas.Top="11.5"
              Stretch="Fill"
              Data="F1 M 20.5,15L 17,15L 17,11.5L 15,11.5L 15,15L 11.5,15L 11.5,17L 15,17L 15,20.5L 17,20.5L 17,17L 20.5,17L 20.5,15 Z " />
      </Canvas>
    </Viewbox>
    

    Step 2: Use dynamic icons in buttons

    When you want to use the dynamic icons, you can host them in any ContentControl. You use StaticResource markup to reference the icon resources. And you reference it directly (without a Binding to the resource). For example

    <Button Content="{StaticResource PlusIcon}" />
    

    Again, you define a default state (in case of a button this is not-clicked) and define triggers that modify the element when the state changes. It is highly recommended to use the VisualStateManager instead of triggers.

    To keep the answer technically as simple as possible, the following example uses triggers. They change the coloring of the icon and the icon itself (as of your requirement):

    <Style TargetType="Button">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Button">
            <StackPanel>
              <ContentControl x:Name="IconHost"
                              Foreground="LightSkyBlue"
                              Background="Transparent"
                              Content="{StaticResource PlusIcon}"
                              Width="50"
                              Height="50" />
    
              <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment" />
            </StackPanel>
    
            <ControlTemplate.Triggers>
              <Trigger Property="IsMouseOver"
                        Value="True">
                <Setter TargetName="IconHost"
                        Property="Content"
                        Value="{StaticResource MinusIcon}" />
                <Setter TargetName="IconHost"
                        Property="Foreground"
                        Value="Red" />
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
    

    Step 3: Use dynamic icons in ToggleButton of TreeViewItem

    You use the icons like in Step 2, by hosting them in a ContentControl.

    The following snippet shows how to use the icon with the expander ToggleButton. You can move the setting of the ToggleButton.Content to a Style:

    <ToggleButton x:Name="Expander"
                  Grid.Column="0"
                  Grid.Row="0" 
                  Content="{StaticResource PlusIcon}"
                  Background="{TemplateBinding Background}"
                  Foreground="LightSkyBlue"
                  IsChecked="{TemplateBinding IsExpanded}"
                  ClickMode="Press"
                  Style="{DynamicResource TreeToggleButtonStyle}" 
                  Visibility="Visible"
                  Margin="0,0,2,0" /> <!-- Consider to use {StaticResource TreeToggleButtonStyle} when possible -->
    

    Step 4: Bringing it all together

    The following example is an improved version of your TreeViewItem style. It fixes the incorrect trigger logic and highlight behavior.

    The complete logic is implemented completely in XAML. It uses the in Step 1 defined dynamic icons and data binding to make the binding converter and extension methods obsolete.

    The layout is improved, for example the redundant "TopBorder" is removed and all elements bind to the templated parent's TreeViewItem.Background property to fetch the current background highlight color (to reduce trigger setters).

    Then the complete template is now based on the default state, which is the collapsed tree node. All attributes like Visibility and Background are configured accordingly.

    The complete TreeViewItem style could look as follows:

    <Window>
      <Window.Resources>
    
        <!-- Minus icon -->
        <Viewbox x:Key="MinusIcon"
                 x:Shared="False"
                 Stretch="Uniform">
          <Canvas Width="32"
                  Height="32"
                  Clip="F1 M 0,0L 32,0L 32,32L 0,32L 0,0"
                  Background="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Background}">
            <Rectangle Width="19"
                       Height="19"
                       Canvas.Left="6.49999"
                       Canvas.Top="6.5"
                       Stretch="Fill"
                       StrokeMiterLimit="2.75"
                       Stroke="#FF575756" />
            <Rectangle Width="9"
                       Height="2"
                       Canvas.Left="11.5"
                       Canvas.Top="15"
                       Stretch="Fill"
                       Fill="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Foreground}" />
          </Canvas>
        </Viewbox>
    
        <!-- Plus icon -->
        <Viewbox x:Key="PlusIcon"
                 x:Shared="False"
                 Stretch="Uniform">
          <Canvas Width="32"
                  Height="32"
                  Clip="F1 M 0,0L 32,0L 32,32L 0,32L 0,0"
                  Background="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Background}">
            <Rectangle Width="19"
                       Height="19"
                       Canvas.Left="6.49999"
                       Canvas.Top="6.5"
                       Stretch="Fill"
                       StrokeMiterLimit="2.75"
                       Stroke="#FF575756" />
            <Path Width="9"
                  Height="9"
                  Canvas.Left="11.5"
                  Canvas.Top="11.5"
                  Stretch="Fill"
                  Fill="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Foreground}"
                  Data="F1 M 20.5,15L 17,15L 17,11.5L 15,11.5L 15,15L 11.5,15L 11.5,17L 15,17L 15,20.5L 17,20.5L 17,17L 20.5,17L 20.5,15 Z " />
          </Canvas>
        </Viewbox>
    
        <!-- Button style -->
        <Style TargetType="Button">
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="Button">
                <StackPanel>
                  <ContentControl x:Name="IconHost"
                                  Width="50"
                                  Height="50"
                                  Foreground="LightSkyBlue"
                                  Background="Transparent"
                                  Content="{StaticResource Plus}" />
                  <ContentPresenter />
                </StackPanel>
    
                <ControlTemplate.Triggers>
                  <Trigger Property="IsMouseOver"
                           Value="True">
                    <Setter TargetName="IconHost"
                            Property="Content"
                            Value="{StaticResource Minus}" />
                    <Setter TargetName="IconHost"
                            Property="Foreground"
                            Value="Red" />
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </Window.Resources>
    
      <TreeView>
        <!-- TreeViewItem style -->
        <TreeView.ItemContainerStyle>
          <Style TargetType="TreeViewItem">
            <Setter Property="FocusVisualStyle"
                    Value="{x:Null}" />
            <Setter Property="Background"
                    Value="White" />
            <Setter Property="Template">
              <Setter.Value>
                <ControlTemplate TargetType="TreeViewItem">
                  <Grid>
                    <Grid.ColumnDefinitions>
                      <ColumnDefinition Width="25" />
                      <ColumnDefinition Width="Auto" />
                      <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                      <RowDefinition x:Name="RowToHide"
                                     Height="20" />
                      <RowDefinition />
                    </Grid.RowDefinitions>
    
                    <Border Name="BrdBackground"
                            Grid.Row="0"
                            Grid.Column="0"
                            Grid.ColumnSpan="3"
                            Background="{TemplateBinding Background}"
                            BorderBrush="Gray"
                            BorderThickness="0,0,0,0" />
    
                    <!--VERTICAL LINE-->
                    <Border Name="VerticalLine"
                            Grid.Row="0"
                            Grid.RowSpan="2"
                            Grid.Column="0">
                      <Path Data="M 5 1 L 5 9"
                            StrokeThickness="0.5"
                            Stroke="Black"
                            Stretch="Fill"
                            VerticalAlignment="Stretch"
                            HorizontalAlignment="Center" />
                    </Border>
    
                    <!--HORIZONTAL LINE-->
                    <Border Grid.Row="0"
                            Grid.Column="0"
                            Width="25">
                      <Path Data="M 12 12 L 25 12"
                            StrokeThickness="0.5"
                            Stroke="Black"
                            Stretch="None" />
                    </Border>
    
                    <!--EXPANDER / PLUS / MINUS ICON-->
                    <ToggleButton x:Name="Expander"
                                  Grid.Column="0"
                                  Grid.Row="0"
                                  Content="{StaticResource PlusIcon}"
                                  Background="{TemplateBinding Background}"
                                  Foreground="LightSkyBlue"
                                  IsChecked="{TemplateBinding IsExpanded}"
                                  ClickMode="Press"
                                  Style="{DynamicResource TreeToggleButtonStyle}"
                                  Visibility="Visible"
                                  Margin="0,0,2,0" /> <!-- Consider to use {StaticResource TreeToggleButtonStyle} when possible (to improve performance) -->
    
                    <!--TREE VIEW ITEM HEADER -->
                    <ContentPresenter x:Name="PART_Header"
                                      Grid.Column="1"
                                      Grid.Row="0"
                                      ContentSource="Header"
                                      HorizontalAlignment="Left" />
    
                    <!--TREE VIEW ITEM CHILDREN HOST-->
                    <ItemsPresenter x:Name="ItemsHost"
                                    Grid.Row="1"
                                    Grid.Column="1"
                                    Grid.ColumnSpan="2"
                                    Visibility="Collapsed" />
                  </Grid>
    
                  <ControlTemplate.Triggers>
                    <Trigger Property="HasItems"
                             Value="false">
                      <Setter TargetName="Expander"
                              Property="Visibility"
                              Value="Collapsed" />
                    </Trigger>
    
                    <Trigger Property="IsExpanded"
                             Value="True">
                      <Setter TargetName="ItemsHost"
                              Property="Visibility"
                              Value="Visible" />
                      <Setter TargetName="Expander"
                              Property="Content"
                              Value="{StaticResource MinusIcon}" />
                    </Trigger>
    
                    <Trigger Property="IsMouseOver"
                             Value="True">
                      <Setter Property="Background"
                              Value="LightSkyBlue" />
                      <Setter TargetName="Expander"
                              Property="Foreground"
                              Value="Red" />
                    </Trigger>
                  </ControlTemplate.Triggers>
                </ControlTemplate>
              </Setter.Value>
            </Setter>
          </Style>
        </TreeView.ItemContainerStyle>
      </TreeView>
    </Window>