В шаблоне управления кнопками, как я могу установить цвет содержащегося текста?

Используя Silverlight 4 и WPF 4, я пытаюсь создать стиль кнопки, который изменяет цвет текста любого содержащегося текста при нажатии кнопки mouseover'd. Поскольку я пытаюсь сделать это совместимым как с 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", тогда как текстовый блок во второй версии просто имеет свой набор свойств текста. Я предполагаю, что это имеет какое-то отношение к порядку создания управления (первая версия кнопки создает TextBlock, во второй версии текстовый блок может быть создан первым? Действительно не уверен в этом).

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

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

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

Ответ 1

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

Ответ 2

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

MSDN Имеет пару образцов о том, как изменить цвет переднего плана. Однако это не похоже на то, что вы хотите сделать эту работу из кода. (Http://msdn.microsoft.com/en-us/library/system.windows.controls.button(VS.95).aspx)

Шаблон по умолчанию кнопки удерживает вас. Как вы заметили, кнопка - это контент-контроль; что означает, что дизайнер может нанести некоторый случайный визуальный объект внутри него. Будучи без содержания, свойство foreground является элементом шаблона, а не элементом управления, потому что для установки цвета не существует гарантированного компонента. Вот почему внутри него есть ведущий контента.

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

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

Проведите отличный день.

Иеремия

Ответ 3

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

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

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