In a button's control template, how can I set the color of contained text?

后端 未结 3 1177
半阙折子戏
半阙折子戏 2021-02-20 10:37

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

相关标签:
3条回答
  • 2021-02-20 10:54

    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.

    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}" />
    
    0 讨论(0)
  • 2021-02-20 10:59

    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.

    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)

    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.

    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)

    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.

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

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

    I hope this helps.

    Have a great day.

    -Jeremiah

    0 讨论(0)
  • 2021-02-20 11:11

    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

    0 讨论(0)
提交回复
热议问题