Jor*_*Day 10 silverlight wpf button visualstatemanager controltemplate
使用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>
Run Code Online (Sandbox Code Playgroud)
由于这是一个常规旧按钮的模板,我知道不能保证里面甚至有一个文本块,起初我不确定这是否可能.奇怪的是,如果按钮声明如下,文本颜色确实会改变:
<Button Content="Hello, World!" />
Run Code Online (Sandbox Code Playgroud)
但如果按钮声明如下,它不会改变:
<Button>
<TextBlock Text="Hello, World!" /> <!-- Same result with <TextBlock>Hello, World </TextBlock> -->
</Button>
Run Code Online (Sandbox Code Playgroud)
即使可视树(在snoop中检查时)是相同的(Button - > ContentPresenter - > TextBlock),但需要注意的是,在第一个版本中创建的文本块将其数据上下文设置为"Hello,World",而文本块在第二个版本只有其text属性集.我假设这与控件创建的顺序有关(按钮创建TextBlock的第一个版本,在第二个版本中可能首先创建文本块?真的不确定).
在研究这个过程中,我已经看到了一些在Silverlight中工作的解决方案(比如用ContentControl替换ContentPresenter),但这在WPF中不起作用(程序实际崩溃).
由于这是在按钮的控件模板中,并且我想尽可能使用VSM,我认为这也排除了显式更改Button自己的Foreground属性(我不知道如何从模板中访问它? )
我真的很感激任何帮助,任何人都可以给予的建议.
这是一个棘手的问题。前景是传递给内容展示者创建的控件的按钮的属性。因为它是一个依赖属性而不是模板中可用控件的属性,所以很难使用纯 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"/>
Run Code Online (Sandbox Code Playgroud)
请注意 Textblocks 的 Text 属性如何绑定到按钮的内容。如果需要绑定到文本以外的任何内容,这就会成为一个问题。TextBlocks 只能显示 AlphaNumeric 值。
另请注意,默认情况下我折叠了 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>
Run Code Online (Sandbox Code Playgroud)
感觉就像一个黑客,我可能不会在很多地方实现这个按钮。我想制作我自己的具有良好 DependencyProperties 的 Button 来绑定。HighlightForeground 和 Text 各一个。如果有人试图将内容设置为 AlphaNumeric 值以外的任何值,我还想隐藏或至少抛出异常。但是,XAML 是相同的,对于不同的视觉状态,我会有不同的文本块。
我希望这有帮助。
祝你有美好的一天。
-耶利米
所以经过一些思考之后,我最终得到的解决方案是在按钮的控件模板中为ContentPresenter元素添加一个附加属性.附加属性接受一个颜色,并在设置时检查任何TextBlocks的内容呈现器的可视树,然后将其前景属性设置为传入的值.这显然可以扩展/制作以处理其他元素但是现在它可以工作因为我需要的.
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我修改了模板,如下所示:
<ContentPresenter x:Name="contentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
local:ButtonAttachedProperties.ButtonTextForeground="{StaticResource ButtonTextNormalColor}" />
Run Code Online (Sandbox Code Playgroud)