在使用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的知識就那麼多,天天投入一點,幾年時間慢慢的也就精通了。