WPF學習系列之七 (樣式與行爲)

  1. 樣式(Styles)是組織和重用格式化選項的重要工具。不是使用重複的標記填充XAML,以設置諸如邊距、顏色及字體等細節,而能夠建立一系列封裝全部這些細節的樣式。而後能夠在須要之處經過一個屬性應用樣式。
    行爲(behavior)是一個重用用戶界面代碼的工具,它封裝了一些通用的UI功能。若是具備適當的行爲,可使用一兩行XAML標記將其附加到一個元素,從而能夠節省編寫和調試代碼的工做。
  2. 建立樣式對象
    複製代碼
    <Window.Resources> <Style x:Key="BigFontButtonStyle"> <Setter Property="Control.FontFamily" Value="Times New Roman"></Setter> <Setter Property="Control.FontSize" Value="20"></Setter> <Setter Property="Control.FontWeight" Value="Bold"></Setter> </Style> </Window.Resources>
    複製代碼

    上面的標記建立了一個獨立的資源:一個System.Windows.Style對象,與全部資源同樣,樣式對象有一個鍵名,從而當須要時 能夠從集合中提取它,根據約定,用於樣式的鍵名一般以Style結尾。樣式經過元素的Style屬性(該屬性是在FrameworkElement基類中 定義的)插入到元素中,以下所示爲按鈕使用了以上定義的樣式:canvas

    <Button Style="{StaticResource BigFontButtonStyle}">A Button For My Test</Button>

    也可使用代碼設置樣式,就是使用FindResources()方法從最近的資源集合中提取樣式:app

    btnStyleTest1.Style = (Style)btnStyleTest1.FindResource("BigFontButtonStyle");

    樣式設置元素的初始外觀,可是能夠隨意覆蓋它們設置的這些特性。例如,若是應用了BigFontButtonStyle樣式,而且明確地設置了FontSize屬性,則在按鈕中的FindSize設置就會覆蓋樣式。
    Setters集合是Style類中最重要的屬性,但並非惟一的屬性,在Style類中共有5個重要屬性:
    QQ圖片20140811225238
    ide

  3. 設置樣式屬性
    每一個Style對象包裝了一個Setter對象的集合,每一個Setter對象設置元素的單個屬性。惟一的限制是設置器只能改變依賴項屬性而不能修改其它屬性。
    在某些狀況下,不能使用簡單的特性字符串設置屬性值,此時可使用嵌套的屬性元素代替特性,以下是一個示例:
    工具

    複製代碼
    <Window.Resources> <Style x:Key="HappyFaceTileStyle"> <Setter Property="Control.Background"> <Setter.Value> <ImageBrush ImageSource="images/happyface.jpg" ViewportUnits="Absolute" Viewport="0,0,32,32" TileMode="Tile"></ImageBrush> </Setter.Value> </Setter> </Style> </Window.Resources>
    複製代碼

    若是但願在多個樣式中(或在同同樣式的多個設置器中)重用相同的圖像畫刷,能夠將其定義爲資源,而後再在樣式中使用資源:post

    複製代碼
    <Window.Resources> <ImageBrush x:Key="happyFace" ImageSource="images/happyface.jpg" ViewportUnits="Absolute" Viewport="0,0,32,32" TileMode="Tile"/> <Style x:Key="HappyFaceTileStyle"> <Setter Property="Control.Background" Value="{StaticResource happyFace}"></Setter> </Style> </Window.Resources>
    複製代碼

    爲了標識但願設置的屬性,須要設置類和屬性的名稱,然而,類名沒必要是定義屬性的類名,也能夠是繼承了屬性的派生類,以下示例,它使用Button類的引用代替了Control類的引用,此時,若是爲Label元素也引用了此樣式,哪麼對於Label此樣式就不起做用:字體

    <Style x:Key="BigFontButtonStyle"> <Setter Property="Button.FontFamily" Value="Times New Roman"></Setter> <Setter Property="Button.FontSize" Value="20"></Setter> <Setter Property="Button.FontWeight" Value="Bold"></Setter> </Style>

    以下樣式,Button.FontSize屬性和TextBlock.FontSize屬性是在它們各自的基類中分別聲明,但它們都引用同 一個依賴項屬性(也就是說,TextBlock.FontSize和Control.FontSize引用都指向同一個 DependencyProperty對象)。因此,當使用這個樣式時,WPF設置FontSize屬性兩次,最後引用的設置具備優先權,而且被同時引用 到Button和TextBlock對象。動畫

    <Style x:Key="BigFontButtonStyle"> <Setter Property="Button.FontSize" Value="20"></Setter> <Setter Property="TextBlock.FontSize" Value="33"></Setter> </Style>

    若是定義的樣式中全部的屬性都準備用於相同的元素類型,能夠設置Style對象的TargetType屬性,指定準備應用屬性的類來簡化樣式聲明,例如,若是隻建立只應用於按鈕的樣式,可按以下方式建立樣式:this

    <Style x:Key="BigFontButtonStyle" TargetType="Button"> <Setter Property="FontFamily" Value="Times New Roman"></Setter> <Setter Property="FontSize" Value="20"></Setter> <Setter Property="FontWeight" Value="Bold"></Setter> </Style>
  4. 關聯事件處理程序
    先看下面的示例,在樣式設置中使用EventSetter對象爲MouseEnter和MouseLeave事件關聯事件處理程序:url

    <Style x:Key="MouseOverHighlightStyle"> <EventSetter Event="TextBlock.MouseEnter" Handler="element_MouseEnter"></EventSetter> <EventSetter Event="TextBlock.MouseLeave" Handler="element_MouseLeave"></EventSetter> <Setter Property="TextBlock.Padding" Value="5"></Setter> </Style>

    下面是事件處理程序:spa

    複製代碼
    private void element_MouseEnter(object sender, MouseEventArgs e) { ((TextBlock)sender).Background = new SolidColorBrush(Colors.LightGoldenrodYellow); } private void element_MouseLeave(object sender, MouseEventArgs e) { ((TextBlock)sender).Background = null; }
    複製代碼

    MouseEnter和MouseLeave事件使用直接事件路由,這意味着它們不在元素樹中冒泡或隧道。若是但願爲大量元素應用以上的效 果,須要爲每一個元素添加MouseEnter和MouseLeave事件處理程序,基於樣式的事件處理程序簡化了這一任務,如今只須要應用單個樣式,該樣 式包含了屬性設置器和事件設置器:

    <TextBlock Grid.Row="3" Style="{StaticResource MouseOverHighlightStyle}">Hover over me</TextBlock>

    在WPF中,事件設置器是一種不多使用的技術,而更多的使用的是事件觸發器。當處理冒泡路由策略時,事件設置器不是一個好的選擇,對因而種狀況,在高層次的元素上處理但願處理的事件一般更容易。

  5. 多層樣式
    儘管能夠在許多不一樣的層次定義任意數量的樣式,可是每一個元素一次只能使用一個樣式對象,乍一看,這好像是一種限制,但因爲屬性值繼承 和樣式繼承特性,所以,實際上這種限制並不存在。若是但願爲一組相同的控件使用相同的字體,而又不想爲每一個控件應用相同的樣式,對於這種狀況,能夠將它們 放置到一個面板,並設置面板的樣式,只要設置的屬性具備屬性值繼承特性,這些值就會被傳遞到子元素。使用這種模型的屬性包括IsEnabled、 IsVisible、Foreground以及全部字體屬性。
    對於樣式繼承特性,能夠經過爲樣式設置 BasedOn 特性在另外一個樣式的基礎上建立樣式,例如如下代碼,第二個樣式繼承了第一個樣式的屬性設置:

    複製代碼
    <Style x:Key="BigFontButtonStyle"> <Setter Property="Control.FontFamily" Value="Times New Roman"></Setter> <Setter Property="Control.FontSize" Value="20"></Setter> <Setter Property="Control.FontWeight" Value="Bold"></Setter> </Style> <Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}"> <Setter Property="Control.Background" Value="White"></Setter> <Setter Property="Control.Foreground" Value="DarkBlue"></Setter> </Style>
    複製代碼

    除非有特殊緣由要求一個樣式繼承自另外一個樣式(例如,第二個樣式是第一個樣式的特例,而且只改變了繼承來有大量設置中的幾個特徵),不然不要使用樣式繼承,由於這種繼承會使應用程序更加脆弱。

  6. 經過類型自動應用樣式
    能夠經過設置樣式的TargetType屬性爲特定類型的元素自動應用樣式,並徹底忽略鍵名,此時,WPF其實是隱式地使用類型標記擴展設置鍵名,以下代碼所示:

    x:Key="{x:Type Button}"

    以下示例,在一個窗口中爲樣式設置TargetType特性爲Button,此樣式就會被應用到這個窗口中的全部按鈕上:

    <Style TargetType="Button"> <Setter Property="FontFamily" Value="Times New Roman"></Setter> <Setter Property="FontSize" Value="20"></Setter> <Setter Property="FontWeight" Value="Bold"></Setter> </Style>

    能夠爲一個元素的樣式特性設置null值來刪除樣式:

    <Button Style="{x:Null}">Test Button</Button>

    儘管自動樣式很方便,可是它會使設計變得複雜,下面是幾條緣由。爲了不出現此問題,最好果斷的使用自動樣式爲整個用戶界面提供一個單一且一致的外觀,而後再爲特例設置明確的樣式。

    • 在具備許多樣式和多層樣式的複雜窗口中,跟蹤是否經過屬性值繼承或經過樣式設置了某個特定屬性有些困難,所以,若是但願改變一個簡單的細節,須要查看整個窗口的所有標記。
    • 窗口中的格式化化操做在開始時一般更通常,可是會愈來愈詳細,若是剛開始爲窗口應用了自動樣式,在許多地方可能須要經過顯式的樣式覆蓋自動樣式,這會使整個設計變得複雜。爲每一個但願設置的格式化特徵的組合建立命名的樣式,並根據名稱應用它們會更直觀。
    • 若是爲TextBlock建立了一個自動樣式,會同時修改使用TextBlock的其它控件。
  7. 觸發器
    使用觸發器,能夠自動完成簡單的樣式改變,例如,當一個屬性發生變化時能夠進行響應,並自動調整樣式。觸發器在Style的 Triggers集合中定義,每一個樣式均可以有任意多個觸發器,每一個觸發器都是System.Windows.TriggerBase的派生類的實例。
    QQ截圖20140814234527
  8. 簡單觸發器
    能夠爲任何依賴項屬性關聯一個簡單觸發器。每一個觸發器都指定了正在監視的屬性,以及正在等待的值,當該屬性值出現時,將自動應用存儲在Trigger.Setters集合中的設置。例以下面的觸發器等待按鈕獲取鍵盤焦點,當獲取焦點時會將其前景色設置爲深紅色:
    複製代碼
    <Style x:Key="BigFontButtonStyle"> <Style.Setters> <Setter Property="Control.FontFamily" Value="Times New Roman"></Setter> <Setter Property="Control.FontSize" Value="20"></Setter> <Setter Property="Control.FontWeight" Value="Bold"></Setter> </Style.Setters> <Style.Triggers> <Trigger Property="Control.IsFocused" Value="True"> <Setter Property="Control.Foreground" Value="DarkRed"></Setter> </Trigger> </Style.Triggers> </Style> 
    複製代碼

    觸發器的優勢是不須要爲翻轉它們而編寫任何邏輯,只要中止應用觸發器,元素就會恢復到它的正常外觀。能夠建立一次應用相同元素的多個觸發器,若是這些觸發器設置不一樣的屬性,這種狀況就不會出現混亂,但若是多個觸發器修改相同的屬性,那麼最後的觸發器有效。 
    若是但願建立只有當幾個條件都爲真時才激活的觸發器,可使用 MultiTrigger。它提供了一個 Conditions 集合,能夠經過該屬性定義一系列屬性和值的組合,在下例中,只有當按鈕具備焦點且鼠標懸停在該按鈕上時,纔會應用樣式:

    複製代碼
    <Style x:Key="BigFontButtonStyle"> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="Control.IsFocused" Value="True"></Condition> <Condition Property="Control.IsMouseOver" Value="True"></Condition> </MultiTrigger.Conditions> <MultiTrigger.Setters> <Setter Property="Control.Foreground" Value="DarkRed"></Setter> </MultiTrigger.Setters> </MultiTrigger> </Style.Triggers> </Style>  
    複製代碼
  9. 事件觸發器
    普通的觸發器等待一個屬性發生變化,而事件觸發器等待一個特定的事件被激發,它也不是經過設置器來改變元素,而是要求用戶提供一系列修改 控件的動做,這些動做一般被用於一個動畫。下面的示例是事件觸發器等待MouseEnter事件,而後動態的在0.2秒內將按鈕的字體放大到22個單位:
    複製代碼
    <Style x:Key="EventTriggerTest"> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="22"/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="FontSize"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style>
    複製代碼
    在XAML中,必須在故事板(Storyboard)中定義每一個動畫,它爲動畫提供了時間線,在故事板內部,用戶能夠定義但願使用的一個或多個動畫對象,每一個動畫對象執行本質上相同的任務:在必定時期內修改依賴項屬性。
    DoubleAnimation類(同全部動畫類,都位於System.Windows.Media.Animation名稱空間中):它能在一段給定的時間內將任何雙精度值屬性逐漸改變爲設定的目標值,改變的實際尺寸取決於時間總量和須要改變的總量。
    與屬性觸發器不一樣,若是但願翻轉回原來的狀態,須要反轉事件觸發器。在以上示例中,樣式使用了一個響應MouseLeave事件的事件觸發器,並在1秒內恢復到原來的大小,此時不須要指明目標字體尺寸,WPF會假定反轉回原來的字體尺寸。
  10. 行爲,行爲旨在封裝一些UI功能,從而能夠不用編寫代碼就可以將其應用到元素上。
    樣式提供了重用一組屬性設置的實用方法,但在典型的應用程序中,屬性設置只是用戶界面基礎結構中的一小部分,而大部分基本的程序一般須要 大量的用戶界面代碼,這些代碼一般與應用程序的功能無關,在許多程序中,用於用戶界面的代碼(如驅動動畫、實現平滑效果、維護用戶界面狀態)不管是在數量 上仍是在複雜性上都超過了業務代碼,而許多這類代碼是通用的,因此Expression Elend創做者開發了一個稱爲行爲的特徵。其思想是開發人員建立一個封裝了一些通用用戶界面功能的行爲,而後經過將行爲拖動到須要行爲的控件上,此控件 就具備了行爲所定義的一些功能。
    爲了得到支持行爲特性的程序集,能夠安裝Expression Blend或安裝Expression Blend SDK(下載地址:http://tinyurl.com/kkp4g8):
    System.Windows.Interactivity.dll:它定義了支持行爲的基本類,它是行爲特徵的基礎。
    Microsoft.Expression.Interactions.dll:它經過添加可選的以核心行爲類爲基礎的動做和觸發器類,增長了一些有用的擴展。
  11. 建立行爲
    首先添加對System.Windows.Interactivity.dll的引用,而後建立一個繼承自Behavior基類的類。在 此類中覆蓋OnAttached()和OnDetaching()方法,在這兩個方法中能夠經過AssociatedObject屬性訪問放置行爲的元 素。在OnAttached()方法中關聯事件處理程序,在OnDetaching()中移除事件處理程序。 如下是建立一個能夠爲任意元素提供使用鼠標在Canvas面板上拖動元素的行爲類 DragInCanvasBehavior:
    複製代碼
     1 public class DragInCanvasBehavior : Behavior<UIElement>  2 {  3 private Canvas canvas;  4  5 //如下的OnAttached()和OnDetaching()方法是用於監視MouseLeftButtonDown、MouseMove、MouseLeftButtonUp事件的代碼。  6 protected override void OnAttached()  7  {  8 base.OnAttached();  9 10 // Hook up event handlers. 11 this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown; 12 this.AssociatedObject.MouseMove += AssociatedObject_MouseMove; 13 this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp; 14  } 15 16 protected override void OnDetaching() 17  { 18 base.OnDetaching(); 19 20 // Detach event handlers. 21 this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown; 22 this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove; 23 this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp; 24  } 25 26 // Keep track of when the element is being dragged. 27 private bool isDragging = false; 28 29 // When the element is clicked, record the exact position 30 // where the click is made. 31 private Point mouseOffset; 32 33 //當用戶單擊鼠標左鍵時,DragInCanvasBehavior開始拖動操做,記錄元素左上角與鼠標指針之間的偏移,並捕獲鼠標。 34 private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 35  { 36 // Find the canvas. 37 if (canvas == null) canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas; 38 39 // Dragging mode begins. 40 isDragging = true; 41 42 // Get the position of the click relative to the element 43 // (so the top-left corner of the element is (0,0). 44 mouseOffset = e.GetPosition(AssociatedObject); 45 46 // Capture the mouse. This way you'll keep receiveing 47 // the MouseMove event even if the user jerks the mouse 48 // off the element. 49  AssociatedObject.CaptureMouse(); 50  } 51 52 //當元素處於拖動模式且移動鼠標時,從新定位元素。 53 private void AssociatedObject_MouseMove(object sender, MouseEventArgs e) 54  { 55 if (isDragging) 56  { 57 // Get the position of the element relative to the Canvas. 58 Point point = e.GetPosition(canvas); 59 60 // Move the element. 61 AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y); 62 AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X); 63  } 64  } 65 66 //當釋放鼠標時,結束拖動。 67 private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 68  { 69 if (isDragging) 70  { 71  AssociatedObject.ReleaseMouseCapture(); 72 isDragging = false; 73  } 74  } 75 }
    複製代碼
  12. 使用行爲
    首先在項目中添加對定義DragInCanvasBehavior類的類庫及 System.Windows.Interactivity.dll程序集的引用,而後在窗口XML中映射這兩個名稱空間。只須要使用 Interaction.Behaviors附加屬性在Canvas面板中添加任意元素。以下是建立了一個具備三個元素的Canvas面板,其中兩個元素 使用了DragInCanvasBehavior行爲:
    複製代碼
    <Window x:Class="WpfApplication1.DragInCanvasTest" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:custom="clr-namespace:CustomBehaviousLibrary;assembly=CustomBehaviousLibrary" Title="DragInCanvasTest" Height="300" Width="300"> <Grid> <Canvas> <Rectangle Canvas.Left="36" Canvas.Top="49" Height="46" Name="rectangle1" Stroke="Black" Width="76" Fill="#FFC00000"> <i:Interaction.Behaviors> <custom:DragInCanvasBehavior></custom:DragInCanvasBehavior> </i:Interaction.Behaviors> </Rectangle> <Ellipse Canvas.Left="82" Canvas.Top="126" Height="69" Name="ellipse1" Stroke="Black" Width="39" Fill="#FF2323D1"> <i:Interaction.Behaviors> <custom:DragInCanvasBehavior></custom:DragInCanvasBehavior> </i:Interaction.Behaviors> </Ellipse> <Ellipse Canvas.Left="164" Canvas.Top="103" Height="34" Name="ellipse2" Stroke="Black" Width="41" Fill="#FF1CAA1C" /> </Canvas> </Grid> </Window>
    複製代碼
相關文章
相關標籤/搜索