在使用WPF编写客户端代码时,咱们会在VM下解耦业务逻辑,而剩下与功能无关的内容好比动画、视觉效果,布局切换等等在数量和复杂性上都超过了业务代码。而如何更好的简化这些编码,WPF设计人员使用了Style和Behavior来帮助咱们构建一致性、组织性好的代码。express
这一章的目的是理解咱们使用行为和资源的目标。使用这2个内容使咱们建立封装一些通用用户界面功能的行为。好比启动故事板,加入重力的动画效果,咱们要把思惟给打开,咱们作的东西是为了通用,而不是为了业务,由于业务在这个时刻只存在于VM中。(即便我的能力所限,或者实际状况所限,V下面仍是有业务代码。,可是咱们心中要有这个自信,我作WPF开发,那么在将来我也能设计出来堪比WPF这种优秀的的框架,若是没有自信和信心,别人一说就受到了打击,那么什么时间才能成为大佬,别说成为大佬了,可能本身慢慢的就放弃了把),跑题了,简单来讲就是咱们使用行为和样式设计出来能够添加到各类控件的通用效果。这里不想考虑更多的内容,好比自定义控件。框架
先讲样式和触发器,咱们设计窗体只有暗色风格,在此风格下的按钮都是黑底白字。ide
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" mc:Ignorable="d" Topmost="True" Background="#000000" Title="MainWindow" Height="450" Width="800"> <Grid> <StackPanel Height="30" Orientation="Horizontal"> <Button Margin="3" Content="我是按钮A" Foreground="#F5FFFA" Background="#696969" BorderBrush="#2f4f4f"/> <Button Margin="3" Content="我是按钮B" Foreground="#F5FFFA" Background="#696969" BorderBrush="#2f4f4f"/> </StackPanel> </Grid> </Window>
实际效果以下图:布局
咱们看到若是这里有N个按钮,那么全部的代码上都要写本身属性对应的样式。咱们使用资源能够规划一些统一的样式。而统一的样式,就被咱们放到了资源里面。咱们一点一点改进咱们的代码,修改代码以下。学习
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" mc:Ignorable="d" Topmost="True" Background="#000000" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style TargetType="Button"> <Setter Property="Foreground" Value="#F5FFFA" /> <Setter Property="Background" Value="#696969"/> <Setter Property="BorderBrush" Value="#2f4f4f"/> <Setter Property="Margin" Value="3"/> </Style> </Window.Resources> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <Button Content="我是按钮A"/> <Button Content="我是按钮B"/> </StackPanel> </Grid> </Window>
咱们看到了咱们在Window节点的Resources下添加了一个Style,而且设置了TargetType为Button。在Button元素内,我删除了对应的代码。这个时候咱们启动程序。发现程序的效果是同样的。那么这个时候咱们在添加其余按钮,就自动使用了这个样式。字体
若是在使用Style的时候,不指定Key,那么全部加载了资源的元素都会默认使用这个资源。咱们给Style指定一个Key,并设置一个Button的Style观察效果,代码以下:动画
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" mc:Ignorable="d" Topmost="True" Background="#000000" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style x:Key="DarkButtonStyle" TargetType="Button"> <Setter Property="Foreground" Value="#F5FFFA" /> <Setter Property="Background" Value="#696969"/> <Setter Property="BorderBrush" Value="#2f4f4f"/> <Setter Property="Margin" Value="3"/> </Style> </Window.Resources> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <Button Style="{StaticResource DarkButtonStyle}" Content="我是按钮A"/> <Button Content="我是按钮B"/> </StackPanel> </Grid> </Window>
·编码
咱们发现没有样式添加了Key以后,没有缺乏Key的TargetType等于Button的资源后,没有引用Style的Button被修改回系统默认的了。。而咱们使用Style={StaticResource }资源的样式的Button外观就变成了咱们资源中定义的。spa
样式中还有一个关键的点,是样式的继承。从一个样式中继承公共的部分后,实现本身特殊部分的样式,好比咱们在继承DarkButtonStyle的样式实现一个警告的按钮的样式。假设统一的警告按钮风格是字体会更粗。咱们须要添加一个新的样式继承自DarkButtonStyle并FontWeight属性,同时使警告的控件引用该样式,代码以下:设计
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" Background="#000000" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style x:Key="DarkButtonStyle" TargetType="Button"> <Setter Property="Foreground" Value="#F5FFFA" /> <Setter Property="Background" Value="#696969"/> <Setter Property="BorderBrush" Value="#2f4f4f"/> <Setter Property="Margin" Value="3"/> </Style> <Style x:Key="WarningDarkButtonStyle" TargetType="Button" BasedOn="{StaticResource DarkButtonStyle}"> <Setter Property="FontWeight" Value="Bold"/> </Style> </Window.Resources> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <Button Style="{StaticResource DarkButtonStyle}" Content="我是按钮A"/> <Button Content="我是按钮B" Style="{StaticResource WarningDarkButtonStyle}"/> </StackPanel> </Grid> </Window>
这样咱们就实现了样式的继承,可是代码中,为了通用,仍是尽可能减小样式的继承,由于要改动代码的话,设计的一旦包含继承关系,在修改外观时就须要考虑改动样式资源带来的影响,可是会让长期稳定迭代的代码更加结构化。通常都是一个控件的几种形态,建议用样式的继承。
咱们在控件引用资源后,咱们发现虽然外观修改了,可是鼠标通过,等其余事件时,控件依然没有对应咱们要的风格。为了简化对应的事件代码,WPF提出了触发器的概念,在这里咱们可使用触发器来方便的维护控件的外观。
咱们在前面代码的基础上添加触发器,若是按钮被禁用,则修改前景色为红色:
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" Background="#000000" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style x:Key="DarkButtonStyle" TargetType="Button"> <Setter Property="Foreground" Value="#F5FFFA" /> <Setter Property="Background" Value="#696969"/> <Setter Property="BorderBrush" Value="#2f4f4f"/> <Setter Property="Margin" Value="3"/> <Style.Triggers> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Foreground" Value="Red" /> </Trigger> </Style.Triggers> </Style> <Style x:Key="WarningDarkButtonStyle" TargetType="Button" BasedOn="{StaticResource DarkButtonStyle}"> <Setter Property="FontWeight" Value="Bold"/> </Style> </Window.Resources> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <Button x:Name="buttonA" Style="{StaticResource DarkButtonStyle}" Content="我是按钮A"/> <Button Content="我是按钮B" Click="SetButtonADisableButton_OnClick" Style="{StaticResource WarningDarkButtonStyle}"/> </StackPanel> </Grid> </Window>
using System.Windows; namespace StyleAndBehavior { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void SetButtonADisableButton_OnClick(object sender, RoutedEventArgs e) { buttonA.IsEnabled = false; } } }
从这个代码中,咱们看到了当咱们点击按钮B时,按钮A的被设置了Disable没法使用,同时前景色被改为了白色,(背景色的变化咱们目前先不关注。后面会讲样式的重写,这里只关注咱们前景色的变化)。
咱们在资源上尝试添加其余触发器,完整代码以下,就会发现触发器能够帮助咱们经过监听属性的变化直接修改样式。咱们的Button获取焦点,和单击按下后,前景色都会发生变化。
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" Background="#000000" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style x:Key="DarkButtonStyle" TargetType="Button"> <Setter Property="Foreground" Value="#F5FFFA" /> <Setter Property="Background" Value="#696969"/> <Setter Property="BorderBrush" Value="#2f4f4f"/> <Setter Property="Margin" Value="3"/> <Style.Triggers> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Foreground" Value="Red" /> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Foreground" Value="Blue"/> </Trigger> <Trigger Property="IsFocused" Value="True"> <Setter Property="Foreground" Value="Yellow"/> </Trigger> </Style.Triggers> </Style> <Style x:Key="WarningDarkButtonStyle" TargetType="Button" BasedOn="{StaticResource DarkButtonStyle}"> <Setter Property="FontWeight" Value="Bold"/> </Style> </Window.Resources> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <Button x:Name="buttonA" Style="{StaticResource DarkButtonStyle}" Content="我是按钮A"/> <Button Content="我是按钮B" Click="SetButtonADisableButton_OnClick" Style="{StaticResource WarningDarkButtonStyle}"/> </StackPanel> </Grid> </Window>
还有一种是知足多个属性同时变动要求的触发器,MultiTriggers。使用这个能够监听多个属性的变化知足条件时设置对应触发器绑定的属性。
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" Background="#000000" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style x:Key="DarkButtonStyle" TargetType="Button"> <Setter Property="Foreground" Value="#F5FFFA" /> <Setter Property="Background" Value="#696969"/> <Setter Property="BorderBrush" Value="#2f4f4f"/> <Setter Property="Margin" Value="3"/> <Style.Triggers> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Foreground" Value="Red" /> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Foreground" Value="Blue"/> </Trigger> <Trigger Property="IsFocused" Value="True"> <Setter Property="Foreground" Value="Yellow"/> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsFocused" Value="True"/> <Condition Property="IsMouseOver" Value="True"/> </MultiTrigger.Conditions> <Setter Property="Foreground" Value="Orange"/> </MultiTrigger> </Style.Triggers> </Style> <Style x:Key="WarningDarkButtonStyle" TargetType="Button" BasedOn="{StaticResource DarkButtonStyle}"> <Setter Property="FontWeight" Value="Bold"/> </Style> </Window.Resources> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <Button x:Name="buttonA" Style="{StaticResource DarkButtonStyle}" Content="我是按钮A"/> <Button Content="我是按钮B" Click="SetButtonADisableButton_OnClick" Style="{StaticResource WarningDarkButtonStyle}"/> </StackPanel> </Grid> </Window>
咱们使用了MultiTrigger来实现多属性变化的触发器,用来设置对应场景下的UI变化。咱们这里设置了前景色为橙色。
由于尚未讲到MVVM因此还有一个DataTrigger这里就先不讲了。后面写自定义控件时经过MVVM会讲到这个DataTrigger的使用。原理是同样的。只是使用DataTrigger绑定时监听的时VM对象下的属性。
接下来是事件触发器。事件触发器须要传入一个故事板对象,咱们可使用事件触发器来实现一个鼠标移入时字体慢慢变大, 鼠标移出时字体慢慢变小的动画效果。
代码已经实现了,可是由于最近搬家写代码用的电脑不同,这个电脑没有录屏软件,因此实际效果无法录屏,复制代码跑起来看看啦。
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" Background="#000000" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style x:Key="DarkButtonStyle" TargetType="Button"> <Setter Property="Foreground" Value="#F5FFFA" /> <Setter Property="Background" Value="#696969"/> <Setter Property="BorderBrush" Value="#2f4f4f"/> <Setter Property="Margin" Value="3"/> <Style.Triggers> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Foreground" Value="Red" /> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Foreground" Value="Blue"/> </Trigger> <Trigger Property="IsFocused" Value="True"> <Setter Property="Foreground" Value="Yellow"/> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsFocused" Value="True"/> <Condition Property="IsMouseOver" Value="True"/> </MultiTrigger.Conditions> <Setter Property="Foreground" Value="Orange"/> </MultiTrigger> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard > <Storyboard> <DoubleAnimation Duration="0:0:1.0" Storyboard.TargetProperty="FontSize" To="22"> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:1.0" Storyboard.TargetProperty="FontSize" To="12"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> <Style x:Key="WarningDarkButtonStyle" TargetType="Button" BasedOn="{StaticResource DarkButtonStyle}"> <Setter Property="FontWeight" Value="Bold"/> </Style> </Window.Resources> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <Button x:Name="buttonA" Style="{StaticResource DarkButtonStyle}" Content="我是按钮A"/> <Button Content="我是按钮B" Click="SetButtonADisableButton_OnClick" Style="{StaticResource WarningDarkButtonStyle}"/> </StackPanel> </Grid> </Window>
对于行为,不少人学的很迷糊,我以前也是。就是拿行为绑定几个命令到后台的VM上。其余的大部分场景都没有用过了。致使没法发挥出来WPF设计人员设计行为的优点,这里咱们也尝试本身写一下行为。
对行为的支持被放到了System.Windows.Interactivity.dll中。他是使用行为的基础。行为主要是为了封装一些UI功能,从而能够没必要编写代码就可以把行为应用到元素上。举个例子,咱们实现一个TextBox的输入水印效果。
咱们新建一个类库工程起名叫作CustomBehaviorLibrary。来存放咱们的行为,经过在该工程上右键=》管理Nuget程序包=》搜索System.Windows.Interactivity.WPF并安装.若是使用WPF下的控件,注意必需要同时有PresentationCore、PresentationFramework、WindwsBase这三个库的引用。缺乏的能够Alt+Enter手动引用一下。
咱们建立TextBoxWatermarkBehavior类,并继承自Behavior类,咱们在Behavior上右键F12,看到里面有一个AssociatedObject名字的对象,这个就是咱们要用来添加行为的对象。咱们先使用propdp添加名字为Watermark的string类型的依赖项属性。用来做为咱们的水印显示文本。
using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; using System.Windows.Media; namespace CustomBehaviorLibrary { public class TextBoxWatermarkBehavior : Behavior<TextBox> { private bool _hasContent = true; public string Watermark { get { return (string)GetValue(WatermarkProperty); } set { SetValue(WatermarkProperty, value); } } // Using a DependencyProperty as the backing store for Watermark. This enables animation, styling, binding, etc... public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(string), typeof(TextBoxWatermarkBehavior), new PropertyMetadata(default(string))); protected override void OnAttached() { base.OnAttached(); var textbox = AssociatedObject; textbox.Loaded += Textbox_Loaded; } protected override void OnDetaching() { base.OnDetaching(); } private void Textbox_Loaded(object sender, RoutedEventArgs e) { var textbox = sender as TextBox; if (string.IsNullOrEmpty(textbox.Text)) { textbox.Foreground = Brushes.Gray; textbox.Text = Watermark; _hasContent = false; } textbox.GotFocus -= Textbox_GotFocus; textbox.LostFocus -= Textbox_LostFocus; textbox.GotFocus += Textbox_GotFocus; textbox.LostFocus += Textbox_LostFocus; } private void Textbox_LostFocus(object sender, RoutedEventArgs e) { var textbox = sender as TextBox; if (string.IsNullOrEmpty(textbox.Text)) { _hasContent = false; textbox.Text = Watermark; textbox.Foreground = Brushes.Gray; } else { _hasContent = true; } } private void Textbox_GotFocus(object sender, RoutedEventArgs e) { var textbox = sender as TextBox; if (!_hasContent) { textbox.Text = ""; textbox.Foreground = Brushes.Black; } } } }
这样咱们的行为就建立好了,这个时候,咱们在主工程下使用这个行为。
1)主工程添加对CustomBehaviorLibrary工程的引用;
2)主工程在NuGet添加对System.Windows.Interactivity.WPF的引用。
3)注意在使用的窗体下添加命名空间
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:customBehavior="clr-namespace:CustomBehaviorLibrary;assembly=CustomBehaviorLibrary"
4)添加TextBox控件,并添加Interactivity下的Behaviors。 在Behaviors中添加咱们自定义的TextBoxWatermarkBehavior 并设置咱们添加的依赖项属性。设置水印内容。代码以下:
<Window x:Class="StyleAndBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StyleAndBehavior" mc:Ignorable="d" Topmost="True" Background="#000000" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:customBehavior="clr-namespace:CustomBehaviorLibrary;assembly=CustomBehaviorLibrary" Title="MainWindow" Height="450" Width="800"> <Grid> <StackPanel VerticalAlignment="Top" Height="30" Orientation="Horizontal"> <TextBox MinWidth="200"> <i:Interaction.Behaviors> <customBehavior:TextBoxWatermarkBehavior Watermark="我是水印,请输入内容"/> </i:Interaction.Behaviors> </TextBox> <TextBox MinWidth="200"> <i:Interaction.Behaviors> <customBehavior:TextBoxWatermarkBehavior Watermark="我是另一个TextBox水印,请输入内容"/> </i:Interaction.Behaviors> </TextBox> </StackPanel> </Grid> </Window>
这样咱们就完成了对行为的使用。这里写的比较简单,其实还有不少相关的知识能够扩展,由于行为是一个比较独立的内容,因此单独在行为中能够扩展的通用的东西特别多。而i:Interaction.Triggers也是在这里的,可是我以前都是直接绑定VM下的Command因此这个等讲到VM和Command的时候在讲这个吧用法是同样的。目前这一章就讲这么多,行为这里配置和引用稍微复杂了一些,可是学习是一个持续的过程,天天进步一点,掌握这个知识点,不要急,WPF的知识就那么多,天天投入一点,几年时间慢慢的也就精通了。