打開博客園,查看首頁左欄的」推薦博客」,排名前五的博客分別是(此處非廣告):Artech、小坦克、聖殿騎士、騰飛(Jesse)、數據之巔。再看看它們博客的最新更新時間:Artech(2014-08-07) 、小坦克(2012-02-13)、聖殿騎士(2015-06-30)、騰飛(Jesse)(2013-12-18) 、數據之巔(2016-02-19) 。雖然數據之巔在最近發表了一篇博客,可是再看看倒數第二篇,更新時間是2015-11-18。從數據的現象看感受如今技術大牛們博客更新的愈來愈少,固然隨着技術沉澱到必定程度,大牛們可能各自的重點不會放在只研究技術上面。可是,我想說的是,如今咱們能深刻學習的資源確實愈來愈少了。最近幾年各類技術處在飛速發展中,浮躁的程序猿愈來愈多,大部分人都是處在熟練使用各類框架的程度,而能靜下心來研究某些框架的具體實現少之甚少。每每比較大型的公司,真正須要的人倒是那些不只懂框架並且能分析框架的人。題外話就說到這裏吧。。。git
先簡單羅列下此次系統寫的幾個簡單控件。首先聲明展現效果的目的是實現功能,請直接忽略各自的美觀感覺。首先是一個登陸界面,效果以下:github
MessageBox效果:框架
Window、可關閉Item的TabControl、可停靠的DockingPanel效果:ide
若是瞭解HTML和CSS,那麼WPF的Style的學習起來也比較容易,Style的設計思想就是仿照CSS來實現的。首先咱們看看WPF中一個Button實現:工具
<Button FontSize=」22」 Background=」Purple」 Foreground=」White」 Height=」50」 Width=」50」 RenderTransformOrigin=」.5,.5」>
一個Button的代碼和HTML元素內嵌樣式很是類似:oop
<Button Style="width:60px;heigth:50px;color:white;background:purple;font-size:22px" />
咱們知道THML可經過CSS把樣式提取出去,WPF中的Style一樣也能夠。在一個Window界面,咱們能夠在Window.Resources中添加獨立樣式:學習
<Style x:Key=」buttonStyle」> <Setter Property=」Button.FontSize」 Value=」22」/> <Setter Property=」Button.Background」 Value=」Purple」/> <Setter Property=」Button.Foreground」 Value=」White」/> <Setter Property=」Button.Height」 Value=」50」/> <Setter Property=」Button.Width」 Value=」50」/> <Setter Property=」Button.RenderTransformOrigin」 Value=」.5,.5」/> </Setter> </Style>
在聲明Style時,可標記TargetType聲明樣式應用到的元素類型。例如:字體
<Style x:Key=」buttonStyle」 TargetType=」{x:Type Button}」>
通常的Style咱們都會指定惟一的Key值,但有時候咱們能夠不指定Key,直接建立隱式樣式。例如:動畫
<Style TargetType=」{x:Type Button}」>
須要說明的是,這裏沒有指定Key並非說該Style沒有key值,其實在編譯過程當中,這總狀況的Style都會默認按照TargetType的類型默認指定一個Key值。而且應用到全部類型爲TargetType的元素上。網站
在實現Style時,通常都會涉及到觸發器,觸發器包括:Property triggers、Data triggers、Event triggers、MultiTrigger。接下來分別介紹下這四個觸發器。
(1)Property Trigger:只能應用到依賴屬性上,當某個依賴屬性的值的知足某些條件就會觸發某些改變。先看看代碼:
<Style x:Key=」buttonStyle」 TargetType=」{x:Type Button}」>
<Style.Triggers>
<Trigger Property=」IsMouseOver」 Value=」True」>
<Setter Property=」RenderTransform」>
<Setter.Value>
<RotateTransform Angle=」10」/>
</Setter.Value>
</Setter>
<Setter Property=」Foreground」 Value=」Black」/>
</Trigger>
</Style.Triggers>
</Style>
上面的觸發器實現的功能是,當鼠標移到Button上,字體顏色爲Black,而且旋轉10度。特別要注意的是,和HTML元素a:hover功能類似,觸發器也有回置的功能,就是說當鼠標移出Button時,Button的字體顏色和旋轉角度恢復到初始值。
(2)DataTrigger:和Property Trigger很是類似,但除了應用到依賴屬性外,DataTrigger可應用到任意的.Net屬性上。DataTrigger的代碼寫法和PropertyTrigger仍是有區別的,它是經過Bingding來綁定須要判斷的屬性。例如:
<StackPanel Width=」200」> <StackPanel.Resources> <Style TargetType=」{x:Type TextBox}」> <Style.Triggers> <DataTrigger Binding=」{Binding RelativeSource={RelativeSource Self}, Path=
Text}」 Value=」disabled」> <Setter Property=」IsEnabled」 Value=」False」/> </DataTrigger> </Style.Triggers> <Setter Property=」Background」 Value=」{Binding RelativeSource={RelativeSource Self}, Path=Text}」/> </Style> </StackPanel.Resources> <TextBox Margin=」3」/> </StackPanel>
TextBox的Text屬性是一個非依賴屬性,若是Text的值爲disabled,設置TextBox的IsEanbled爲false。DataTrigger是經過Binding來綁定TextBox的Text屬性。
(3)EventTrigger:當元素某些事件被觸發時,可執行某些動畫或者元素的某些屬性發生變化,這些動做可經過EventTrigger實現。看看下面的代碼:
<Button.Triggers> <EventTrigger RoutedEvent=」Button.Click」> <EventTrigger.Actions> <BeginStoryboard> <Storyboard TargetProperty=」Width」> <DoubleAnimation From=」50」 To=」100」 Duration=」0:0:5」 AutoReverse=」True」/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Button.Triggers>
上面的代碼實現功能是,當用戶點擊按鈕時,觸發一個動畫,按鈕的Width在5秒鐘的時間內從50增長到100個像素。
(4)MultiTrigger:前面介紹的觸發器都是當元素的某一個屬性或者事件觸發時操做。有些時候,咱們須要判斷多個條件是否同時知足,才執行某個操做。這個功能可經過MultiTrigger實現。例如,咱們的Button按鈕,當鼠標移到上面而且獲取焦點時,設置字體顏色爲Black和旋轉度爲10度。實現代碼以下:
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property=」IsMouseOver」 Value=」True」/>
<Condition Property=」IsFocused」 Value=」True」/>
</MultiTrigger.Conditions>
<Setter Property=」RenderTransform」>
<Setter.Value>
<RotateTransform Angle=」10」/>
</Setter.Value>
</Setter>
<Setter Property=」Foreground」 Value=」Black」/>
</MultiTrigger>
</Style.Triggers>
經常在設計一個系統時,都會實現一套本身的UI界面,有些時候咱們不得不重寫樣式比較簡單的WPF Window界面。Template給開發者提供徹底重寫界面控件的技術,例如咱們能夠實現本身的窗口TitleBar,而且在上面添加菜單功能。咱們在重寫了控件UI的同時也保留了控件的各類功能。模板在分割可視化和邏輯上也功不可沒,例如一些界面的樣式交互效果咱們徹底能夠在模板中實現,而源代碼部分徹底只考慮業務邏輯。咱們看看下面一個常規的Button樣式模板代碼:
<ControlTemplate x:Key=」buttonTemplate」 TargetType=」{x:Type Button}」> <Grid> <Ellipse x:Name=」outerCircle」 Width=」100」 Height=」100」> <Ellipse.Fill> <LinearGradientBrush StartPoint=」0,0」 EndPoint=」0,1」> <GradientStop Offset=」0」 Color=」Blue」/> <GradientStop Offset=」1」 Color=」Red」/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <Ellipse Width=」80」 Height=」80」> <Ellipse.Fill> <LinearGradientBrush StartPoint=」0,0」 EndPoint=」0,1」> <GradientStop Offset=」0」 Color=」White」/> <GradientStop Offset=」1」 Color=」Transparent」/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> </Grid> <ControlTemplate.Triggers> <Trigger Property=」IsMouseOver」 Value=」True」> <Setter TargetName=」outerCircle」 Property=」Fill」 Value=」Orange」/> </Trigger> <Trigger Property=」IsPressed」 Value=」True」> <Setter Property=」RenderTransform」> <Setter.Value> <ScaleTransform ScaleX=」.9」 ScaleY=」.9」/> </Setter.Value> </Setter> <Setter Property=」RenderTransformOrigin」 Value=」.5,.5」/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
上面的代碼徹底重寫了Button的默認樣式,默認的Button是一個矩形,而如今變成了一個Ellipse橢圓形。而且使用漸變的顏色填充橢圓。咱們知道默認的Button當咱們鼠標移上去或者被單擊時都會有樣式變化。因此,咱們還得給Button添加觸發器,這裏咱們使用的是Property Trigger。當鼠標移到Button上(IsMouseOver=true),設置橢圓的填充顏色爲Orange。當按鈕被按下時(IsPressed=true),改變漸變顏色的起始位置。
一個比較完善的系統都提供了切換系統主題的功能, 咱們就拿Window 10系統來講,看看下面的截圖:
Window 10系統爲用戶提供了不少主題選擇,當咱們選擇不一樣的主題,系統的菜單或者窗口的顏色都會發生變化,可是界面的結構是沒有改變的。WPF的主題也徹底同樣。當界面開發人員重寫玩控件的模板後,通常都會把模板中寫某些資源提取出來,放到公共的資源文件中去。這些被提取出來的資源通常包括:界面背景色、邊框顏色、字體顏色、以及圖片等。咱們先看一個TabControl的自定義模板:
<Style x:Key="CustomTabControlStyle" TargetType="{x:Type control:CustomTabControl}" BasedOn="{StaticResource {x:Type TabControl}}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type control:CustomTabControl}"> <DockPanel LastChildFill="True"> <Border DockPanel.Dock="Top" Background="{DynamicResource Tab_Control_Background_Normal}" BorderBrush="{DynamicResource CustomControlBorderBrush}" BorderThickness="1, 1, 1, 0"> <TabPanel Margin="0,0,0,0" IsItemsHost="True" /> </Border> <Border Background="White" BorderBrush="{DynamicResource CustomControlBorderBrush}" BorderThickness="1"> <ContentPresenter ContentSource="SelectedContent" /> </Border> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> ... </Style.Triggers> </Style>
代碼中,Border的背景色動態的使用了Tab_Control_Background_Normal資源,邊框顏色使用CustomControlBorderBrush資源。而這些被引用的資源都是單獨存放在一個資源文件中。這個資源文件的部分代碼以下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- Common Style --> <SolidColorBrush x:Key="CustomControlBackground" Color="#EDEDED" /> <SolidColorBrush x:Key="CustomControlBorderBrush" Color="#b9bac1" /> <!-- Button Style --> <LinearGradientBrush x:Key="ButtonBackgroundColorBusrh" EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#fdfdfd" Offset="0"/> <GradientStop Color="#f5f5f5" Offset="0.5"/> <GradientStop Color="#e9e9e9" Offset="1"/> </LinearGradientBrush> </ResourceDictionary>
基礎部分就先簡單的介紹到這裏,接下來咱們看看WPF的UI開發須要那些技能。
1.Blend :要作WPF的界面開發,Blend是必備的技能。經過Blend能夠很方便的畫出UI,而且能夠經過手動的方式添加動畫效果。Blend的界面和Visual Studio很類似,因此上手也比較快。
2.PhotoShop:不論是作Web前段仍是WPF前段,使用PhotoShop切圖以及簡單的設計某些圖片效果的技能也是須要掌握的。而且界面開發人員只掌握到這個程度就能夠了。
3.Snoop:是一個捕獲WPF界面層次結構的工具,經過簡單的操做就能夠看到咱們開發的WPF界面的完整層次結構,而且可以看到每層元素的屬性值。當界面出現某些問題時,經過Snoop分析界面可達到事半功倍的效果。另外,在經過Visual Studio調試代碼時,經過監視功能也能看到界面的層次結構。
4.素材資源:這裏提供一些資源網站Icon Find(http://findicons.com/)、Icon Finder(https://www.iconfinder.com/)。
UI界面的工程通常包括:Component和Theme兩個工程。自定義控件通常都是添加在Component工程裏邊,而控件的樣式模板都會添加到Theme工程裏邊。
先看下咱們的Component工程,工程名稱爲HeaviSoft.FrameworkBase.Component。工程結構以下:
若是咱們新增一個WPF的Custom Control,Visual Studio會自動在工程下面建立/Themes/Generic.xaml文件。而且自動在Generic.xaml文件中建立一個簡單的控件模板。代碼以下:
<Style TargetType="{x:Type docking:CustomDocumentPanel}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type docking:CustomDocumentPanel}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
上面的模板展現出來就是一片空白的效果,什麼都有沒有。因此咱們得重寫Template。其實稍後咱們在Theme會重寫這些控件的模板,但爲何這裏也須要重寫?Component下寫的模板是控件的默認模板。但咱們沒有任何主題時,就顯示該默認模板。
只要建立一個控件,Generic.xaml中就會增長一個TargetType爲控件類型的模板,若是增長個10個控件,Generic.xaml中就增長了10個Style,代碼量比較龐大。因此,我都會單獨按照控件的名稱在Theme下單首創建一個同名的資源文件。並把Generic.xaml中的對應樣式移動到這個同名資源文件。而Generic.xaml中值用存放資源引用路徑便可。例如:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:HeaviSoft.FrameworkBase.Component" xmlns:docking="clr-namespace:HeaviSoft.FrameworkBase.Component.Docking"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/HeaviSoft.FrameworkBase.Component;component/Themes/CustomWindow.xaml" /> <ResourceDictionary Source="/HeaviSoft.FrameworkBase.Component;component/Themes/CustomTextBox.xaml" /> <ResourceDictionary Source="/HeaviSoft.FrameworkBase.Component;component/Themes/CustomMessageBox.xaml" /> <ResourceDictionary Source="/HeaviSoft.FrameworkBase.Component;component/Themes/CustomTabControl.xaml" /> <ResourceDictionary Source="/HeaviSoft.FrameworkBase.Component;component/Themes/CustomTabItem.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary>
Component工程結構介紹完了,接下來看Theme工程。咱們的Theme工程命名爲HeaviSoft.FrameworkBase.Theme。工程結構以下:
工程包含CustomControl和Themes兩個文件夾,CustomControl文件夾下有多個以控件名稱命名的.xaml文件,例如Button.xaml文件,它裏邊的代碼就是真正咱們本身實現的Button模板。Themes下包含一個GrayWhite文件夾,這個文件夾就是咱們的一個主題,它下面存放了各個控件模板須要的資源,包括圖片、顏色、字體等。例如,咱們在CustomControl下添加了CustomWindow界面模板,界面右上角須要關閉、最大化、最小化按鈕圖片。這些圖片資源存放在/Themes/GrayWhite/Images/Window下。而CustomWindow須要的顏色以及字體資源分別存放在/Themes/GrayWhite/ColorBrush.xaml和/Themes/GrayWhite/Text.xaml中。同Component類似,/Themes/Generic.xaml的內容就是引用模板、圖片、顏色、字體資源文件。在系統啓動時,只須要動態加載/Themes/Generic.xaml文件就可加載全部的資源文件了。
UI控件的開發步驟咱們就拿實現自定義Window來舉例。先看看實現的效果:
自定義窗口包含兩個部分,TitleBar和Body。TitleBar從左到右分別包含了圖標、標題、最小化、最大化、關閉按鈕。而Body部分主要包含咱們在界面添加的內容。接下來就讓咱們一步步的去實現上圖的界面功能。
首先,選擇HeaviSoft.FrameworkBase.Component工程,添加Custom Control(WPF)。添加後,CustomWindow的默認代碼爲:
public class CustomWindow : Window { static CustomWindow() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow), new FrameworkPropertyMetadata(typeof(CustomWindow))); } }
CustomWindow類裏邊須要添加什麼內容咱們先無論。Visual Studio在添加CustomWindow文件的同時,會在/Themes/Generic.xaml中添加樣式:
<Style TargetType="{x:Type control:CustomWindow}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type control:CustomWindow}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
上面的樣式其實只是顯示一個空的Window界面,它在何時會被用到?當咱們沒有引用任何的主題時,默認加載該樣式。
接下來咱們切換到HeaviSoft.FrameworkBase.Theme工程,在CustomControl文件夾下添加資源文件CustomWindow.xaml。而後在/Themes/Generic.xaml中添加CustomWindow.xaml文件的引用。引用的代碼以下:
<ResourceDictionary Source="/HeaviSoft.FrameworkBase.Theme;component/CustomControl/CustomWindow.xaml" />
接下來咱們就在CustomWindow.xaml中實現自定義Window模板,這裏先給出代碼,而後慢慢分析:
<Style x:Key="WindowStyle" TargetType="{x:Type control:CustomWindow}"> <Setter Property="WindowStyle" Value="None" /> <Setter Property="AllowsTransparency" Value="True" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="ResizeMode" Value="NoResize" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type control:CustomWindow}"> <Border x:Name="MainBorder" Background="{DynamicResource CustomControlBackground}" CornerRadius="6" BorderBrush="{DynamicResource CustomControlBorderBrush}" BorderThickness="1" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition Height="1" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Border Grid.Row="0" Panel.ZIndex="101" x:Name="PART_TITLEBAR" BorderThickness="0" CornerRadius="6, 6, 0, 0" Background="{DynamicResource Window_TitleBar_Background}"> <DockPanel LastChildFill="False" > <Image DockPanel.Dock="Left" Source="{TemplateBinding Icon}" /> <Label VerticalAlignment="Center" DockPanel.Dock="Left" Content="{TemplateBinding Title}" Style="{DynamicResource TitleStyle}"/> <Button x:Name="PART_CLOSE" Style="{DynamicResource Window_Titlebar_ButtonStyle}" DockPanel.Dock="Right"> <Image x:Name="c" Margin="3, 0" Style="{DynamicResource TitleButtonImageStyle}" DockPanel.Dock="Right" Width="24" Height="24" Source="{DynamicResource Window_Button_Close_ImageBrush}" /> </Button> <Button x:Name="PART_MAXIMIZE_RESTORE" Style="{DynamicResource Window_Titlebar_ButtonStyle}" DockPanel.Dock="Right"> <Image Name="MaximizeRestoreImage" Margin="3, 0" Style="{DynamicResource TitleButtonImageStyle}" DockPanel.Dock="Right" Width="24" Height="24" Source="{DynamicResource Window_Button_Max_ImageBrush}" /> </Button> <Button x:Name="PART_MINIMIZE" Style="{DynamicResource Window_Titlebar_ButtonStyle}" DockPanel.Dock="Right"> <Image Margin="3, 0" Style="{DynamicResource TitleButtonImageStyle}" DockPanel.Dock="Right" Width="24" Height="24" Source="{DynamicResource Window_Button_Min_ImageBrush}" RenderTransformOrigin="0.5,0.5" > </Image> </Button> </DockPanel> </Border> <Border BorderBrush="{DynamicResource CustomControlBorderBrush}" BorderThickness="1" Grid.Row="1" /> <ContentPresenter Grid.Row="2" Margin="8, 5" /> </Grid> </Border> <ControlTemplate.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=WindowState}" Value="Maximized"> <Setter TargetName="MaximizeRestoreImage" Property="Source" Value="{DynamicResource Window_Button_Maximum_ImageBrush}" /> </DataTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
首先咱們不會使用Window默認的TitleBar,因此咱們要隱藏掉Window默認的TitleBar,經過設置:WindowStyle爲None就可隱藏掉標題欄和邊框。在以前看到的效果圖中,咱們看到Window有使用圓角效果,那麼必須設置三個屬性值:
<Setter Property="AllowsTransparency" Value="True" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="ResizeMode" Value="NoResize" />
AllowsTransparency爲True容許窗口透明,Background爲Transparent繼承父窗口的背景色(也即設置背景色爲透明)。ResizeMode爲NoResize禁止手動拖動窗口大小,隱藏虛線邊框。
而後設置Template的Value,通常都是直接建立一個ControlTemplate節點,並指定TargetType爲CustomWindow。
<ControlTemplate TargetType="{x:Type control:CustomWindow}"> ... </ControlTemplate>
接下就該添加模板的具體實現內容,首先咱們要設置有圓角的邊框,那麼咱們能夠在Root節點添加一個Border節點,設置背景色Background和邊框寬度BorderThickness、邊框顏色BorderBrush,並設置CornerRadius圓角度。代碼以下:
<Border x:Name="MainBorder" Background="{DynamicResource CustomControlBackground}" CornerRadius="6" BorderBrush="{DynamicResource CustomControlBorderBrush}" BorderThickness="1" > ... </Border>
咱們知道整個界面包含標題和內容,我還會在標題和內容之間添加一條分割線,因此咱們可經過一個包含三行的Grid來實現,第零行爲標題欄、第一行爲一條Bordrer實現的分割線、第二行就直接爲咱們的內容,可經過ContentPresenter存放這些內容。第一行和第二行的實現以下:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition Height="1" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> ...省略標題欄代碼 <Border BorderBrush="{DynamicResource CustomControlBorderBrush}" BorderThickness="1" Grid.Row="1" /> <ContentPresenter Grid.Row="2" Margin="8, 5" /> </Grid>
這裏須要說明的是,通常在寫自定義控件時,控件的內容可經過ContentPresenter來顯示。
其實整個界面實現比較複雜的部分是標題欄部分,咱們要顯示圖標、標題、最小化、最大化、關閉按鈕。圖標、標題靠左顯示,而最小化、最大化、關閉按鈕靠右顯示。這裏咱們想起了DockPanel面板,圖標和標題實現以下:
<DockPanel LastChildFill="False" > <Image DockPanel.Dock="Left" Source="{TemplateBinding Icon}" /> <Label VerticalAlignment="Center" DockPanel.Dock="Left" Content="{TemplateBinding Title}" Style="{DynamicResource TitleStyle}"/> ... </DockPanel>
顯示的圖標和標題咱們可經過TemplateBinding直接綁定Window的圖標和標題。DockPanel右邊主要是三個按鈕,咱們這裏拿關閉按鈕分析:
<Button x:Name="PART_CLOSE" Style="{DynamicResource Window_Titlebar_ButtonStyle}" DockPanel.Dock="Right"> <Image x:Name="c" Margin="3, 0" Style="{DynamicResource TitleButtonImageStyle}" DockPanel.Dock="Right" Width="24" Height="24" Source="{DynamicResource Window_Button_Close_ImageBrush}" /> </Button>
關閉按鈕是一個Button類型元素,它的內容就是顯示一個圖標,咱們知道默認的Button按鈕,自己有一些Normal、MouseOver、Press狀態的效果。但這些效果不是咱們想要的,因此必須得重寫,上面代碼咱們設置Button的Style爲Window_Titlebar_ButtonStyle就是重寫設置了Button的樣式,而嵌套的Image樣式也是引用的TitleButtonImageStyle樣式。Window_Titlebar_ButtonStyle的樣式代碼爲:
<Style x:Key="Window_Titlebar_ButtonStyle" TargetType="{x:Type ButtonBase}"> <Setter Property="BorderThickness" Value="0" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderThickness" Value="0" /> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Background" Value="Transparent" /> <Setter Property="BorderThickness" Value="0" /> </Trigger> </ControlTemplate.Triggers> <ContentPresenter … />
</ControlTemplate> </Setter.Value> </Setter> </Style>
代碼中,經過觸發器重寫了MouseOver和IsEnabled爲False狀態的顯示效果,CustomWindow.xaml模板就分析到這裏。
如今咱們只是實現了界面的可視化界面,當咱們點擊最小化按鈕時須要把窗口最小化,怎樣實現這樣的功能?咱們又不得不回到HeaviSoft.FrameworkBase.Component工程中添加的CustomWindow類,咱們能夠經過窗口的可視化層次關係找到最小化按鈕,在查找按鈕以前咱們必須知道它們的Name,因此通常咱們都會在類的頭部聲明模板部件名稱。以下所示:
/// <summary> /// 自定義界面 /// </summary> [TemplatePart(Name = "PART_TITLEBAR", Type = typeof(UIElement))] [TemplatePart(Name = "PART_CLOSE", Type = typeof(Button))] [TemplatePart(Name = "PART_MAXIMIZE_RESTORE", Type = typeof(Button))] [TemplatePart(Name = "PART_MINIMIZE", Type = typeof(Button))] public class CustomWindow : Window { }
上面的代碼可讓咱們很直觀的知道最小化按鈕的名稱爲PART_MINIMIZE,而後咱們聲明一個Button屬性:
/// <summary> /// 最小化按鈕 /// </summary> private Button MinimizeButton { get; set; }
接下來在什麼時機捕獲顯示的按鈕呢?確定是必須得等在HeaviSoft.FrameworkBase.Theme工程下添加的CustomWindow.xaml模板被加載完後才能查找,正好Control控件爲咱們提供了可重寫的方法OnApplyTemplate,咱們能夠在該方法中去遞歸遍歷查找控件。實現代碼以下:
public override void OnApplyTemplate() { base.OnApplyTemplate(); AttachToVisualTree(); } /// <summary> /// 附加可視化樹到模板 /// </summary> private void AttachToVisualTree() { AttachCloseButton(); AttachMinButton(); AttachMaximizeRestoreButton(); AttachTitleBar(); } /// <summary> /// 附加最小化按鈕 /// </summary> private void AttachMinButton() { if(MinimizeButton != null) { MinimizeButton.Command = null; } var minimizeButton = GetChildControl<Button>("PART_MINIMIZE"); if(minimizeButton != null) { minimizeButton.Command = MinimizedCommand; MinimizeButton = minimizeButton; } }
OnApplyTemplate方法調用了AttachToVisualTree方法,而AttachToVisualTree方法中又調用了AttachMinButton方法,AttachMinButton方法經過GetChildControl<Button>("PART_MINIMIZE")來查找模板中的最小化按鈕,找到以後綁定MinimizedCommand命令,命名的初始化代碼片斷以下:
<public CustomWindow() { CreateCommandBindings(); } /// <summary> /// 建立綁定命令 /// </summary> private void CreateCommandBindings() { CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (a, b) => { Close(); })); CommandBindings.Add(new CommandBinding(MinimizedCommand, (a, b) => { WindowState = WindowState.Minimized; })); CommandBindings.Add(new CommandBinding(MaximizeRestoreCommand, (a, b) => { WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; })); } /// <summary> /// 最小化指令 /// </summary> private readonly RoutedCommand MinimizedCommand = new RoutedUICommand("Minmize", "Minmize", typeof(CustomWindow)); /// <summary> /// 最大化復原指令 /// </summary> private readonly RoutedCommand MaximizeRestoreCommand = new RoutedUICommand("MaximizeRestore", "MaximizeRestore", typeof(CustomWindow));
代碼也很簡單,首先聲明一個Command,而後在CreateCommandBindings方法中爲聲明的Command綁定命名,好比最小化指令綁定的方法爲:(a, b) => { WindowState = WindowState.Minimized; },直接設置WindowState的值爲Minimized最小化。其餘的操做,例如最大化、關閉以及窗口的拖拽可直接查看源代碼瞭解,這裏就不在作分析了。
一個自定義控件的實現咱們差很少完成了90%,還剩一個任務就是把模板文件中的主題資源(例如顏色、圖片)提取到主題/Themes/下面去。例如前面實現的Window模板中,最小化、最大化等按鈕有使用圖片,比較好的設計確定是不會直接使用圖片路徑,因此咱們把這些資源提取到/Themes/ColorBrush.xaml資源文件中去。提取資以下:
<SolidColorBrush x:Key="Window_Title_Font_Color" Color="#77818b" /> <SolidColorBrush x:Key="Window_Font_Color" Color="#77818b" /> <SolidColorBrush x:Key="Window_Dark_Font_Color" Color="#fff" /> <BitmapImage x:Key="Window_Button_Close_ImageBrush" UriSource="Images/Window/close.png" /> <BitmapImage x:Key="Window_Button_Min_ImageBrush" UriSource="Images/Window/min.png" /> <BitmapImage x:Key="Window_Button_Max_ImageBrush" UriSource="Images/Window/max.png" /> <BitmapImage x:Key="Window_Button_Maximum_ImageBrush" UriSource="Images/Window/maximum.png" />
Window模板中咱們只使用這些Key值便可。而不關係圖片的具體路徑。這樣設計,之後咱們切換主題時就很方便。到目前爲止,怎樣完完整整的添加一個自定義控件就介紹完了。因爲代碼量比較大,因此在上面介紹時只貼出了部分代碼,完整的代碼可在GitHub上獲取。
能看到這裏的,也辛苦各位了。本片主要介紹了:
(1).WPF界面設計的幾個基礎技術點,包括Style、Template、Theme。
(2)界面設計的工具和資源,包括Blend、PhotoShop、Snoop以及網站資源。
(3)工程結構。
(4)自定義控件實現過程。
若是本篇內容對你們有幫助,請點擊頁面右下角的關注。若是以爲很差,也歡迎拍磚。大家的評價就是博主的動力!下篇內容,敬請期待!
完整的代碼存放在GitHub上,代碼路徑:https://github.com/heavis/Documentor_V01R01/。