在ContentPresenter中将Binding Path Fill绑定到Button前景色

5

我有一个按钮样式,其中包含一个ContentPresenter模板,在该模板中,我尝试将Path的填充绑定到按钮的前景色:

<!-- This is inside the template of a button style -->
<ContentPresenter>
 <ContentPresenter.Resources>
  <Style TargetType="{x:Type Path}">
   <Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
   </Style>
  </ContentPresenter.Resources>
</ContentPresenter>

我还有一个没有填充的路径,我可以在按钮中将其作为内容引用,如下所示:

<Button Style="{DynamicResource MyButtonStyle}" Content="{DynamicResource PathIcon}" Foreground="Blue"/>

我希望按钮内的路径是蓝色的,但事实并非如此... 它没有从按钮中获取前景色。
我应该如何使路径绑定到按钮的颜色上呢?
谢谢!
附言:
如果我在值(Value)中放入硬编码的颜色(例如Value="Red"),那么按钮内的路径就会变成红色,所以我知道这行得通...
<ContentPresenter>
 <ContentPresenter.Resources>
  <Style TargetType="{x:Type Path}">
   <Setter Property="Fill" Value="Red"/>
   </Style>
  </ContentPresenter.Resources>
</ContentPresenter>

编辑:

以下是完整的样式和控件模板:

<Style x:Key="Button_Style" TargetType="{x:Type Button}">
        <Setter Property="Foreground" Value="{StaticResource White_Brush}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid x:Name="grid" Background="Transparent">
                        <ContentPresenter>
                            <ContentPresenter.Resources>
                                <Style TargetType="{x:Type Path}">
                                    <Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
                                </Style>
                            </ContentPresenter.Resources>
                        </ContentPresenter>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                               <!-- Should affect Text as well as Paths in the Content property of the button! -->                              
                               <Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
2个回答

7

好的,让我们开始:

它无法从按钮中抓取前景。

在样式中,使用以下结构:

RelativeSource={RelativeSource AncestorType=Button}

因为Style仅仅是一组属性集合,他并不知道控件是否存在,尤其是在视觉树中的内容。由于RelativeSource应该引用视觉树中上面的项。为此,通常使用DataTemplateControlTemplate

如果我在Value中放一个硬编码的颜色(即Value="Red")

是的,在这种情况下,将会工作,并且总是更好地创建表单的设计:

<SolidColorBrush x:Key="MyButtonColor" Color="Blue" />

您可以将其用于控制,例如按钮

<Button Background="{StaticResource MyButtonColor}" ... />

无论是在 Style 中还是其他地方:

<Setter Property="Fill" Value="{StaticResource MyButtonColor}" />   

也就是说,最好不要依赖于可视树中的元素参数(背景颜色等),因为它可能会:

  • 移动到另一个面板(GridStackPanel)或UserControl
  • 离开项目

而作为资源的画笔始终在一个位置,更改它们的位置,所有元素都将使用它们。此外,颜色可以存储在特殊的数据模型中,该数据模型不依赖于特定的技术实现(资源、变量),其中数据可以来自外部源,例如项目/配置设置。

如果可能的话,最好避免使用动态资源,因为这样会不必要地使用系统性能(并且在某些情况下会导致内存泄漏),在您的情况下不需要使用它们。

动态资源通常是显式定义为SolidColorBrush和其他类型的画笔,因为默认情况下它们是被冻结的,并且不建议更改它们,因为上述原因(内存泄漏)。更多信息可以在此处找到:

MSDN上的Freezable对象概述

编辑

据我所了解,您想要为Button创建通用的Style,以使PathText的内容更容易(在同时使用时)。正如我之前提到的,RelativeSource应该位于ControlTemplate周围,因此,Path将位于带有ContentPresenterGrid中。

为了使样式知道是为文本还是路径提供的,Tag(可选属性)指示两个属性:OnlyTextOnlyPath

为了设置Path的数据,我创建了一个附加依赖属性,并在ControlTemplate中进行了规定。

以下是完整的示例:

XAML

<Window x:Class="ButtonPathHelp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ButtonPathHelp"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    WindowStartupLocation="CenterScreen"
    Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <SolidColorBrush x:Key="Green_Brush" Color="Green" />
        <SolidColorBrush x:Key="Black_Brush" Color="Black" />

        <Style x:Key="Button_Style" TargetType="{x:Type Button}">
            <Setter Property="Foreground" Value="{StaticResource Green_Brush}" />
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="VerticalAlignment" Value="Center" />

            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Grid x:Name="grid">
                            <ContentPresenter x:Name="MyContent"
                                              Content="{TemplateBinding Content}"
                                              HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                                              VerticalAlignment="{TemplateBinding VerticalAlignment}" />

                            <Path x:Name="MyPath" 
                                  SnapsToDevicePixels="True" 
                                  Width="20" 
                                  Height="18" 
                                  Stretch="Fill" 
                                  Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" 
                                  Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:MyDependencyClass.DataForPath)}" />
                        </Grid>

                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
                            </Trigger>

                            <Trigger Property="Tag" Value="OnlyText">
                                <Setter TargetName="MyPath" Property="Visibility" Value="Collapsed" />
                                <Setter TargetName="MyContent" Property="Visibility" Value="Visible" />
                            </Trigger>

                            <Trigger Property="Tag" Value="OnlyPath">
                                <Setter TargetName="MyPath" Property="Visibility" Value="Visible" />
                                <Setter TargetName="MyContent" Property="Visibility" Value="Collapsed" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <WrapPanel>
        <WrapPanel.Resources>
            <sys:String x:Key="Save">
                F1 M 20.5833,20.5833L 55.4167,20.5833L 55.4167,55.4167L 45.9167,55.4167L 45.9167,44.3333L 30.0833,44.3333L 30.0833,
                55.4167L 20.5833,55.4167L 20.5833,20.5833 Z M 33.25,55.4167L 33.25,50.6667L 39.5833,50.6667L 39.5833,55.4167L 33.25,
                55.4167 Z M 26.9167,23.75L 26.9167,33.25L 49.0833,33.25L 49.0833,23.75L 26.9167,23.75 Z
            </sys:String>

            <sys:String x:Key="Search">
                F1 M 23.4454,49.2637L 31.7739,41.1598C 30.6986,39.2983 30.4792,37.1377 30.4792,34.8333C 30.4792,27.8377 35.7544,
                22.1667 42.75,22.1667C 49.7456,22.1667 55.4167,27.8377 55.4167,34.8333C 55.4167,41.8289 49.7456,47.1042 42.75,
                47.1042C 40.5639,47.1042 38.5072,46.9462 36.7125,45.9713L 28.3196,54.1379C 27.0829,55.3746 24.6821,55.3746 23.4454,
                54.1379C 22.2088,52.9013 22.2088,50.5004 23.4454,49.2637 Z M 42.75,26.9167C 38.3777,26.9167 34.8333,30.4611 34.8333,
                34.8333C 34.8333,39.2056 38.3777,42.75 42.75,42.75C 47.1222,42.75 50.6667,39.2056 50.6667,34.8333C 50.6667,
                30.4611 47.1222,26.9167 42.75,26.9167 Z
            </sys:String>
        </WrapPanel.Resources>

        <Button Name="SaveButton"
                Style="{StaticResource Button_Style}" 
                Tag="OnlyPath"
                local:MyDependencyClass.DataForPath="{StaticResource Save}"
                Margin="10" />

        <Button Name="JustText"
                Style="{StaticResource Button_Style}" 
                Tag="OnlyText"
                Content="Just Text"
                Margin="10" />

        <Button Name="SearchButton"
                Style="{StaticResource Button_Style}" 
                Tag="OnlyPath"
                local:MyDependencyClass.DataForPath="{StaticResource Search}"
                Margin="10" />
    </WrapPanel>
</Window>

后端代码

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

public class MyDependencyClass : DependencyObject
{
    #region IsCheckedOnDataProperty

    public static readonly DependencyProperty DataForPathProperty;

    public static void SetDataForPath(DependencyObject DepObject, string value)
    {
        DepObject.SetValue(DataForPathProperty, value);
    }

    public static string GetDataForPath(DependencyObject DepObject)
    {
        return (string)DepObject.GetValue(DataForPathProperty);
    }

    #endregion

    static MyDependencyClass()
    {
        PropertyMetadata MyPropertyMetadata = new PropertyMetadata(String.Empty);

        DataForPathProperty = DependencyProperty.RegisterAttached("DataForPath",
                                                            typeof(string),
                                                            typeof(MyDependencyClass),
                                                            MyPropertyMetadata);
    }
}

注意:在样式中,我没有使用TemplateBinding来绑定附加属性,因为TemplateBinding无法在模板外或其VisualTree属性外部工作,所以甚至不能在模板的触发器内使用TemplateBinding。因此,我们必须使用构造函数{RelativeSource TemplatedParent}和Path等于要检索其值的依赖属性。

输出

enter image description here

要下载整个示例,请使用此链接

谢谢您的回复!这里有很多关于WPF最佳实践的好信息,但我不确定如何应用您的评论来解决我的问题。 - user1084857
@user1084857:好的,你有什么问题? - Anatoliy Nikolaev
我可能应该更清楚地表达高级问题:我想能够将文本块、路径或两者都作为按钮的内容,并在鼠标悬停、按下等情况下更改颜色,而不必管理大量状态作为DataTriggers来强制路径的颜色。我想到的一个简单方法是直接在样式/模板中为每个状态设置前景色,然后放在内容上的任何路径都会获取前景色的颜色。这就是我想做的... - user1084857
尝试将RelativeSource移动到ControlTemplate中,它会起作用,在Style中它不会起作用。 - Anatoliy Nikolaev
@user1084857:也许使用转换器可以解决这个问题,但我认为最好的解决方案是使用附加依赖属性。转换器或依赖属性 - 由您决定使用哪种。当输入是参数且结果是动态的时,通常只使用转换器。依赖属性似乎更适合,它只是控件的附加属性,您可以使用Tag来实现此目的。 - Anatoliy Nikolaev
显示剩余4条评论

0

我遇到了类似的问题,但我想知道如何获取处于DISABLED状态的按钮的“前景颜色”(以便正确绘制颜色)。这里有一个非常简单的解决方案。没有模板,没有样式,没有代码,什么也没有。只需要正确的相对绑定 :-):

     <StackPanel Orientation="Horizontal">
        <Button Height="22" IsEnabled="False">
            <Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 " 
                     Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
                <Polygon.LayoutTransform>
                    <RotateTransform Angle="90"></RotateTransform>
                </Polygon.LayoutTransform>
            </Polygon>
        </Button>
        <Button Height="22" IsEnabled="True">
            <Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 " 
                     Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
                <Polygon.LayoutTransform>
                    <RotateTransform Angle="180"></RotateTransform>
                </Polygon.LayoutTransform>
            </Polygon>
        </Button>
    </StackPanel>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接