В шаблоне управления кнопки, как я могу установить цвет текста? - PullRequest
10 голосов
/ 04 октября 2010

Используя 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 -> ContentPresenter -> TextBlock), с оговоркой, что текстовый блок, созданный в 1-й версии, имеет свой контекст данных, установленный в «Hello, World», тогда как текстовый блок во второй версии просто имеет свой набор свойств текста. Я предполагаю, что это как-то связано с порядком создания элемента управления (в первой версии кнопка создает текстовый блок, во второй версии текстовый блок может быть создан первым? Действительно не уверен в этом).

В ходе исследования этого я видел несколько решений, которые работают в Silverlight (например, замена ContentPresenter на ContentControl), но не будут работать в WPF (программа действительно аварийно завершает работу).

Поскольку это находится в шаблоне элемента управления кнопки, и я хотел бы использовать VSM, если это возможно, я думаю, что это также исключает явное изменение собственного свойства Foreground кнопки (я не знаю, как бы я получил к нему доступ изнутри). шаблон?)

Буду очень признателен за любую помощь, совет, который может дать любой.

Ответы [ 3 ]

5 голосов
/ 05 октября 2010

Итак, после некоторых размышлений, решение, которое я в конечном итоге нашел, заключается в добавлении присоединенного свойства к элементу ContentPresenter в шаблоне элемента управления кнопки. Вложенное свойство принимает Color, и когда установлено, проверяет визуальное дерево предъявителя контента для любых TextBlocks, и в свою очередь устанавливает их свойства Foreground равным переданному значению. Это, очевидно, может быть расширено / сделано для обработки дополнительных элементов, но пока это работает за то, что мне нужно.

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}" />
4 голосов
/ 05 октября 2010

Это сложный вопрос. Передний план - это свойство кнопки, которое передается элементам управления, которые создает предъявитель контента. Поскольку это свойство зависимости, а не свойство элементов управления, доступных в шаблоне, его сложно анимировать с использованием чистого 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"/> 

Обратите внимание, как свойства Text в текстовых блоках связаны с содержимым кнопки. Это становится проблемой, если когда-либо возникает необходимость связать что-либо, кроме текста. TextBlocks просто не может отображать ничего, кроме буквенно-цифровых значений.

Также обратите внимание, что по умолчанию я свернул видимость на 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 был бы одинаковым, у меня были бы разные текстовые блоки для разных визуальных состояний.

Надеюсь, это поможет.

Хорошего дня.

* 1030 Иеремия *

0 голосов
/ 20 ноября 2015

Я использую это в моем шаблоне, и он отлично работает

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

Вы должны вставить этот код при создании шаблона кнопки но я использую это, когда помещаю текст в содержимое кнопки, и если вы хотите добавить другие элементы управления, используйте обычный стиль

...