【问题标题】:In a button's control template, how can I set the color of contained text?在按钮的控件模板中,如何设置包含文本的颜色?
【发布时间】:2011-04-20 21:13:06
【问题描述】:

使用 Silverlight 4 和 WPF 4,我正在尝试创建一个按钮样式,当按钮被鼠标悬停时,它会改变任何包含文本的文本颜色。由于我试图使其与 Silverlight 和 WPF 兼容,因此我正在使用可视状态管理器:

<Style TargetType="{x:Type Button}">
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="Button">
            <Border x:Name="outerBorder" CornerRadius="4" BorderThickness="1" BorderBrush="#FF757679">
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal" />
                        <VisualState x:Name="MouseOver">
                            <Storyboard>
                                <ColorAnimation Duration="0" To="#FFFEFEFE"
                                                Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"
                                                Storyboard.TargetName="contentPresenter"/> 
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
                <Grid>
                    <Border x:Name="Background" CornerRadius="3" BorderThickness="1" BorderBrush="Transparent">
                        <Grid>
                            <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"/>
                        </Grid>
                    </Border>
                </Grid>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

由于这是一个普通旧按钮的模板,我知道不能保证里面甚至有一个文本块,起初我不确定这是否可能。奇怪的是,如果按钮声明如下,文本颜色确实会改变:

<Button Content="Hello, World!" />

但如果按钮声明如下,它不会改变:

<Button>
    <TextBlock Text="Hello, World!" /> <!-- Same result with <TextBlock>Hello, World </TextBlock> -->
</Button>

尽管可视化树(在 snoop 中检查时)是相同的(按钮 -> ContentPresenter -> TextBlock),但需要注意的是,在第一个版本中创建的文本块的数据上下文设置为“Hello, World”,而第二个版本中的文本块只设置了它的文本属性。我假设这与控件创建的顺序有关(第一个版本的按钮创建 TextBlock,在第二个版本中可能首先创建文本块?真的不确定)。

在研究此问题的过程中,我看到了一些在 Silverlight 中有效的解决方案(例如将 ContentPresenter 替换为 ContentControl),但在 WPF 中不起作用(程序实际上崩溃了)。

由于这是在按钮的控件模板中,如果可能的话我想使用 VSM,我认为这也排除了显式更改按钮自己的 Foreground 属性(我不知道如何从内部访问它模板?)

我非常感谢任何人提供的任何帮助和建议。

【问题讨论】:

  • 考虑到 TextElement 与 TextBlock 没有任何关系,我很惊讶它完全有效。

标签: wpf silverlight button controltemplate visualstatemanager


【解决方案1】:

我在我的模板中使用它,它工作正常

<ControlTemplate TargetType="Button">
                <Border 
                    Margin="{TemplateBinding Margin}"
                    BorderBrush="{StaticResource BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    CornerRadius="2"
                    >

                    <TextBlock 
                        Margin="{TemplateBinding Padding}"
                        Text="{TemplateBinding Content}" 
                        Foreground="White"
                         HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Border>

您必须在按钮模板创建中插入此代码 但是当我将文本放入按钮内容中时我会使用它,如果您想在其上添加其他控件,请使用普通样式

【讨论】:

    【解决方案2】:

    所以经过一番思考,我最终得出的解决方案是在按钮的控件模板中的 ContentPresenter 元素中添加一个附加属性。附加属性接受颜色,设置时检查任何文本块的内容呈现器的可视树,然后将它们的前景属性设置为传入的值。这显然可以扩展/制作以处理其他元素,但现在它可以工作满足我的需要。

    public static class ButtonAttachedProperties
        {
            /// <summary>
            /// ButtonTextForegroundProperty is a property used to adjust the color of text contained within the button.
            /// </summary>
            public static readonly DependencyProperty ButtonTextForegroundProperty = DependencyProperty.RegisterAttached(
                "ButtonTextForeground",
                typeof(Color),
                typeof(FrameworkElement),
                new FrameworkPropertyMetadata(Color.FromArgb(255, 0, 0, 0), FrameworkPropertyMetadataOptions.AffectsRender, OnButtonTextForegroundChanged));
    
            public static void OnButtonTextForegroundChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
            {
                if (e.NewValue is Color)
                {
                    var brush = new SolidColorBrush(((Color) e.NewValue)) as Brush;
                    if (brush != null)
                    {
                        SetTextBlockForegroundColor(o as FrameworkElement, brush);
                    }
                }
            }
    
            public static void SetButtonTextForeground(FrameworkElement fe, Color color)
            {
                var brush = new SolidColorBrush(color);
                SetTextBlockForegroundColor(fe, brush);
            }
    
            public static void SetTextBlockForegroundColor(FrameworkElement fe, Brush brush)
            {
                if (fe == null)
                {
                    return;
                }
    
                if (fe is TextBlock)
                {
                    ((TextBlock)fe).Foreground = brush;
                }
    
                var children = VisualTreeHelper.GetChildrenCount(fe);
                if (children > 0)
                {
                    for (int i = 0; i < children; i++)
                    {
                        var child = VisualTreeHelper.GetChild(fe, i) as FrameworkElement;
                        if (child != null)
                        {
                            SetTextBlockForegroundColor(child, brush);
                        }
                    }
                }
                else if (fe is ContentPresenter)
                {
                    SetTextBlockForegroundColor(((ContentPresenter)fe).Content as FrameworkElement, brush);
                }
            }
        }
    

    我像这样修改了模板:

    <ContentPresenter x:Name="contentPresenter" 
                      ContentTemplate="{TemplateBinding ContentTemplate}" 
                      local:ButtonAttachedProperties.ButtonTextForeground="{StaticResource ButtonTextNormalColor}" />
    

    【讨论】:

    • 这也是一个很好的解决方案。我很好奇你可以使用哪些动画故事板,因为你绑定到一个静态资源。
    • 我不得不使用 ObjectAnimationUsingKeyFrames,而不是 ColorAnimation 或类似的东西。
    【解决方案3】:

    这是一个棘手的问题。前景是传递给内容演示者创建的控件的按钮的属性。因为它是依赖属性而不是模板中可用控件的属性,所以很难使用纯 xaml 对其进行动画处理。

    MSDN 有几个关于如何更改前景色的示例。但是,听起来您不想从代码中完成这项工作。 (http://msdn.microsoft.com/en-us/library/system.windows.controls.button(VS.95).aspx)

    按钮的默认模板让您望而却步。正如您所注意到的,按钮是一个无内容的控件。这意味着设计师可以将一些随机的视觉对象推入其中。无内容会强制前景属性成为模板的成员而不是控件的成员,因为没有保证的组件可以将颜色设置为。这就是为什么它里面有一个内容展示器。

    所以现在你有两个选择。 1. 简单但不灵活(纯 xaml),2. 创建自己的控件来完成按钮所做的一切(需要代码和大量测试)

    我会为你实现#1。

    如果您修改按钮的模板并删除内容展示器,您可以在其中放置两个文本块。一个是正​​常颜色,另一个是鼠标悬停在颜色上。

    <TextBlock x:Name="RedBlock" Text="{TemplateBinding Content}"      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Foreground="Red" Visibility="Collapsed"/>       
    <TextBlock x:Name="BlueBlock" Text="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Foreground="#FF0027FF"/> 
    

    注意文本块的文本属性如何绑定到按钮的内容。如果需要绑定到文本以外的任何内容,这将成为一个问题。 TextBlocks 只能显示 AlphaNumeric 值。

    另请注意,默认情况下,我已折叠 RedBlock 上的可见性。

    然后,在我的MouseOver VisualState's Storyboard 中,我可以将 RedBlock 设置为可见而 BlueBlock 设置为不可见:

    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="RedBlock">
        <DiscreteObjectKeyFrame KeyTime="0">
            <DiscreteObjectKeyFrame.Value>
                <Visibility>Visible</Visibility>
            </DiscreteObjectKeyFrame.Value>
        </DiscreteObjectKeyFrame>
    </ObjectAnimationUsingKeyFrames>
    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="BlueBlock">
        <DiscreteObjectKeyFrame KeyTime="0">
            <DiscreteObjectKeyFrame.Value>
                <Visibility>Collapsed</Visibility>
            </DiscreteObjectKeyFrame.Value>
        </DiscreteObjectKeyFrame>
    </ObjectAnimationUsingKeyFrames>
    

    感觉就像是 hack,我可能不会在很多地方实现这个按钮。我想用良好的 DependencyProperties 制作我自己的 Button 来绑定。 HighlightForeground 和 Text 各一个。如果有人试图将内容设置为 AlphaNumeric 值以外的任何值,我还想隐藏或至少抛出异常。但是,XAML 将是相同的,对于不同的视觉状态,我会有不同的文本块。

    我希望这会有所帮助。

    祝你有美好的一天。

    -耶利米

    【讨论】:

    • 嗨,Jeremiah,这是一个非常好的解决方案,在按钮只包含文本的情况下效果很好。就我而言,我希望有一个更通用的解决方案,以便任何文本块子项都可以设置前景色。 (要注意的任何有趣的事情是,当您明确设置其 Foreground 属性时,Button 的工作方式 - 任何子 TextBlocks 都会收到适当的颜色,无论它们在按钮内容中的嵌套程度如何。我已经详细说明了解决方案我最终在这个线程中使用了附加属性。
    猜你喜欢
    • 1970-01-01
    • 2015-12-07
    • 2023-03-21
    • 2012-09-10
    • 2022-01-12
    • 1970-01-01
    • 2015-05-11
    • 2011-09-12
    • 2014-10-24
    相关资源
    最近更新 更多