简体   繁体   English

与 StaticResource 或 x:Static 一起使用时,控件模板触发器无法设置值

[英]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 .当尝试将StaticResourcex:Static与来自ControlTemplate.Trigger的转换器一起使用时,转换器value始终为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:上面的代码导致:

在此处输入图像描述

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到目前为止一切都很好, StaticResoucex:Static都可以正常工作,但是在ControlTemplate.Triggers中使用相同的示例,然后转换器将NULL作为VALUE

This is tested in .NET 4.5, 4.7.2 and 4.8 in VS 16.11.15 (2019)这在 VS 16.11.15 (2019) 中的 .NET 4.5、4.7.2 和 4.8 中进行了测试

To reproduce:重现:

MyResources.xaml我的资源.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主窗口.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主窗口.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):在下面的示例中,我等待了几秒钟(查找录制的快捷方式):

在此处输入图像描述

When pointing at the TreeViewItem s and the Background turns blue, the icon disappears.当指向TreeViewItem并且Background变为蓝色时,图标消失。 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:在下面的示例中,我使用录制快捷方式稍微快了一点:

在此处输入图像描述

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.ControlTemplate.Trigger中使用带有{Binding Source={StaticResource...Setter时,如果您等待几秒钟,它只会在编译后短暂读取StaticResource 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.经过一些评论, @BionicCode的以下评论消除了混乱。

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.绑定将更新源袭击者的目标一个 PropertyChanged 事件。 Binding is not meant to set a referenced to an object.绑定并不意味着设置对对象的引用。 You use StaticResource for this purpose (see my answer)您为此目的使用 StaticResource(请参阅我的回答)

In order to use the Converter I was creating a binding to a StaticResource which of course was not the intention.为了使用 Converter,我创建了一个到 StaticResource 的绑定,这当然不是本意。

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.此外,您的样式包含冗余元素,例如TreeViewItem模板中的"TopBorder"和错误的触发逻辑。

The proper way would be to define all resources in a XAML ResourceDictionary and reference them using the StaticResource markup.正确的方法是在 XAML ResourceDictionary中定义所有资源并使用StaticResource标记来引用它们。 Don't use x:Static in this context.不要在这种情况下使用x:Static

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).例如,您不应该遍历图标的可视化树来更改每个元素的Background ——即使基于元素类型(这需要实现异味类型切换)。 Your current algorithm even requires XAML element de-/serialization.您当前的算法甚至需要 XAML 元素反序列化/序列化。 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我假设TreeViewItem扩展器的期望行为是

  • 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将鼠标上的图标背景更改为LightSkyBlue
  • change icon stroke on mouse over to Red将鼠标上的图标笔划更改为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.由于布尔变量的默认值以及扩展器的ToggleButton.IsChecked属性为false ,因此您应该根据折叠状态设计模板。 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第 1 步:实现动态图标

Define your icons to allow dynamic coloring ie 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 ).以下示例期望绘图托管在ContentControl (或派生类型,如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.它使用ContentControl.Background为图标的背景(在本例中为Canvas.Background )着色,并使用ContentControl.Foreground为实际图标绘图的笔划着色。

To achieve this behavior we use Bindig.RelativeSource to bind to the ContentControl (the parent that will be added later in a different scope):为了实现这种行为,我们使用Bindig.RelativeSource绑定到ContentControl (稍后将在不同范围内添加的父级):

<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第 2 步:在按钮中使用动态图标

When you want to use the dynamic icons, you can host them in any ContentControl .当您想使用动态图标时,您可以将它们托管在任何ContentControl中。 You use StaticResource markup to reference the icon resources.您使用StaticResource标记来引用图标资源。 And you reference it directly (without a Binding to the resource).直接引用它(没有Binding到资源)。 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.强烈建议使用VisualStateManager而不是触发器。

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第三步:在TreeViewItemToggleButton中使用动态图标

You use the icons like in Step 2 , by hosting them in a ContentControl .您可以使用第 2 步中的图标,将它们托管在ContentControl中。

The following snippet shows how to use the icon with the expander ToggleButton .以下片段显示了如何将图标与扩展器ToggleButton一起使用。 You can move the setting of the ToggleButton.Content to a Style :您可以将ToggleButton.Content的设置移动到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第 4 步:将所有内容放在一起

The following example is an improved version of your TreeViewItem style.以下示例是TreeViewItem样式的改进版本。 It fixes the incorrect trigger logic and highlight behavior.它修复了不正确的触发逻辑并突出显示行为。

The complete logic is implemented completely in XAML.完整的逻辑完全在 XAML 中实现。 It uses the in Step 1 defined dynamic icons and data binding to make the binding converter and extension methods obsolete.它使用Step 1中定义的动态图标和数据绑定来使绑定转换器和扩展方法过时。

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).改进了布局,例如删除了多余的"TopBorder" ,所有元素都绑定到模板化父级的TreeViewItem.Background属性以获取当前背景突出显示颜色(以减少触发器设置器)。

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. VisibilityBackground等所有属性都进行了相应配置。

The complete TreeViewItem style could look as follows:完整的TreeViewItem样式如下所示:

<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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 WPF从x:static定义一个double的static资源 - WPF define a staticresource from x:static for doubles 使用staticresource在xaml中设置结构的值 - set value of structs in xaml with staticresource XAML WPF 中 {x:Static} 和 {StaticResource} 的区别 - Difference between {x:Static} and {StaticResource} in XAML WPF 在后面的代码中使用StaticResource和x:Static - Using StaticResource and x:Static in code behind 对于Setter上的“ System.Windows.Controls.Control.Template”属性,“ StaticResource MyTemplate”不是有效值 - 'StaticResource MyTemplate' is not a valid value for the 'System.Windows.Controls.Control.Template' property on a Setter 控制模板可见性触发器 - control template visibility triggers 在后面的代码中获取控件的样式{StaticResource {x:Type TextBlock}} - Get the Style of a Control {StaticResource {x:Type TextBlock}} in code behind 如何使用默认值填充模板中使用的控件? - How to populate the control that is used in the template with default value? 在控件模板中指定背景和内容时,未设置按钮控件的背景和内容属性的本地值 - Local value for Background and Content property at button control is not set when background and content is specified in Control template 控制模板故事板,在同一模板内的其他控件中设置值 - Control Template Storyboard, set value in other control within same template
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM