在按钮的控件模板中,如何设置包含的文本的颜色?

时间:2022-07-07 18:16:10

Using Silverlight 4 & WPF 4, I'm trying to create a button style that alters the text color of any contained text when the button is mouseover'd. Since I'm trying to make this compatible with both Silverlight & WPF, I'm using the visual state manager:

使用Silverlight 4 & WPF 4,我尝试创建一个按钮样式,当鼠标移到按钮时,它会改变任何包含文本的文本颜色。由于我试图使它与Silverlight和WPF兼容,所以我使用了visual state manager:

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

Since this is a template for a regular old button, I know there's no guarantee that there even is a textblock inside of it, and at first I wasn't sure this was even possible. Curiously, the text color does change if the button is declared like:

因为这是一个普通旧按钮的模板,我知道没有保证它里面有一个textblock,起初我不确定这是否可能。奇怪的是,如果按钮声明为:

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

but it does not change if the button is declared like:

但如果按钮声明为:

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

Even though the visual tree (when inspected in snoop) is identical (Button -> ContentPresenter -> TextBlock), with the caveat that the textblock created in the 1st version has it's data context set to "Hello, World", whereas the textblock in the second version merely has its text property set. I'm presuming this has something to do with the order of control creation (the first version the button creates the TextBlock, in the second version the textblock might be created first? Really not sure on this).

尽管视觉树(snoop当检查)是相同的(按钮- > ContentPresenter - > TextBlock),但需要说明的是,在第一个版本中创建的TextBlock数据上下文设置为“你好,世界”,而在第二个版本只是TextBlock其文本属性集。我假设这与创建的顺序控制(按钮创建一个TextBlock第一个版本,第二个版本TextBlock可能首先创建?真的不确定。

In the course of researching this, I've seen some solutions that work in Silverlight (like replacing the ContentPresenter with a ContentControl), but that won't work in WPF (program actually crashes).

在研究这方面的过程中,我看到了一些在Silverlight中工作的解决方案(比如用ContentControl替换ContentPresenter),但是在WPF中就不行了(程序实际上崩溃了)。

Since this is in the button's control template, and I'd like to use the VSM if possible, I think that also rules out explicitly changing the Button's own Foreground property (I don't know how I would access that from within the template?)

由于这是在按钮的控制模板中,如果可能的话,我希望使用VSM,我认为这也排除了显式地更改按钮自己的前台属性(我不知道如何从模板中访问它)的可能性。

I'd really appreciate any help, advice anyone could give.

我非常感谢大家的帮助和建议。

3 个解决方案

#1


5  

So after some more thinking, the solution I've ultimately arrived at is to add an attached property to the ContentPresenter element within the button's control template. The attached property accepts a Color and when set examines the visual tree of the content presenter for any TextBlocks, and in turn sets their Foreground properties to the value passed in. This could obviously be expanded/made to handle additional elements but for now it works for what I need.

所以经过更多的思考,我最终得到的解决方案是在按钮的控件模板中向ContentPresenter元素添加一个附加属性。附加的属性接受一个颜色,当set检查内容演示程序的可视树以查看任何文本块时,然后将它们的前台属性设置为传入的值。这显然可以扩展/制作来处理额外的元素,但是现在它可以满足我的需要。

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);
            }
        }
    }

and I modified the template like so:

我对模板做了如下修改:

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

#2


4  

This is a tricky one. The foreground is a property of the button that is passed through to the controls the content presenter creates. Because it is a dependency property and not a property of the controls available in the template it is hard to animate it using pure xaml.

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

MSDN Has a couple samples on how to change foreground color. However, it doesn't sound like you want to do this work from code. (http://msdn.microsoft.com/en-us/library/system.windows.controls.button(VS.95).aspx)

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

The button's default template is holding you back. As you've noticed, button is a content-less control; meaning the designer can push some random visual object inside of it. Being content-less forces the foreground property to be a member of the template rather than the control because there isn't a guarranteed component to set the color to. Which is why there is a content presenter inside of it.

这个按钮的默认模板会阻碍你。正如您所注意到的,button是一个无内容控件;意思是设计者可以在里面随意的推一些视觉对象。缺少内容将迫使前台属性成为模板的成员,而不是控件,因为没有将颜色设置为guarranteed组件。这就是为什么里面有一个内容展示器。

So now you have two options. 1. Easy but not flexible (pure xaml), 2. Create your own control that does everything a button does (requires code and lots of testing)

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

I'll implement #1 for you.

我将为您实现第一条。

If you modify the template of the button and remove the content presenter you can place inside of it two textblocks. One with the normal color, the other with your mouse over color.

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

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

Notice how the Text properties of the Textblocks are bound to the content from button. This becomes a problem if there is ever a need to bind to ANYTHING other than text. TextBlocks simply can't show anything but AlphaNumeric values.

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

Also notice that by default I have collapsed the visibility on the RedBlock.

还要注意,在默认情况下,我已经折叠了RedBlock上的可见性。

Then, in my MouseOver VisualState's Storyboard I can animate the RedBlock to be visible and the BlueBlock to be invisible:

然后,在鼠标悬停VisualState的故事板中,我可以将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>

It feels like a hack, and I probably wouldn't implement this button in lots of places. I'd want to make my own Button with good DependencyProperties to bind to. One each for HighlightForeground and Text. I'd also want to hide or atleast throw an exception if someone tried to set the content to anything other than AlphaNumeric values. However, the XAML would be the same, I'd have different textblocks for different visual states.

这感觉像一个黑客,我可能不会在很多地方实现这个按钮。我想要创建一个具有良好依赖性属性的按钮。其中一个用于高亮前景和文本。如果有人试图将内容设置为除字母数字值之外的任何值,我还希望隐藏或至少抛出一个异常。但是,XAML是相同的,不同的视觉状态会有不同的文本块。

I hope this helps.

我希望这可以帮助。

Have a great day.

祝你有美好的一天。

-Jeremiah

耶利米

#3


0  

I use this in my template and it works fine

我在模板中使用这个,它工作得很好

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

You must insert this code in your button template creation but i use this when i put text in the button content and if you want to add other controls on it use the normal style

您必须在按钮模板创建中插入这段代码,但是当我在按钮内容中插入文本时,我就会使用这段代码

#1


5  

So after some more thinking, the solution I've ultimately arrived at is to add an attached property to the ContentPresenter element within the button's control template. The attached property accepts a Color and when set examines the visual tree of the content presenter for any TextBlocks, and in turn sets their Foreground properties to the value passed in. This could obviously be expanded/made to handle additional elements but for now it works for what I need.

所以经过更多的思考,我最终得到的解决方案是在按钮的控件模板中向ContentPresenter元素添加一个附加属性。附加的属性接受一个颜色,当set检查内容演示程序的可视树以查看任何文本块时,然后将它们的前台属性设置为传入的值。这显然可以扩展/制作来处理额外的元素,但是现在它可以满足我的需要。

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);
            }
        }
    }

and I modified the template like so:

我对模板做了如下修改:

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

#2


4  

This is a tricky one. The foreground is a property of the button that is passed through to the controls the content presenter creates. Because it is a dependency property and not a property of the controls available in the template it is hard to animate it using pure xaml.

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

MSDN Has a couple samples on how to change foreground color. However, it doesn't sound like you want to do this work from code. (http://msdn.microsoft.com/en-us/library/system.windows.controls.button(VS.95).aspx)

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

The button's default template is holding you back. As you've noticed, button is a content-less control; meaning the designer can push some random visual object inside of it. Being content-less forces the foreground property to be a member of the template rather than the control because there isn't a guarranteed component to set the color to. Which is why there is a content presenter inside of it.

这个按钮的默认模板会阻碍你。正如您所注意到的,button是一个无内容控件;意思是设计者可以在里面随意的推一些视觉对象。缺少内容将迫使前台属性成为模板的成员,而不是控件,因为没有将颜色设置为guarranteed组件。这就是为什么里面有一个内容展示器。

So now you have two options. 1. Easy but not flexible (pure xaml), 2. Create your own control that does everything a button does (requires code and lots of testing)

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

I'll implement #1 for you.

我将为您实现第一条。

If you modify the template of the button and remove the content presenter you can place inside of it two textblocks. One with the normal color, the other with your mouse over color.

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

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

Notice how the Text properties of the Textblocks are bound to the content from button. This becomes a problem if there is ever a need to bind to ANYTHING other than text. TextBlocks simply can't show anything but AlphaNumeric values.

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

Also notice that by default I have collapsed the visibility on the RedBlock.

还要注意,在默认情况下,我已经折叠了RedBlock上的可见性。

Then, in my MouseOver VisualState's Storyboard I can animate the RedBlock to be visible and the BlueBlock to be invisible:

然后,在鼠标悬停VisualState的故事板中,我可以将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>

It feels like a hack, and I probably wouldn't implement this button in lots of places. I'd want to make my own Button with good DependencyProperties to bind to. One each for HighlightForeground and Text. I'd also want to hide or atleast throw an exception if someone tried to set the content to anything other than AlphaNumeric values. However, the XAML would be the same, I'd have different textblocks for different visual states.

这感觉像一个黑客,我可能不会在很多地方实现这个按钮。我想要创建一个具有良好依赖性属性的按钮。其中一个用于高亮前景和文本。如果有人试图将内容设置为除字母数字值之外的任何值,我还希望隐藏或至少抛出一个异常。但是,XAML是相同的,不同的视觉状态会有不同的文本块。

I hope this helps.

我希望这可以帮助。

Have a great day.

祝你有美好的一天。

-Jeremiah

耶利米

#3


0  

I use this in my template and it works fine

我在模板中使用这个,它工作得很好

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

You must insert this code in your button template creation but i use this when i put text in the button content and if you want to add other controls on it use the normal style

您必须在按钮模板创建中插入这段代码,但是当我在按钮内容中插入文本时,我就会使用这段代码