十一、WPF的命令
WPF中的命令路由与事件路由是两个很让初学者头痛的概念,对于命令路由可以理解为,系统(WPF)定义了一系列的操作,在应用程序中可以直接使用。例如,定义一系列菜单,执行对窗体中文本框的复制、剪切、粘贴操作,简单地可以这样做:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="23" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu Grid.Row="0" Grid.Column="0">
<MenuItem Header="Edit">
<MenuItem x:Name="menuCopy" Header="Copy"
Command="ApplicationCommands.Copy" />
<MenuItem x:Name="menuCut" Header="Cut"
Command="ApplicationCommands.Cut" />
<MenuItem x:Name="menuPaste" Header="Paste"
Command="ApplicationCommands.Paste" />
</MenuItem>
</Menu>
<TextBox Grid.Row="1" Grid.Column="0" x:Name="mainText"
TextWrapping="Wrap" AcceptsReturn="True" />
</Grid>
WPF 中的路由命令模型可以分为四个主要概念:命令、命令源、命令目标以及命令绑定:
- 命令是要执行的操作。在本例中命令为ApplicationCommands.Copy、Cut、Paste
- 命令源是调用命令的对象。 在本例中命令源为三个MenuItem控件
- 命令目标是在其上执行命令的对象。 在本例中命令目标是mainText这个TextBox文本框
- 命令绑定是将命令逻辑映射到命令的对象。 命令绑定到系统定义的对于文本框的“复制”、“剪切”、“粘贴”操作、
其四者的关系如下图所示:
一、命令
WPF 中的命令是通过实现 ICommand 接口来创建的。ICommand 公开两个方法(Execute 和 CanExecute)和一个事件 (CanExecuteChanged)。Execute 执行与命令关联的操作。CanExecute 确定是否可以在当前命令目标上执行命令。如果集中管理命令操作的命令管理器检测到命令源中发生了更改,此更改可能使得已引发但尚未由命令绑定执行的命令无效,则将引发 CanExecuteChanged。ICommand 的 WPF 实现是 RoutedCommand 类。
RoutedCommand 上的 Execute 方法在命令目标上引发 PreviewExecuted 和 Executed 事件。RoutedCommand 上的 CanExecute 方法在命令目标上引发 CanExecute 和 PreviewCanExecute 事件。这些事件沿元素树以隧道和冒泡形式传递,直到遇到具有该特定命令的 CommandBinding 的对象。
WPF 提供了一组常用的路由命令,这组命令分布在几个类中:MediaCommands、ApplicationCommands、NavigationCommands、ComponentCommands 和 EditingCommands。这些类仅包含 RoutedCommand 对象,而不包含命令的实现逻辑。实现逻辑由在其上执行命令的对象负责。
WPF已封装的命令类有:
命令类 | 示例命令 |
---|---|
ApplicationCommands | Close、Cut、Copy、Paste、Save、Print |
NavigationCommands | BrowseForward、BrowseBack、Zoom、Search |
EditingCommands | AlignXXX、MoveXXX、SelectXXX |
MediaCommands | Play、Pause、NextTrack、IncreaseVolume、Record、Stop |
ComponentCommands | MoveXXX、SelectXXX、ScrollXXX、ExtendSelectionXXX |
XXX 代表操作的集合,例如 MoveNext 和 MovePrevious。其中ApplicationCommands为默认的命令类,引用其中的命令时可以省略ApplicationCommands。
二、命令源
命令源是调用命令的对象。例如,MenuItem、Button 和 KeyGesture 就是命令源。
WPF 中的命令源通常实现 ICommandSource 接口。
ICommandSource 公开三个属性:Command、CommandTarget 和 CommandParameter:
- Command 是在调用命令源时执行的命令。
- CommandTarget 是要在其上执行命令的对象。值得注意的是,在 WPF 中,ICommandSource 上的 CommandTarget 属性只有在 ICommand 是 RoutedCommand 时才适用。如果在 ICommandSource 上设置了 CommandTarget,而对应的命令不是 RoutedCommand,将会忽略命令目标。如果未设置 CommandTarget,则具有键盘焦点的元素将是命令目标。
- CommandParameter 是用户定义的数据类型,用于将信息传递到实现命令的处理程序。
实现 ICommandSource 的 WPF 类包括:ButtonBase、MenuItem、Hyperlink 以及 InputBinding。ButtonBase、MenuItem 和 Hyperlink 在被单击时调用命令,InputBinding 在与之关联的 InputGesture 执行时调用命令。
ButtonBase等直接使用控件的Command属性绑定命令:
<Button Command="ApplicationCommands.Copy" />
而InputBinding使用KeyBinding或MouseBinding绑定特定的输入手势到某一命令上:
例如在Window上注册Ctrl+F2快捷键到ApplicationCommands.Open上:
<KeyBinding Command="ApplicationCommands.Open" Key="F2" Modifiers="Control" />
三、命令目标
命令目标是在其上执行命令的元素。对于 RoutedCommand 而言,命令目标是 Executed 和 CanExecute 的路由的起始元素。前面已提到,在 WPF 中,ICommandSource 上的 CommandTarget 属性只有在 ICommand 是一个 RoutedCommand 时才适用。如果在 ICommandSource 上设置了 CommandTarget,而对应的命令不是 RoutedCommand,将会忽略命令目标。
命令源可以显式设置命令目标。如果未定义命令目标,则具有键盘焦点的元素将用作命令目标。将具有键盘焦点的元素用作命令目标的一个好处是,应用程序开发人员可以使用同一个命令源在多个目标上调用命令,而不必跟踪命令目标。例如,如果 MenuItem 在具有一个 TextBox 控件和一个 PasswordBox 控件的应用程序中调用“粘贴”命令,则目标既可以是 TextBox,也可以是 PasswordBox,具体取决于哪个控件具有键盘焦点。
如将Paste命令设置到mainText控件上:
<MenuItem x:Name="menuPaste" Header="Paste"
Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=txtMain}"/>
四、命令绑定
CommandBinding 将一个命令与实现该命令的事件处理程序关联。
CommandBinding 类包含一个 Command 属性以及 PreviewExecuted、Executed、PreviewCanExecute 和 CanExecute 事件。
Command 是 CommandBinding 要与之关联的命令。附加到 PreviewExecuted 和 Executed 事件的事件处理程序实现命令逻辑。附加到 PreviewCanExecute 和 CanExecute 事件的事件处理程序确定命令是否可以在当前命令目标上执行。
- CanExecute事件和PreviewCanExecute事件,通过其EventArgs参数中的CanExecute属性,设置其命令是否可以执行,并且系统会自动的和命令目标的某些特定属性进行绑定,例如Button、MenuItem等在CanExecute属性的值设为False时,会“灰化”,不可用
- Executed事件和PreviewExecuted事件的代码,是执行命令的真正代码。
例如,将ApplicationCommans.Save绑定到菜单栏的Save菜单项中,并在文本框中没有任何文本时不可用:
<Window x:Class="InputCommandAndFocus.WinCommandDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WinCommandDemo" Height="300" Width="300" Focusable="True">
<Window.InputBindings>
<KeyBinding Command="ApplicationCommands.Save"
Key="F3" Modifiers="Control" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Save"
CanExecute="CommandBinding_Save_CanExecute"
Executed="CommandBinding_Save_Executed" />
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="23" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu Grid.Row="0" Grid.Column="0">
<MenuItem Header="File">
<MenuItem x:Name="menuSave" Header="Save"
Command="ApplicationCommands.Save" />
</MenuItem>
</Menu>
<TextBox Grid.Row="1" Grid.Column="0" x:Name="mainText"
TextWrapping="Wrap" AcceptsReturn="True" />
</Grid>
</Window>
private void CommandBinding_Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (mainText.Text == string.Empty)
{
// 如果文本框中没有任何文本,则不可以保存
e.CanExecute = false;
}
else
{
e.CanExecute = true;
}
}
private void CommandBinding_Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
// 保存文件对话框
SaveFileDialog save = new SaveFileDialog();
save.Filter = "文本文件|*.txt|所有文件|*.*";
bool? result = save.ShowDialog();
if (result.Value)
{
// 执行保存文件操作
}
}
十二、WPF资源
一、什么是资源
通常使用 WPF 资源作为重用通常定义的对象和值的简单方法。例如定义一种可以复用的单色的Brush对象,按钮的背景及矩形的填充颜色均使用此Brush:
<Window x:Class="WPFResource.WinBasicResource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Basic Resource" Height="200" Width="300">
<Window.Resources>
<SolidColorBrush x:Key="myBrush" Color="Gold" />
</Window.Resources>
<StackPanel>
<Button Margin="5" Content="Sample Button" Background="{StaticResource myBrush}" />
<Rectangle Margin="5" Width="100" Height="100" Fill="{StaticResource myBrush}" />
</StackPanel>
</Window>
在WPF中资源通常用作“样式”(Style)、样式模板、数据模板等。
二、资源的定义及XAML中引用
资源可以定义在以下几个位置:
- 应用程序级资源:定义在App.xaml文件中,作为整个应用程序共享的资源存在
在App.xaml文件中定义:
<Application x:Class="WPFResource.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<Application.Resources>
<SolidColorBrush Color="Gold" x:Key="myGoldBrush" />
</Application.Resources>
</Application>
在ApplicationResourceDemo.xaml文件(窗体)中使用App.xaml中定义的Resource
<Window x:Class="WPFResource.ApplicationResourceDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Application Resource Demo" Height="300" Width="300">
<StackPanel>
<Button Margin="5" Background="{StaticResource myGoldBrush}">Sample Button</Button>
</StackPanel>
</Window>
- 窗体级资源:定义在Window或Page中,作为一个窗体或页面共享的资源存在
<Window x:Class="WPFResource.WindowResourceDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowResourceDemo" Height="300" Width="300">
<Window.Resources>
<SolidColorBrush x:Key="myRedBrush" Color="Red" />
</Window.Resources>
<StackPanel>
<Button Margin="5" Background="{StaticResource myRedBrush}">Sample Button</Button>
</StackPanel>
</Window>
- 文件级资源:定义在资源字典的XAML文件中,再引用
在Visual Studio的WPF应用程序项目中,添加“资源字典(Resource Dictionary)”类型的项
)
在其XAML文件中定义:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="myWhiteBrush" Color="White" />
</ResourceDictionary>
在FileResourceDemo.xaml文件(窗体)中,将其注册为窗体级的资源,并引用
<Window x:Class="WPFResource.FileResourceDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FileResourceDemo" Height="300" Width="300">
<Window.Resources>
<ResourceDictionary Source="MyResourceDictionary.xaml" />
</Window.Resources>
<StackPanel>
<Button Margin="5" Background="{StaticResource myWhiteBrush}">Sample Button</Button>
</StackPanel>
</Window>
- 对象(控件)级资源:定义在某个ContentControl中,作为其子容器、子控件共享的资源在Button中定义一个资源,供Button内的Content控件使用
<Window x:Class="WPFResource.ControlResourceDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControlResourceDemo" Height="300" Width="300">
<StackPanel>
<Button Margin="5">
<Button.Resources>
<SolidColorBrush x:Key="myGreenBrush" Color="Green" />
</Button.Resources>
<Button.Content>
<TextBlock Text="Sample Text" Background="{StaticResource myGreenBrush}" />
</Button.Content>
</Button>
</StackPanel>
</Window>
三、XAML解析资源的顺序
在XAML中解析资源按照由引用资源的控件向外层容器依次调用资源。例如在在应用程序级别、窗体级别及对象级别分为定义x:Key相的同资源:
在App.xaml文件中:
<Application x:Class="WPFResource.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MultiResourceReference.xaml">
<Application.Resources>
<!-- 应用程序级资源 -->
<SolidColorBrush Color="Gold" x:Key="myGoldBrush" />
<SolidColorBrush Color="Blue" x:Key="myBrush" />
</Application.Resources>
</Application>
在窗体的XAML文件中:
<Window x:Class="WPFResource.MultiResourceReference"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MultiResourceReference" Height="265" Width="300">
<Window.Resources>
<!-- 窗体级资源 -->
<SolidColorBrush Color="White" x:Key="myWhiteBrush" />
<SolidColorBrush Color="Green" x:Key="myBrush" />
</Window.Resources>
<StackPanel>
<!-- 使用应用程序级定义的资源 -->
<Button Margin="5" Content="Sample Button" Background="{StaticResource myGoldBrush}" />
<!-- 使用窗体级定义的资源 -->
<Button Margin="5" Content="Sample Button" Background="{StaticResource myWhiteBrush}" />
<!-- 窗体级资源的值覆盖应用程序级资源的值 -->
<Button Margin="5" Content="Sample Button" Background="{StaticResource myBrush}" />
<StackPanel Background="#FF999999">
<StackPanel.Resources>
<!-- 对象级资源 -->
<SolidColorBrush Color="Yellow" x:Key="myYellowBrush" />
<SolidColorBrush Color="Red" x:Key="myBrush" />
</StackPanel.Resources>
<!-- 使用应用程序级定义的资源 -->
<Button Margin="5" Content="Sample Button" Background="{StaticResource myGoldBrush}" />
<!-- 使用窗体级定义的资源 -->
<Button Margin="5" Content="Sample Button" Background="{StaticResource myWhiteBrush}" />
<!-- 使用对象级定义的资源 -->
<Button Margin="5" Content="Sample Button" Background="{StaticResource myYellowBrush}" />
<!-- 使用对象级定义的资源覆盖窗体级、应用程序级定义的资源 -->
<Button Margin="5" Content="Sample Button" Background="{StaticResource myBrush}" />
</StackPanel>
</StackPanel>
</Window>
四、静态资源(StaticResource)和动态资源(DynamicResource)
资源可以作为静态资源或动态资源进行引用。这是通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展完成的。
通常来说,不需要在运行时更改的资源使用静态资源;而需要在运行时更改的资源使用动态资源。动态资源需要使用的系统开销大于静态资源的系统开销。例如以下的例子:
private void Button_Click(object sender, RoutedEventArgs e)
{
SolidColorBrush brush = new SolidColorBrush(Colors.Green);
this.Resources["ButtonBrush"] = brush;
}
以上的例子在运行时显示如下:
而点击“Change Button Resource”按钮后,显示的结果为:
从程序执行的结果来看,我们可以得到如下的结论:
- 静态资源引用是从控件所在的容器开始依次向上查找的,而动态资源的引用是从控件开始向上查找的(即控件的资源覆盖其父容器的同名资源)
- 更改资源时,动态引用的控件样式发生变化(即“Dynamic Resource Button A”发生变化)
如果要更改“Dynamic Resource Button B”的背景,需要在按钮的事件中添加以下代码(将“Dynamic Resource Button B”的控件的x:Name设置为“btn4”)
SolidColorBrush brushB = new SolidColorBrush(Colors.Blue);
this.btn4.Resources["ButtonBrush"] = brushB;
执行的结果如下:
静态资源引用最适合于以下情况:
- 您的应用程序设计几乎将所有的应用程序资源集中到页或应用程序级别的资源字典中。静态资源引用不会基于运行时行为(例如重新加载页)进行重新求值,因此,根据您的资源和应用程序设计避免大量不必要的动态资源引用,这样可以提高性能。
- 您正在设置不在 DependencyObject 或 Freezable 上的属性的值。
- 您正在创建将编译为 DLL 并打包为应用程序的一部分或在应用程序之间共享的资源字典。
- 您正在为自定义控件创建一个主题,并定义在主题中使用的资源。对于这种情况,通常不需要动态资源引用查找行为,而需要静态资源引用行为,以使该查找可预测并且独立于该主题。使用动态资源引用时,即使是主题中的引用也会直到运行时才进行求值,并且在应用主题时,某个本地元素有可能会重新定义您的主题试图引用的键,并且本地元素在查找中会位于主题本身之前。如果发生该情况,主题将不会按预期方式运行。
- 您正在使用资源来设置大量依赖项属性。依赖项属性具有由属性系统启用的有效值缓存功能,因此,如果您为可以在加载时求值的依赖项属性提供值,该依赖项属性将不必查看重新求值的表达式,并且可以返回最后一个有效值。该方法具有性能优势。
- 您需要为所有使用者更改基础资源,或者需要通过使用 x:Shared 属性为每个使用者维护独立的可写实例。
动态资源最适合于以下情况:
- 资源的值取决于直到运行时才知道的情况。这包括系统资源,或用户可设置的资源。例如,您可以创建引用由 SystemColors、SystemFonts 或 SystemParameters 公开的系统属性的 setter 值。这些值是真正动态的,因为它们最终来自于用户和操作系统的运行时环境。您还可以使用可以更改的应用程序级别的主题,在此情况下,页级别的资源访问还必须捕获更改。
- 您正在为自定义控件创建或引用主题样式。
- 您希望在应用程序生存期调整 ResourceDictionary 的内容。
- 您有一个存在依存关系的复杂资源结构,在这种情况下,可能需要前向引用。静态资源引用不支持前向引用,但动态资源引用支持,因为资源直到运行时才需要进行求值,因此,前向引用不是一个相关概念。
- 从编译或工作集角度来说,您引用的资源特别大,并且加载页时可能无法立即使用该资源。静态资源引用始终在加载页时从 XAML 加载;而动态资源引用直到实际使用时才会加载。
- 您要创建的样式的 setter 值可能来自受主题或其他用户设置影响的其他值。
- 您正在将资源应用到元素,而在应用程序生存期中可能会在逻辑树中重新设置该元素的父级。更改此父级还可能会更改资源查找范围,因此,如果您希望基于新范围对重新设置了父级的元素的资源进行重新求值,请始终使用动态资源引用。
五、不同类型的资源
1、程序集资源。这种常见于将图片设定到程序集中,做为程序集的资源。
程序集资源在定义时,将文件复制到解决方案-项目所在的目录或其子目录中,并将文件的属性中的Build Action设置为Resource。(注意,WPF不支持项目属性中的资源)
然后在XAML文件中使用如Image的Source属性,指定到此文件:
<StackPanel Background="#FFC7DAFF">
<TextBlock Margin="5" Text="Assembly Resource" />
<Image Margin="5" Source="Image/Users.png" Width="32" Height="32" />
<Button Margin="5">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Margin="5" Source="Image/Users.png" Width="32" Height="32" />
<TextBlock Text="My Button" VerticalAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
此项目编译后,在Assembly中将封装该图片文件。此种方法适用于较小的资源。
2、对象资源
除刚刚我们使用的图片做为程序集资源外,前面例子中所使用的资源均是对象资源。系统对于对象资源使用ResouceDictionary这个字典集合处理,其Key对应即x:Key声明的键,Value对应资源。我们前面使用的都是SolidColorBrush对象,再例如使用字符串及ImageBrush对象做为资源:
<StackPanel Background="#FFE6FDC8">
<StackPanel.Resources>
<c:String x:Key="strMessage">My String Value.</c:String>
<ImageBrush x:Key="imgBrush" ImageSource="Image/Users.png"
ViewportUnits="Absolute" Viewport="0 0 32 32"/>
</StackPanel.Resources>
<TextBlock Margin="5" Text="Object Resource" />
<TextBox Margin="5" Text="{StaticResource strMessage}" />
<Button Margin="5" Height="32" HorizontalContentAlignment="Right"
Content="{StaticResource strMessage}"
Background="{StaticResource imgBrush}" Width="125" />
</StackPanel>
程序执行结果为:
十三、WPF样式(Style)与模板(Template)
一、WPF样式
类似于Web应用程序中的CSS,在WPF中可以为控件定义统一的样式(Style)。样式属于资源的一种,例如为Button定义统一的背景颜色和字体:
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Margin" Value="5" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</Window.Resources>
<StackPanel>
<Button>Button A</Button>
<Button Foreground="Red" Background="White">Button B</Button>
</StackPanel>
从执行的结果上来看:
- 在Style中定义的属性及值,影响到Window中的所有类型为Button的控件的样式
- 在Button中可以新定义其他属性(如Foreground),覆盖Style中的定义(Background)
这种样式,类似于CSS中的类型选择器,为某种类型定义样式。
此外还可以在Style中加入x:Key属性,做为特定的样式(注意,这种也需要定义TargetType);定义时还可以基于已定义的某种样式,例如,基于刚才的Button的样式,更改字体的大小及文本的前景及背景颜色:
<Window.Resources>
<Style
TargetType="Button">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Margin" Value="5" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
<Style
TargetType="Button"
x:Key="ButtonStyleA"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="Green" />
<Setter Property="Foreground" Value="Yellow" />
<Setter Property="FontSize" Value="28"/>
</Style>
</Window.Resources>
<StackPanel>
<Button>Button A</Button>
<Button Foreground="Red" Background="White">Button B</Button>
<Button Style="{StaticResource ButtonStyleA}">Button C</Button>
<Button Style="{StaticResource ButtonStyleA}" Content="Button D">
<Button.Foreground>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="#FFFFFF" />
<GradientStop Offset="1.0" Color="#0000FF" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Foreground>
</Button>
</StackPanel>
二、控件模板(ControlTemplate)
当使用一个控件时,如果控件的属性、方法、事件满足程序的需求,但控件的外观不满足要求的时候,除了自定义控件这种方法外,我们还可以通过使用“控件模板”的方式更改控件的外观。例如定义一个圆形的按钮:
<Window.Resources>
<Style TargetType="Button" x:Key="ButtonStyle">
<!--设置按钮的默认的样式-->
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="#fff" />
<GradientStop Offset="1.0" Color="#009" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<!--设置按钮的模板-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"/>
<ContentPresenter
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<Button Margin="5" Style="{StaticResource ButtonStyle}"
Width="100" Height="100"
Content="My Button">
</Button>
<Button Margin="5" Width="200">Common Button</Button>
</StackPanel>
三、触发器
值得注意的是,这个时候,对于此按钮,无论是否获得焦点、鼠标是处于其上方,显示的外观均是相同的,如果要定义以上的一些效果,可以使用触发器来实现。
Style、ControlTemplate 和 DataTemplate 都具有 Triggers 属性,该属性可以包含一组触发器。某个属性值更改时,或某个事件引发时,触发器会相应地设置属性或启动操作(如动画操作)。
触发器包含以下几种:
- 属性触发器
- EventTrigger 和 Storyboard
- MultiTrigger、DataTrigger 和 MultiDataTrigger
我们这里可以使用属性触发器来实现:
例如,在ControlTemplate中(即上段代码28行前插入以下代码):
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!--鼠标在上移动时-->
<Setter Property="Foreground" Value="Yellow" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<!--控件获得键盘焦点时-->
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
当按键获得键盘焦点时:
鼠标在其上时:
十四、WPF数据绑定概述
WPF数据绑定为应用程序提供了一种表示数据和与数据交互的简单而又一致的方法。元素能够以公共语言运行库 (CLR) 对象和 XML 的形式绑定到各种数据源中的数据。
一、数据绑定的基本概念
数据绑定涉及到两个方面:一个是绑定源,再一个是绑定目标。绑定源即控件绑定所使用的源数据,绑定目标即数据显示的控件。
1、对于绑定源,在WPF可以是以下四种:
- CLR对象:可以绑定到CLR类的公开的属性、子属性、索引器上
- ADO.Net对象:例如DataTable、DataView等
- XML文件:使用XPath进行解析
- DependencyObject:绑定到其依赖项属性上,即控件绑定控件
2、对于绑定目标,必须是WPF中的DependencyObject,将数据绑定到其依赖项属性上。
二、绑定的基本方式
WPF数据绑定源方式
1、Source
2、DataContext
3、RelativeSource 根据视图属逐一往上搜索数据源
根据数据流的方向,WPF中的数据绑定分为以下四种:
- OneWay 绑定导致对源属性的更改会自动更新目标属性,但是对目标属性的更改不会传播回源属性。此绑定类型适用于绑定的控件为隐式只读控件的情况。例如,您可能绑定到如股票行情自动收录器这样的源,或许目标属性没有用于进行更改的控件接口(如表的数据绑定背景色)。如果无需监视目标属性的更改,则使用 OneWay 绑定模式可避免 TwoWay 绑定模式的系统开销。
- TwoWay 绑定导致对源属性的更改会自动更新目标属性,而对目标属性的更改也会自动更新源属性。此绑定类型适用于可编辑窗体或其他完全交互式 UI 方案。大多数属性都默认为 OneWay 绑定,但是一些依赖项属性(通常为用户可编辑的控件的属性,如 TextBox 的 Text 属性和 CheckBox 的 IsChecked 属性)默认为 TwoWay 绑定。确定依赖项属性绑定在默认情况下是单向还是双向的编程方法是:使用 GetMetadata 获取属性的属性元数据,然后检查 BindsTwoWayByDefault 属性的布尔值。
- OneWayToSource 与 OneWay 绑定相反;它在