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

Используя 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 кнопки (я не знаю, как бы я получил к нему доступ из шаблона? )

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

 Denis04 окт. 2010 г., 22:01
Я удивлен, что это сработало, учитывая, что TextElement не имеет ничего общего с TextBlock.

Ответы на вопрос(3)

Решение Вопроса

после некоторых размышлений, решение, к которому я в конечном итоге пришел, заключается в добавлении присоединенного свойства к элементу 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}" />
 Jordan0Day07 окт. 2010 г., 16:40
Мне пришлось использовать ObjectAnimationUsingKeyFrames, а не ColorAnimation или что-то подобное.
 Jeremiah05 окт. 2010 г., 22:41
Это тоже хорошее решение. Мне любопытно, какие анимационные раскадровки вы сможете использовать, поскольку вы привязываетесь к StaticResource.

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

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

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

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

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

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

Иеремия

 Jordan0Day05 окт. 2010 г., 19:38
Привет, Джеремия, это действительно хорошее решение, которое хорошо работает в ситуации, когда кнопка будет содержать только текст. В моем случае я бы хотел, чтобы решение было более общим, чтобы у любого потомка в текстовом блоке были установлены цвета переднего плана. (Любопытно отметить, что именно так работает Button, когда вы явно устанавливаете его свойство Foreground - любые дочерние TextBlocks получают соответствующий цвет, независимо от того, насколько они вложены в панели / элементы управления, они находятся в содержимом кнопки. Я подробно описал решение В конечном итоге я пришел к использованию прикрепленных свойств в этой теме.

Ваш ответ на вопрос