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