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

时间:2010-10-04 15:41:04

标签: wpf silverlight button controltemplate visualstatemanager

使用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中检查时)是相同的(Button - &gt; ContentPresenter - &gt; TextBlock),但需要注意的是,在第一个版本中创建的文本块将其数据上下文设置为“Hello,World” ,而第二个版本中的文本块仅具有其文本属性集。我假设这与控件创建的顺序有关(按钮创建TextBlock的第一个版本,在第二个版本中可能首先创建文本块?真的不确定)。

在研究这个过程中,我看到了一些在Silverlight中有效的解决方案(比如用ContentControl替换ContentPresenter),但这在WPF中无效(程序实际崩溃)。

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

我真的很感激任何帮助,任何人都可以给予的建议。

3 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:4)

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

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

按钮的默认模板会阻止您。正如您所注意到的,button是一个无内容控件;这意味着设计师可以在其中推送一些随机的视觉对象。无内容强制前景属性成为模板的成员而不是控件,因为没有保证组件来设置颜色。这就是为什么里面有一个内容演示者。

所以现在你有两个选择。 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"/> 

注意Textblock的Text属性如何绑定到按钮的内容。如果需要绑定到除文本之外的任何其他内容,这将成为一个问题。 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>

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

我希望这会有所帮助。

度过美好的一天。

-Jeremiah

答案 2 :(得分:0)

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

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

您必须在创建按钮模板中插入此代码 但是当我在按钮内容中添加文本并且如果你想在其上添加其他控件时我使用这个使用正常的样式