1. 程式人生 > 實用技巧 >WPF快速入門系列(6)——WPF資源和樣式

WPF快速入門系列(6)——WPF資源和樣式

一、引言

  WPF資源系統可以用來儲存一些公有物件和樣式,從而實現重用這些物件和樣式的作用。而WPF樣式是重用元素的格式的重要手段,可以理解樣式就如CSS一樣,儘管我們可以在每個控制元件中定義格式,但是如果多個控制元件都應用了多個格式的時候,我們就可以把這些格式封裝成格式,然後在資源中定義這個格式,之前如果用到這個格式就可以直接使用這個樣式,從而達到重用格式的手段。從中可以發現,WPF資源和WPF樣式是相關的,我們經常把樣式定義在資源中。

二、WPF資源詳解

2.1 資源基礎介紹

  儘管可以在程式碼中建立和操作資源,但是通常都是以XAML標籤的形式定義資源的。下面具體看看如何去定義一個資源,具體的XAML程式碼如下所示:

<Window x:Class="ResourceDemo.ResourceUse"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="REsource" Height="100" Width="350"
        xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <Window.Resources>
        <!--定義一個字串資源-->
        <sys:String x:Key="nameStr">
            LearningHard部落格:http://www.cnblogs.com/zhili/
        </sys:String>
    </Window.Resources>
<StackPanel> <!--通過資源key來對資源進行使用--> <TextBlock Text="{StaticResource nameStr}" Margin="10"/> </StackPanel> </Window>

  每一個元素都有一個Resources屬性,該屬性儲存了一個資源字典集合。關於資源字典將會在下面部分介紹。儘管每個元素都提供了Resources屬性,但通常在視窗級別上定義資源,就如上面XAML程式碼所示的那樣。因為每個元素都可以訪問它自己的資源集合中的資源,也可以訪問所有父元素的資源集合中的資源。

2.2 靜態資源和動態資源區別

  為了使用XAML標記中的資源,需要一種引用資源的方法,可以通過兩個標記來進行引用資源:一個用於靜態資源,另一個用於動態資源。在上面的XAML中,我們引用的方式就是靜態資源的引用方式,因為我們指定了StaticResource。那靜態資源和動態資源有什麼區別呢?

  對於靜態資源在第一次建立視窗時,一次性地設定完畢;而對於動態資源,如果發生了改變,則會重新應用資源。下面通過一個示例來演示下他們之間的區別。具體的XAML程式碼如下所示:

<Window x:Class="ResourceDemo.DynamicResource"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DynamicResource" Height="300" Width="300">
    <Window.Resources>
        <SolidColorBrush x:Key="RedBrush" Color="Red"></SolidColorBrush>
    </Window.Resources>
    <StackPanel Margin="5">
      <Button Background="{StaticResource RedBrush}" Margin="5" FontSize="14" Content="Use a Static Resource"/>
        <Button Background="{DynamicResource RedBrush}" Margin="5" FontSize="14" Content="Use a Dynamic Resource"/>
        <Button Margin="5" FontSize="14" Content="Change the RedBrush to Yellow" Click="ChangeBrushToYellow_Click"/>
    </StackPanel>
</Window>

  對應改變資源按鈕的後臺程式碼如下所示:

private void ChangeBrushToYellow_Click(object sender, RoutedEventArgs e)
        {
            // 改變資源
            this.Resources["RedBrush"] = new SolidColorBrush(Colors.Yellow);
        }

  執行上面程式,你將發現,當點選Change按鈕之後,只改變了動態引用資源按鈕的背景色,而靜態引用按鈕的背景卻沒有發生改變,具體效果圖如下所示:

2.3 資源字典

  在前面中講到,每個Resources屬性儲存著一個資源字典集合。如果希望在多個專案之間共享資源的話,就可以建立一個資源字典。資源欄位是一個簡單的XAML文件,該文件就是用於儲存資源的,可以通過右鍵專案->新增資源字典的方式來新增一個資源字典檔案。下面具體看下如何去建立一個資源字典。具體的XAML程式碼如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="blueBrush" Color="Blue"/>
    <FontWeight x:Key="fontWeight">Bold</FontWeight>
</ResourceDictionary>

  為了使用資源字典,需要將其合併到應用程式中資源集合位置,當然你也可以合併到視窗資源集合中,但是通常是合併到應用程式資源集合中,因為資源字典的目的就是在於多個窗體中共享,具體的XAML程式碼如下所示:

<Application x:Class="ResourceDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="DynamicResource.xaml">
    <Application.Resources>
        <!--合併資源字典到Application.Resources中-->
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Generic.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

  那怎樣使用資源字典中定義的資源呢?其使用方式和引用資源的方式是一樣的,一樣是通過資源的Key屬性來進行引用的,具體使用程式碼如下所示:

<Window x:Class="ResourceDemo.ResourceUse"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="REsource" Height="100" Width="350"
        xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <Window.Resources>
        <!--定義一個字串資源-->
        <sys:String x:Key="nameStr">
            LearningHard部落格:http://www.cnblogs.com/zhili/
        </sys:String>
    </Window.Resources>
    <StackPanel>
        <!--使用資源字典中定義的資源-->
        <Button  Margin="10" Background="{StaticResource blueBrush}" Content="Blue Button" FontWeight="{StaticResource fontWeight}"/>
        <!--通過資源key來對資源進行使用-->
        <TextBlock Text="{StaticResource nameStr}" Margin="10"/>
    </StackPanel>
</Window>

  此時的執行效果如下圖所示:

  前面只是介紹在當前應用程式下共享資源可以把資源字典合併到應用程式資源集合中,如果想在多個應用程式共享資源怎麼辦呢?最簡單的方法就是在每個應用程式中拷貝一份資源字典的XAML檔案,但是這樣不能對版本進行控制,顯然這不是一個好的辦法。更好的辦法是將資源字典編譯到一個單獨的類庫程式集中,應用程式可以通過引用程式集的方式來共享資源。這樣就達到了在多個應用程式中共享資源的目的。

  使用這種方式面臨著另一個問題,即如何獲得所需要的資源並在應用程式中使用資源。對此,可以採用兩種方法。第一種辦法是通過程式碼建立一個ResourceDictionary物件,再通過指定其Source屬性來定位程式中資源字典檔案,一旦建立了ResourceDictionary物件,就可以通過key來檢索對應的資源,具體的實現程式碼如下:

  ResourceDictionary resourceDic = new ResourceDictionary();
            // ReusableDictionary.xaml是資源字典檔案
            resourceDic.Source = new Uri("ResourceLibrary;component/ReusableDictionary.xaml", UriKind.Relative);
            SolidColorBrush blueBrush =(SolidColorBrush)resourceDic["BlueBrush"];

  這種方式不需要手動指定資源,當載入一個新的資源字典時,視窗中所有的DynamicResource引用都會自動引用新的資源,這樣的方式可以用來構建動態的面板功能。

  另外一種辦法可以使用ComponentResourceKey標記,使用ComponentResourceKey為資源建立鍵名。具體使用例子請參看博文:Defining and Using Shared Resources in a Custom Control Library

三、WPF樣式詳解

  在前面介紹了WPF資源,使用資源可以在一個地方定義物件而在整個應用程式中重用它們,除了在資源中可以定義各種物件外,還可以定義樣式,從而達到樣式的重用。

  樣式可以理解為元素的屬性集合。與Web中的CSS類似。WPF可以指定具體的元素型別為目標,並且WPF樣式還支援觸發器,即當一個屬性發生變化的時,觸發器中的樣式才會被應用。

3.1 WPF樣式使用

  之前WPF資源其實完全可以完成WPF樣式的功能,只是WPF樣式對資源中定義的物件進行了封裝,使其存在於樣式中,利於管理和應用,我們可以把一些公共的屬性定義放在樣式中進行定義,然後需要引用這些屬性的控制元件只需要引用具體的樣式即可,而不需要對這多個屬性進行分別設定。下面XAML程式碼就是一個樣式的使用示例:

<Window x:Class="StyleDemo.StyleDefineAndUse"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="400">
    <Window.Resources>
        <!--定義樣式-->
        <Style TargetType="Button">
            <Setter Property="FontFamily" Value="Times New Roman" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="FontWeight" Value="Bold" />
        </Style>
    </Window.Resources>
    <StackPanel Margin="5">
        <!--由於前面定義的樣式沒有定義key標記,如果沒有顯示指定Style為null,這按鈕將指定引用事先定義的樣式-->
        <Button Padding="5" Margin="5">Customized Button</Button>
        <TextBlock Margin="5">Normal Content.</TextBlock>
        <!--使其不引用事先定義的樣式-->
        <Button Padding="5" Margin="5" Style="{x:Null}">A Normal Button</Button>
    </StackPanel>
</Window>

  具體的執行效果如下圖所示:

  當樣式中沒有定義key標記時,則對應的樣式會指定應用到目標物件上,上面XAML程式碼就是這種情況,如果顯式為樣式定義了key標記的話,則必須顯式指定樣式Key的方式,對應的樣式才會被應用到目標物件上,下面具體看看這種情況。此時XAML程式碼如下所示:

<Window x:Class="StyleDemo.ReuseFontWithStyles"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ReuseFontWithStyles" Height="300" Width="300">
    <Window.Resources>
        <!--帶有key標籤的樣式-->
        <Style TargetType="Button" x:Key="BigButtonStyle">
            <Setter Property="FontFamily" Value="Times New Roman" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="FontWeight" Value="Bold" />
        </Style>
    </Window.Resources>
    <StackPanel Margin="5">
        <!--如果不顯式指定樣式key將不會應用樣式-->
        <Button Padding="5" Margin="5">Normal Button</Button>
        <Button Padding="5" Margin="5" Style="{StaticResource BigButtonStyle}">Big Button</Button>
        <TextBlock Margin="5">Normal Content.</TextBlock>
        <!--使其不引用事先定義的樣式-->
        <Button Padding="5" Margin="5" Style="{x:Null}">A Normal Button</Button>
    </StackPanel>
</Window>

  此時執行效果如下圖所示:

3.2 樣式觸發器

  WPF樣式還支援觸發器,在樣式中定義的觸發器,只有在該屬性或事件發生時才會被觸發,下面具體看看簡單的樣式觸發器是如何定義和使用的,具體的XAML程式碼如下所示:

<Window x:Class="StyleDemo.SimpleTriggers"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SimpleTriggers" Height="300" Width="300">
    <Window.Resources>
        <Style x:Key="BigFontButton">
            <Style.Setters>
                <Setter Property="Control.FontFamily" Value="Times New Roman" />
                <Setter Property="Control.FontSize" Value="18" />

            </Style.Setters>
            <!--樣式觸發器-->
            <Style.Triggers>
                <!--獲得焦點時觸發-->
                <Trigger Property="Control.IsFocused" Value="True">
                    <Setter Property="Control.Foreground" Value="Red" />
                </Trigger>
                <!--滑鼠移過時觸發-->
                <Trigger Property="Control.IsMouseOver" Value="True">
                    <Setter Property="Control.Foreground" Value="Yellow" />
                    <Setter Property="Control.FontWeight" Value="Bold" />
                </Trigger>
                <!--按鈕按下時觸發-->
                <Trigger Property="Button.IsPressed" Value="True">
                    <Setter Property="Control.Foreground" Value="Blue" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <StackPanel Margin="5">
        <Button Padding="5" Margin="5"
            Style="{StaticResource BigFontButton}" 
              >A Big Button</Button>
        <TextBlock Margin="5">Normal Content.</TextBlock>
        <Button Padding="5" Margin="5"
            >A Normal Button</Button>
    </StackPanel>
</Window>

  此時的執行效果如下圖所示:

  上面定義的觸發器都是在某個屬性發生變化時觸發的,也可以定義當某個事件啟用時的觸發器,我們也把這樣的觸發器稱為事件觸發器,下面示例定義的事件觸發器是等待MouseEnter事件,一旦觸發MouseEnter事件,則動態改變按鈕的FontSize屬性來形成動畫效果,具體的XAML程式碼如下所示:

<Window x:Class="StyleDemo.EventTrigger"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="EventTrigger" Height="300" Width="300">
    <Window.Resources>
        <Style x:Key="BigFontButton">
            <Style.Setters>
                <Setter Property="Control.FontFamily" Value="Times New Roman" />
                <Setter Property="Control.FontSize" Value="18" />
                <Setter Property="Control.FontWeight" Value="Bold" />
            </Style.Setters>
            <Style.Triggers>
                <!--定義事件觸發器-->
                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <!--事件觸發時只需的操作-->
                    <EventTrigger.Actions>
                        <!--把動畫放在動畫面板中-->
                        <BeginStoryboard>
                            <!--在0.2秒的時間內將字型放大到22單位-->
                            <Storyboard>
                                <DoubleAnimation
                  Duration="0:0:0.2"
                  Storyboard.TargetProperty="FontSize"
                  To="22"  />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
                <!--滑鼠移開觸發的事件-->
                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <!--在1秒的時間內將字型尺寸縮小到原來的大小-->
                            <!--如果目標字型尺寸沒有明確指定,則WPF將預設使用第一次動畫之前按鈕的字型尺寸-->
                            <Storyboard>
                                <DoubleAnimation
                  Duration="0:0:1"
                  Storyboard.TargetProperty="FontSize"  />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Style.Triggers>
        </Style>     
    </Window.Resources>
    <StackPanel Margin="5">
        <Button Padding="5" Margin="5"
            Style="{StaticResource BigFontButton}" 
              >A Big Button</Button>
        <TextBlock Margin="5">Normal Content.</TextBlock>
        <Button Padding="5" Margin="5"
            >A Normal Button</Button>
    </StackPanel>
</Window>

  此時的執行效果如下圖所示:

四、小結

  到這裡,WPF資源和樣式的內容就介紹結束。總結為,WPF樣式類似CSS,可以將多個屬性定義在一個樣式中,而樣式又存放在資源中,資源成了樣式和物件的容器。另外WPF樣式還支援觸發器功能,本文中演示了屬性觸發器和事件觸發器的使用。在接下來一篇博文中將介紹WPF模板。

  本文所有原始碼:ResourceAndStyle.zip