天天看點

WPF基礎知識

0.前言

此文主要介紹WPF的基礎知識,文章會直接摘抄一些優秀文章的表達以及示例用于說明,如果侵犯了作者權利,請聯系我速删。編寫文章的目的在于記錄與分享,友善後續重溫與掌握。文章中可能會存在一些小問題,還望各位看官不吝指出。

1.XAML

1.1WPF簡介

WPF(Windows Presentation Foundation) 是建立桌面用戶端應用程式的UI架構。WPF開發平台支援廣泛的應用開發功能,包括應用模型、資源、控件、圖形、布局、資料綁定、文檔和安全性。WPF是.Net的一部分,WPF使用XAML(Extensible Application Markup Language)為應用程式設計提供聲明性模型。

1.2XAML

1.定義

可擴充應用程式标記語言 (XAML) 是一種基于XML 的聲明性語言,可用于建立應用程式UI,其廣泛用于以下類型的應用程式:

  • Windows Presentation Fundation(WPF)應用
  • 通用Windows平台(UWP)應用
  • Xamarin.Forms應用

2.優勢

将界面設計與邏輯編碼分離,例如,設計人員可以設計一個UI,然後将XAML檔案交給開發人員以添加邏輯代碼。即使設計人員和開發人員是同一個人(通常如此),程式員也可以将視覺内容儲存在XAML檔案(.xaml)中,而将邏輯代碼儲存在代碼隐藏檔案(.cs)中。

3.例子

下面的例子中涉及WPF中的一些概念,如布局控件、内容控件、x:字首、資料綁定等,這些概念後面都會介紹到,例子用于對 XAML 有個初步的認識。

<Window x:Class="WPFLearn.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:WPFLearn.Services"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="500" Loaded="Window_Loaded">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="234*"/>
            <RowDefinition Height="134*"/>
            <RowDefinition Height="67*"/>
        </Grid.RowDefinitions>
        <Canvas Grid.Row="0" Margin="0,0,0,67" Grid.RowSpan="3">
            <Ellipse x:Name="EllipseRed" Fill="Red" Width="100" Height="60" Canvas.Left="56" Canvas.Top="98" local:TurnoverManager.Angle="{Binding ElementName=SliderAngle,Path=Value}"/>
            <Rectangle x:Name="RectangleBlue" Fill="Blue" Width="80" Height="80" Canvas.Left="288" Canvas.Top="171" local:TurnoverManager.Angle="45"/>
            <Button x:Name="ButtonWelcome" Content="歡迎光臨" Canvas.Left="265" Canvas.Top="48" FontSize="20" local:TurnoverManager.Angle="60"/>
        </Canvas>
        <WrapPanel Grid.Row="2">
            <Label Content="角度大小"/>
            <Slider x:Name="SliderAngle" Minimum="0" Maximum="240" Width="300"/>
        </WrapPanel>
    </Grid>
</Window>
           
WPF基礎知識

1.2文法

XAML文法這一部分基本内容都是摘抄微軟官方的WPF中的 XAML 概述,隻不過進行了小部分的詞語修改以及增加Demo,降低了解難度。

1.對象元素

對象元素通常聲明類型的執行個體,該類型在将XAML 用作語言的技術所引用的程式集中定義。指定對象元素标記時,會建立一條指令,訓示XAML解析器建立基礎類型的新執行個體。每個執行個體都是在分析和加載 XAML時通過調用基礎類型的無參數構造函數來建立。

<Grid>
  <Button Content="Click Me!"/>
</Grid>
           
注解: Grid、Button都是對象元素。

2.特性文法(屬性)

(1)對象的屬性通常可表示為對象元素的特性。

(2)對象的特性文法為:property=“value”。

(3)特性的值始終指定為包含在引号中的字元串。

注解: Background=“Blue”、Foreground=“Red”、Content=“Click Me!” 都是屬性的特性文法。

3.屬性元素文法

對于對象元素的某些屬性,無法使用特性文法,因為無法在特性文法的引号和字元串限制内充分地為屬性值提供所必需的對象或資訊。 對于這些情況,可以使用另一個文法,即屬性元素文法。

<!--使用屬性文法代替特性文法-->
<Button>
    <Button.Background>
        <SolidColorBrush Color="Blue"/>
    </Button.Background>
    <Button.Foreground>
        <SolidColorBrush Color="Red"/>
    </Button.Foreground>
    <Button.Content>
        Click
    </Button.Content>
</Button>

<!--使用屬性文法建立漸變色-->
<Grid >
  <Rectangle Width="200" Height="200">
    <Rectangle.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
        <GradientStop Color="Yellow" Offset="0.0" />
        <GradientStop Color="Red" Offset="0.25" />
        <GradientStop Color="Blue" Offset="0.75" />
        <GradientStop Color="LimeGreen" Offset="1.0" />
      </LinearGradientBrush>
    </Rectangle.Fill>
  </Rectangle>
</Grid>
           

注解:

1.第一個 Button 使用了屬性文法來指派 Background、Foreground、Content ,最終效果與前一點的示例一樣。

2.第二個 Button 使用了屬性文法來建立漸變色,由于漸變色對象無法在雙引号中被表達,是以隻能使用這種方式給屬性指派。

3.如果可以使用特性文法給屬性指派的話,就選擇特性文法,這樣會使内容簡潔、編寫高效。

4.集合文法

XAML語言包含一些優化,可以生成更易于閱讀的标記。其中一項優化是:如果某個特定屬性采用集合類型,則在标記中聲明為該屬性值内的子元素的項目将成為集合的一部分。在這種情況下,子對象元素的集合是設定為集合屬性的值。

<!--顯式聲明集合屬性-->
<Grid >
  <Rectangle Width="200" Height="200">
    <Rectangle.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
        <GradientStopCollection>
            <GradientStop Color="Yellow" Offset="0.0" />
            <GradientStop Color="Red" Offset="0.25" />
            <GradientStop Color="Blue" Offset="0.75" />
            <GradientStop Color="LimeGreen" Offset="1.0" />
        </GradientStopCollection>
      </LinearGradientBrush>
    </Rectangle.Fill>
  </Rectangle>
</Grid>

<!--沒有聲明集合屬性,效果相同-->
<Grid >
  <Rectangle Width="200" Height="200">
    <Rectangle.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
        <GradientStop Color="Yellow" Offset="0.0" />
        <GradientStop Color="Red" Offset="0.25" />
        <GradientStop Color="Blue" Offset="0.75" />
        <GradientStop Color="LimeGreen" Offset="1.0" />
      </LinearGradientBrush>
    </Rectangle.Fill>
  </Rectangle>
</Grid>
           
注解: 第二個 Rectangle 設定漸變色時,雖然省略了集合元素 GradientStopCollection ,但 XAML 處理器依舊可以處理得到相同的結果。

5.内容屬性

XAML指定了一個語言功能,通過該功能,類可以指定它的有且僅有一個的屬性為XAML内容屬性。該對象元素的子元素用于設定該内容屬性的值。換言之,僅對内容屬性而言,可以在XAML标記中設定該屬性時省略屬性元素,并在标記中生成更直覺的父級/子級形式。

<!--顯示聲明内容屬性-->
<Border>
    <Border.Child>
        <TextBox Width="300"/>
    </Border.Child>
</Border>
<!--沒有聲明内容屬性,效果相同-->
<Border>
    <TextBox Width="300"/>
</Border>
           
注解: 本質同集合文法類似,都是 XAML 處理器進行了一些優化,相容支援了多種寫法,此處是省略表示内容屬性的 Border.Child 元素。

6.文本内容

有少量XAML元素可直接将文本作為其内容來處理。若要實作此功能,必須滿足以下條件之一:

  • 類必須聲明一個内容屬性,并且該内容屬性必須是可指派給字元串的類型(該類型可以是Object)。
  • 類型必須聲明一個類型轉換器,該類型轉換器将文本内容用作初始化文本。
  • 類型必須為已知的XAML語言基元。
<Button>Hello</Button>
<Brush>Blue</Brush>
           
注解: 本質都是XMAL處理器的優化處理。

7.特性文法(事件)

特性文法還可用于事件成員,而非屬性成員。 在這種情況下,特性的名稱為事件的名稱。在XAML事件的WPF實作中,特性的值是實作該事件的委托的處理程式的名稱。

<Button Click="Button_Click" >
    Click Me!
</Button>
           
注解: 上述的 XAML 檔案關聯的 CS 檔案中,定義了名稱為 Button_Click 的函數。

8.大小寫和空白

一般而言,XAML區分大小寫。XAML中的值并不總是區分大小寫:值是否區分大小寫将取決于與采用該值的屬性關聯的類型轉換器行為,或取決于屬性值類型。例如,采用Boolean類型的屬性可以采用true或True作為等效值,但隻是因為将字元串轉換為Boolean的本機WPF XAML分析程式類型轉換已經允許将這些值作為等效值。

WPF XAML處理器和序列化程式将忽略或删除所有無意義的空白,并标準化任何有意義的空白,即XAML将空格、換行符和制表符轉化為空格,如果它們在一個字元串的任一端連續出現,則保留一個空格。

9.标記擴充

标記擴充是一個 XAML 語言概念。在提供特性文法的值時,用大括号({ 和 })表示标記擴充用法。 此用法訓示 XAML 處理器不要像通常那樣将特性值視為文本字元串或者可轉換為字元串的值。

WPF 應用程式設計中最常用的标記擴充是 Binding(用于資料綁定表達式)以及資源引用 StaticResource 和 DynamicResource 。通過使用标記擴充,即使屬性通常不支援特性文法,也可以使用特性文法為屬性提供值。标記擴充經常使用中間表達式類型實作一些功能,例如,引用僅在運作時才存在的值。

<Window x:Class="index.Window1"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="100" Width="300">
    <Window.Resources>
        <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
        <Style TargetType="Border" x:Key="PageBackground">
            <Setter Property="BorderBrush" Value="Blue"/>
            <Setter Property="BorderThickness" Value="5" />
        </Style>
    </Window.Resources>
    <Border Style="{StaticResource PageBackground}">
        <StackPanel>
            <TextBlock Text="Hello" />
        </StackPanel>
    </Border>
</Window>
           
注解: 以上标記使用特性文法設定 Style 屬性的值。Style 屬性采用 Style 類的執行個體,該類在預設情況下無法由特性文法字元串進行執行個體化。但在本例中,特性引用了特定的标記擴充 StaticResource 。XAML 處理器處理該标記擴充時,将傳回在資源字典中執行個體化的某個樣式的引用。

10.根元素和命名空間

一個 XAML 檔案隻能有一個根元素,以便同時作為格式正确的 XML 檔案和有效的 XAML 檔案。對于典型 WPF 方案,可使用在 WPF 應用模型中具有突出意義的根元素(例如,頁面的 Window 或 Page 、外部字典的 ResourceDictionary 或應用定義的 Application )。

根元素還包含特性 xmlns 和 xmlns:x 。這些特性向 XAML 處理器訓示哪些 XAML 命名空間包含标記将其作為元素引用的後備類型的類型定義。 xmlns 特性明确訓示預設的 XAML 命名空間。在預設的 XAML 命名空間中,可以不使用字首指定标記中的對象元素。對于大多數 WPF 應用程式方案以及 SDK 的 WPF 部分中給出的幾乎所有示例,預設的 XAML 命名空間均映射到 WPF 命名空間http://schemas.microsoft.com/winfx/2006/xaml/presentation。xmlns:x 特性訓示另一個 XAML 命名空間,該命名空間映射 XAML 語言命名空間http://schemas.microsoft.com/winfx/2006/xaml。

隻有在每個 XAML 檔案的根元素上, xmlns 特性才是絕對必需的。 xmlns 定義将應用于根元素的所有後代元素。 xmlns 屬性也允許位于根下的其他元素上,并将應用于定義元素的任何後代元素。 但是,頻繁定義或重新定義 XAML 命名空間會導緻難以閱讀 XAML 标記樣式。

<Page x:Class="index.Page1"
	  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"      
 	  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Page1">
</Page>
           

11.x:字首

常用的x:字首如下:

字首 作用
x:Key 為(或其他架構中的類似字典概念)每個資源設定唯一的鍵。
x:Class 向為XAML頁提供代碼隐藏的類指定CLR命名空間和類名。必須具有這樣一個類才能支援每個WPF程式設計模型的代碼隐藏。
x:Name 處理對象元素後,為運作時代碼中存在的執行個體指定運作時對象名稱。通常,經常為 x:Name 使用 WPF 定義的等效屬性。此類屬性特定映射到 CLR 後備屬性,是以更便于進行應用程式設計,在應用程式設計中,經常使用運作時代碼從初始化的 XAML 中查找命名元素。 最常見的此類屬性是 FrameworkElement.Name 。在特定類型中不支援等效的 WPF 架構級屬性時,仍然可以使 x:Name 。
x:Type 根據類型名稱構造引用。用于指定采用 Type(例如 Style.TargetType)的特性,但屬性經常具有本機的字元串到 Type 的轉換功能,是以使用 Type 标記擴充用法是可選的。
x:Null 在XAML中指定null值。

注解:

1.指定 Name 的兩種方式:Name = “buttonName” 和 x:Name = “buttonName” 。

2.指定 Type 的兩種方式:TargetType = “{x:Type Button}” 和 TargetType = “Button” 。

3.x:Null 用于顯示指定控件樣式為空,來避免指定了類型的樣式的全局應用。

12.自定義字首和自定義類型

對于自身的自定義程式集或 PresentationCore、PresentationFramework 和 WindowsBase 的 WPF 核心以外的程式集,可以将該程式集指定為自定義映射的一部分。隻要該類型能夠正确地實作以支援正在嘗試的 XAML 用法,就可以在 XAML 中引用該程式集中的類型。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:NumericUpDownCustomControl;assembly=CustomLibrary"
>
  <StackPanel Name="LayoutRoot">
    <custom:NumericUpDown Name="numericCtrl1" Width="100" Height="60"/>
  </StackPanel>
</Page>
           

13.附加屬性和附加事件

XAML 指定了一個語言功能,該功能允許對任何元素指定某些屬性或事件,即使要設定屬性或事件的元素的類型定義中不存在該屬性或事件。該功能的屬性版本稱為附加屬性,事件版本稱為附加事件。從概念上講,可以将附加屬性和附加事件視為可以在任何 XAML 元素/對象執行個體上設定的全局成員。

附加屬性的最常見方案是使子元素向其父元素報告屬性值。

<DockPanel Button.Click="DockPanel_Click">
    <Button DockPanel.Dock="Left" Width="100" Height="20">
        I am on the left
    </Button>
    <Button DockPanel.Dock="Right" Width="100" Height="20">
        I am on the right
    </Button>
</DockPanel>
           

注解:

1.上述中的 DockPanel 元素在自身中給 Button.Click 屬性事件指派,而這個屬性是不屬于 DockPanel 元素的,這便是附加事件。在父元素中處理子元素的事件也展現了 WPF 的另一個特性,即路由事件。路由事件就是在WPF中,事件會按照元素樹向上或向下傳遞,直到被設定了已處理的辨別符。

2.上述中的 Button 元素在自身中給 DockPanel.Dock 屬性指派,而這個屬性是不屬于 Button 元素的,這便是附加屬性。

2.控件

2.1控件分類

WPF中所有控件都繼承自Control,根據用途不同分為四種類型:布局控件(Panel Controls)、内容控件(Content Controls)、條目控件(Items Controls)、特殊控件(Special Controls)。

2.1.1布局控件

WPF的布局控件都繼承于System.Windows.Controls.Panel,常用的布局控件有:Canvas、StackPanel、WrapPanel、DockPanel、Grid、ScrollViewer。

1.Canvas

Canvas 是一個類似于坐标系的面闆,所有的元素通過設定坐标來決定其在坐标系中的位置.。具體表現為使用 Left、Top、Right、 Bottom 附加屬性在 Canvas 中定位控件。

Canvas 預設不會自動裁剪超過自身範圍的内容,即溢出的内容會顯示在 Canvas 外面,可以設定 ClipToBounds 屬性來裁剪多出的内容。

<Canvas Margin="10,10,10,10" Background="White" >
    <Rectangle Name="rect" Canvas.Left="300" Canvas.Top="180" Fill="Black" Stroke="Red"  Width="200" Height="200"/>
    <Ellipse  Name="el" Canvas.Left="160" Canvas.Top="150" Fill="Azure" Stroke="Green" Width="180" Height="180"/>
</Canvas>
           

2.StackPanel

StackPanel 将控件按照行或列來順序排列,但不會換行。通過設定面闆的 Orientation 屬性控制兩種排列方式:橫排(Horizontal)和豎排(Vertical),預設為豎排(Vertical)。

<StackPanel Margin="10,10,10,10" Background="Azure">
    <Label>A Button Stack</Label>         
    <Button Content="Button 1"></Button>  
    <Button>Button 2</Button> 
    <Button>Button 3</Button>
    <Button Content="Button 4"></Button>
</StackPanel>
           

3.WrapPanel

WrapPanel 布局面闆将各個控件按照一定方向羅列,當長度或高度不夠時自動調整進行換行換列。

  • Orientation=“Horizontal” 時各控件從左至右羅列,當面闆長度不夠時,子控件就會自動換行,繼續按照從左至右的順序排列。
  • Orientation=“Vertical” 時各控件從上至下羅列,當面闆高度不夠時,子控件就會自動換列,繼續按照從上至下的順序排列。
<WrapPanel Margin="10" Background="Azure">
    <Button VerticalAlignment="Top" Margin="5">
        Top Button
    </Button>
    <Button MinHeight="50">
        Tall Button
    </Button>
    <Button VerticalAlignment="Bottom">
        Bottom Button
    </Button>
    <Button>
        Stretch Button
    </Button>
    <Button VerticalAlignment="Center">
        Center Button
    </Button>
    <Button>
        Next Button
    </Button>
</WrapPanel>
           

4.DockPanel

DockPanel 支援讓元素簡單地停靠在整個面闆的某一條邊上,然後拉伸元素以填滿全部寬度或高度。它也支援讓一個元素填充其他已停靠元素沒有占用的剩餘空間。

DockPanel 有一個 Dock 附加屬性,是以子元素用4個值來控制她們的停靠:Left、Top、Right、Bottom 。最後的子元素預設填滿所有剩餘的空間,除非 DockPanel 的 LastChildFill 屬性為 false 。

<DockPanel>
    <Button Content="Button左" DockPanel.Dock="Left"/>
    <Button Content="Button右" DockPanel.Dock="Right"/>
    <Button Content="Button上" DockPanel.Dock="Top"/>
    <Button Content="Button下" DockPanel.Dock="Bottom"/>
</DockPanel>
           

5.Grid

Grid允許我們通過自定義行列來進行布局,這類似于表格。

(1)基本使用

  • 通過定義Grid的RowDifinitions和ColumnDifinitions來實作對于表格行和列的定義。
  • 元素根據附加屬性Grid.Row和Grid.Column确定自己的位置。
  • 元素根據附加屬性Grid.RowSpan和Grid.ColumnSpan占用多行/多列空間。

(2)定義列寬與行高

  • 固定長度:屬性值為确定的數字。
  • 自動長度:屬性值為 Auto 。
  • 比例長度:屬性值為 (number)* ,當 number 為空時表示占用剩餘空間,否則按照不同行/列的 number 值進行比例計算後配置設定空間。

(3)實作拖動方式更改區域尺寸的功能

使用GidSplitter可以實作Grid行列尺寸重新配置設定。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="6"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Content="Button"/>
    <Button Grid.Row="1" Content="Button"/>
    <GridSplitter  Grid.Row="2" HorizontalAlignment="Stretch"/>
    <Button Grid.Row="3" Grid.RowSpan="2" Content="Button"/>
</Grid>
           

6.ScrollViewer

ScrollViewer 是帶有滾動條的面闆。在 ScrollViewer 中隻能有一個子控件,若要顯示多個子控件,需要先将一個附加的 Panel 控件放置在 ScrollViewer 中,然後可以将子控件放置在該控件中。

  • HorizontalScrollBarVisibility 屬性設定水準滾動條是否顯示,可選值為 Hidde/Visible/Auto ,預設值為 Hidden ,一般選擇 Auto 。
  • VerticalScrollBarVisibility 屬性設定垂直滾動條是否顯示,可選值為 Hidde/Visible/Auto ,預設值為 Visible ,一般選擇 Auto 。
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
    <Button Content="Button" Width="800" Height="800"/>
</ScrollViewer>
           

2.1.2内容控件

内容控件都繼承自 ContentControl ,隻能容納一個其他控件作為其内容(需要容納多個控件時,使用布局控件存放多個控件),内容控件根據是否帶标題加以細分:

  • 不帶标題的内容控件:共同父類是 ContentControl ,常見的控件有:Label、Button 和 Tooltip 等。
  • 帶标題的内容控件:共同父類是

    HeaderedContentControl (繼承自 ContentControl ),常見的控件有:TabItem、GroupBox 和 Expander 等。

2.1.3條目控件

條目控件都繼承自 ItemsControl ,可以顯示一列資料,條目控件根據是否帶标題加以細分:

  • 不帶标題的條目控件:共同父類是 ItemsControl ,常見的控件有:ListBox、Menu 和 StatusBar 等。
  • 帶标題的條目控件:共同父類是

    HeaderedItemsControl (繼承自 ItemsControl ),常見的控件有 MenuItem、TreeViewItem 和 ToolBar等。

2.1.4特殊控件

特殊控件沒有直接的共同父類,這類控件相對比較獨立,常見的控件有:TextBox、TextBlock、Border 和 Image 等。其中,Border 控件是為了實作類似圓角按鈕這樣的功能,WPF 中的自帶按鈕本身沒有圓角這一屬性。

2.1.5綜合示例

<Window x:Class="WPFLearn.Views.Control.ControlDemo"
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:WPFLearn.Views.Control"
mc:Ignorable="d"
Title="ControlDemo"
Height="500" Width="800">
    <StackPanel Margin="5">
        <GroupBox Header="不帶标題的内容控件">
            <Label Content="我是Labe控件"/>
        </GroupBox>
        <GroupBox Header="帶标題的内容控件" Margin="0,10,0,0">
            <Expander Header="折疊/展開">
                <Label Content="可以被折疊的内容"/>
            </Expander>
        </GroupBox>
        <GroupBox Header="不帶标題的條目控件" Margin="0,10,0,0">
            <ListBox>
                <ListBoxItem Content="C++"/>
                <ListBoxItem Content="C#"/>
                <ListBoxItem Content="Java"/>
            </ListBox>
        </GroupBox>
        <GroupBox Header="帶标題的條目控件" Margin="0,10,0,0">
            <Menu>
                <MenuItem Header="檔案">
                    <MenuItem Header="建立"/>
                    <Separator/>
                    <MenuItem Header="儲存"/>
                    <MenuItem Header="另存為"/>
                </MenuItem>
            </Menu>
        </GroupBox>
        <GroupBox Header="特殊控件" Margin="0,10,0,0">
            <StackPanel>
                <TextBox Height="60" BorderBrush="DarkGray"/>
                <Image Source="/uires/icon.png" Stretch="None" HorizontalAlignment="Left"/>
                <Border BorderBrush="DarkOrange" BorderThickness="1" CornerRadius="5" Height="60" HorizontalAlignment="Left" Padding="5">
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Height="30">
                        <Button Width="90">
                            按鈕1
                        </Button>
                        <Button Width="90" Margin="10,0,0,0">
                            按鈕2
                        </Button>
                        <Button Width="90" Margin="10,0,0,0">
                            按鈕3
                        </Button>
                    </StackPanel>
                </Border>
            </StackPanel>
        </GroupBox>
    </StackPanel>
</Window>
           

2.2控件外觀

XAML 中可以通過三種方式更改控件的外觀:

  • 為控件的外觀屬性指派。
  • 為控件建立Style。
  • 為控件建立ControlTemplate。

2.2.1更改外觀屬性值

許多控件具有支援更改控件外觀的屬性,常見外觀屬性有:

  • Margin:控件外邊距。
  • Padding:控件内邊距。
  • Background:背景顔色,顔色的屬性值格式支援常見顔色名稱和16進制顔色數值。
  • BorderBursh:邊框顔色。
  • BorderThickness:邊框厚度。

2.2.2建立Style

WPF 支援通過建立樣式來指定控件的外觀,以實作批量設定執行個體外觀的效果。

  • 任意元素都有 Resources 屬性,在該屬性元素下可以建立任意可執行個體化的資源,比如 WPF 程式集中的類型/C#中的基礎類型/…
  • 可以在任意 Resources 節點下用 Style 标簽建立樣式。
  • Style 元素的 TargetType 屬性可以用兩種方式表達:TargetType = “Button” 和 TargetType = {x:Type Button}。
  • 如果 Style 元素沒有顯示指定 TargetType ,則在元素中使用屬性時需要加上 Control 字首。
  • 如果 Style 元素隻指定 TargetType,而沒有指定 x:Key,則樣式會自動給樣式定義後該類型的所有元素。如果需要取消引用,需要顯示指定 Style = {x:Null} 。
  • 通過使用屬性 BasedOn 實作樣式的繼承。

2.2.3建立ControlTemplate

在 WPF 中,控件的 ControlTemplate 用于定義控件的外觀。可以通過定義新的 ControlTemplate 并将其配置設定給控件來更改控件的結構和外觀。在許多情況下,模闆提供了足夠的靈活性,進而無需自行編寫自定義控件。實作方式:

  • 通用:在 Resources 節點下定義 ControlTemplate 元素,然後給控件的 Template 屬性設定ControlTemplate 元素的 Key 值。
  • 封裝:定義樣式時,設定 Template 屬性,其值用屬性元素方式表達 ControlTemplate 元素。

2.3.4綜合示例

<Window x:Class="WPFLearn.Views.Control.ControlAppearanceDemo"
        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:WPFLearn.Views.Control"
        mc:Ignorable="d"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="控件外觀示範" Height="450" Width="800">
    <Window.Resources>
        <!--###沒有x:Key屬性的Style将應用到所有類型為TargetType的執行個體-->
        <!--<Style TargetType="Button">
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="16"/>
        </Style>-->

        <!--###在Resources節點下建立樣式,以實作批量設定效果-->
        <Style x:Key="StyleButton" TargetType="{x:Type Button}">
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="16"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="FontWeight" Value="Bold"/>
                </Trigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="StyleButtonWithControlTemplate" TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse Name="EllipseSection" Fill="Orange" Width="100" Height="100"/>
                            <ContentPresenter Content="{TemplateBinding Button.Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="EllipseSection" Property="Fill" Value="Yellow"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <!--###單獨定義ControlTemplate,然後再設定Button.Template屬性-->
        <ControlTemplate TargetType="Button" x:Key="ControlTemplateButton">
            <Grid>
                <Ellipse Name="EllipseSection" Fill="Orange" Width="100" Height="100"/>
                <ContentPresenter Content="{TemplateBinding Button.Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="EllipseSection" Property="Fill" Value="Yellow"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>
    <StackPanel>
        <GroupBox Header="更改外觀屬性值">
            <StackPanel Orientation="Horizontal">
                <StackPanel.Resources>
                    <!--###在任意元素的Resources元素下建立資源,以實作值定義效果-->
                    <FontFamily x:Key="ButtonFontFamily">Times New Roman</FontFamily>
                    <sys:Double x:Key="ButtonFontSize">16</sys:Double>
                </StackPanel.Resources>
                <Button FontFamily="Times New Roman" FontSize="16" >
                    StyledButton1
                </Button>
                <Button FontFamily="Times New Roman" FontSize="16" Margin="10,0,0,0">
                    StyledButton2
                </Button>
                <Button FontFamily="{StaticResource ButtonFontFamily}" FontSize="{StaticResource ButtonFontSize}" Margin="10,0,0,0">
                    StyledButton3
                </Button>
                <Button Content="StyledButton4" Margin="10,0,0,0">
                    <Button.Style>
                        <Style>
                            <Style.Setters>
                                <Setter Property="Control.FontFamily" Value="{StaticResource ButtonFontFamily}"/>
                                <Setter Property="Control.FontSize" Value="{StaticResource ButtonFontSize}"/>
                            </Style.Setters>
                            <Style.Triggers>
                                <Trigger Property="Control.IsMouseOver" Value="True">
                                    <Setter Property="Control.FontWeight" Value="Bold"/>
                                </Trigger>
                            </Style.Triggers>
                        </Style>
                    </Button.Style>
                </Button>
            </StackPanel>
        </GroupBox>
        <GroupBox Header="建立Style實作批量修改外觀屬性" Margin="0,10,0,0">
            <StackPanel Orientation="Horizontal">
                <Button Content="StyledButton1"/>
                <Button Style="{x:Null}" Content="StyledButton2" Margin="10,0,0,0"/>
                <Button Style="{StaticResource StyleButton}" Content="StyledButton3" Margin="10,0,0,0"/>
            </StackPanel>
        </GroupBox>
        <GroupBox Header="建立ControlTemplate實作自定義控件外觀" Margin="0,10,0,0">
            <StackPanel Orientation="Horizontal">
                <Button Content="NormalButton" VerticalAlignment="Center"/>
                <Button Content="NewButton1" Style="{StaticResource StyleButtonWithControlTemplate}" Margin="10,0,0,0"/>
                <Button Content="NewButton2" Template="{StaticResource ControlTemplateButton}" Margin="10,0,0,0"/>
            </StackPanel>
        </GroupBox>
    </StackPanel>
</Window>
           

注解:

1.在 StackPanel.Resources 節點下定義了 FontFamily 類型資源和 sys:Double 類型資源,然後在 Button 元素中通過标記擴充文法引用上述資源,引用文法為 FontFamily="{StaticResource ButtonFontFamily}" FontSize="{StaticResource ButtonFontSize}"。通過這種方式,後續需要修改屬性值時,隻需要在資源定義處修改,不必改動到多處。

2.如果需要設定控件動态樣式,如滑鼠移到上面時的樣式、滑鼠點選時的樣式,則需要在 Style 元素下使用屬性元素的文法來設定 Style.Triggers 屬性的值,Trigger 為觸發器。

3.在視窗根元素 Window 節點下定義資源,則該頁面所有地方都能引用到該資源。

4.假設 x:Key 為 StyleButton 的樣式沒有設定 x:Key 值,則頁面中所有按鈕預設都使用了這個樣式,除了顯示給 x:Key 指派 x:Null 的按鈕。

5.ControlTemplate 的兩種實作方式歸根到底是設定控件的 Template 屬性,隻不過在 Style 元素中設定該值能實作批量修改控件外觀的功能。

3.綁定

WPF綁定可以了解為一種關系,該關系告訴WPF從一個源對象提取一些資訊,并将這些資訊來設定目标對象的屬性。

  • WPF綁定,必須要有綁定目标和綁定資料源。
  • WPF綁定文法是在 XAML 中使用 {Binding…} 語句,Binding 可以通過 XAML 語句實作界面與資料的耦合,一般情況下,Binding 源是邏輯層的對象,Binding 目标是UI層的控件對象。
  • WPF 綁定使得原本需要多行代碼實作的功能,現在隻需通過簡單的 XAML 代碼就可以完成。
    WPF基礎知識

簡單的了解:WPF 綁定特性使得在修改對象的值後,展現對象的界面會自行重新整理,不用在代碼中手動更新界面展示。如果使用其他UI架構,則可能的執行操作流程是:修改對象的值----更新界面負責展示該值的控件的内容。通過下面的例子可以體會下 WPF 綁定特性的便捷性。

<Window x:Class="WPFLearn.Views.DataBindingDemo"
        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:WPFLearn.Views"
        mc:Ignorable="d"
        Title="資料綁定之綁定控件" Height="300" Width="400"
        x:Name="window">
    <StackPanel HorizontalAlignment="Stretch" Margin="10">
        <ListBox x:Name="ListColor">
            <ListBoxItem Content="Blue"/>
            <ListBoxItem Content="Red"/>
            <ListBoxItem Content="Green"/>
            <ListBoxItem Content="Gray"/>
            <ListBoxItem Content="Cyan"/>
        </ListBox>
        <TextBlock Text="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=OneWay}" Background="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=OneWay}" Margin="0,10,0,0"/>
        <TextBox Text="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=TwoWay}" Background="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=OneWay}" Margin="0,10,0,0"/>
        <Button Margin="0,10,0,0">切換焦點</Button>
    </StackPanel>
</Window>
           
public partial class DataBindingDemo : Window
{
    public DataBindingDemo()
    {
        InitializeComponent();
    }
}
           

注解:

1.第一段代碼段為視窗的 XAML 檔案内容,第二代碼段為視窗的 CS 檔案内容。

2.該視窗實作的功能:切換清單選擇項,下方的文本顯示框和文本編輯框的内容和背景色會跟着改變;修改文本編輯框的内容,然後點選切換焦點的按鈕,清單選中項的内容會變為文本編輯框中的内容,同時文本顯示框的内容、背景顔色與文本編輯框的背景顔色會跟着改變。

3.我們發現在視窗的 CS 檔案中,我們沒有寫任何額外的代碼,比如更新控件内容、更新控件背景顔色,我們隻是在 XAML 檔案中使用了 {Binding…} 這樣的綁定文法,便實作了資料同步這一功能,這便是 WPF 綁定的便捷性,也展現出了 WPF 的威力。

3.1資料源

WPF 綁定的資料源需要具有通知更改功能,有兩種方法設計這種資料源:

  • 使用 DependencyObject 類型(依賴屬性)。
  • 使用實作 INotifyPropertyChanged 接口的類型。

3.2綁定方法

1.ItemsControl

  • 在代碼中将集合指派給控件的 ItemsSource 屬性,在 XAML 檔案中就不用給控件的 ItemsSource 屬性指派。
  • 在 XAML 檔案中,給控件的 ItemsSource 屬性指派 “{Binding}” ,然後在代碼初始化視窗時,将集合作為 DataContext 。
  • 在 XAML 檔案中,首先使用 ObjectDataProvider 對象标簽,設定其 ObjectType、MethodName 屬性,這兩個屬性能夠定位提供資料源的方法。然後給控件的 ItemsSource 屬性指派 “{Binding Source={StaticResource DataList}}” 。其中 DataList 為 ObjectDataProvider 标簽中的 x:Key 屬性的值。

2.ContentControl

在 XAML 檔案中,給控件的屬性指派 “{Binding Path=AttributeName}” ,然後在代碼初始化視窗時,将視窗本身指派給 DataContext 。

3.3綁定模式

控制綁定機制的關聯性,比如單向、雙向還是單次…

1.屬性:Mode 。

2.枚舉值:見下表。

枚舉值 意義
Default 使用綁定目标的預設 Mode 值。每個依賴項屬性的預設值都不同。一般情況下,使用者可編輯控件屬性(例如文本框和複選框的屬性)預設為雙向綁定,而多數其他屬性預設為單向綁定。
TwoWay 源屬性或目标屬性的更改可自動更新對方。
OneWay 當綁定源(源)更改時,更新綁定目标(目标)屬性。如果無需監視目标屬性的更改,則使用 OneWay 綁定模式可避免 TwoWay 綁定模式的系統開銷。
OneWayToSource 當目标屬性更改時更新源屬性。
OneTime 當應用程式啟動或資料上下文更改時,更新綁定目标。如果要從源屬性初始化具有某個值的目标屬性,并且事先不知道資料上下文,則也可以使用此綁定類型。此綁定類型實質上是 OneWay 綁定的簡化形式,在源值不更改的情況下可以提供更好的性能。

3.4綁定更新

控制綁定機制觸發更新的時機,比如失焦、屬性更改…

1.屬性:UpdateSourceTrigger 。

2.枚舉值:見下表。

枚舉值 意義
Default 綁定目标屬性的預設更新值,大多數依賴項屬性的預設值都為 PropertyChanged ,而 Text 屬性的預設值為 LostFocus 。
Explicit 僅在調用 UpdateSource 方法時更新綁定源。
LostFocus 當綁定目标元素失去焦點時,更新綁定源。
PropertyChanged 當綁定屬性更改時,立即更新綁定源。

3.5示例

<Window x:Class="WPFLearn.Views.DataBinding.BindingDataDemo"
        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:WPFLearn.Views.DataBinding"
        mc:Ignorable="d"
        x:Name="window"
        Title="資料綁定之綁定對象" Height="300" Width="400">
    <Grid >
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <StackPanel x:Name="PanelStudentInfo">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="學号:"/>
                    <TextBlock Text="{Binding Path=ID}" Width="100"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                    <TextBlock Text="姓名:"/>
                    <TextBlock Text="{Binding Path=Name}" Width="100"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                    <TextBlock Text="分數:"/>
                    <TextBlock Text="{Binding Path=Score}" Width="100"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                    <Button Content="改變姓名" Name="ButtonChangeName" Click="ButtonChangeName_Click"/>
                    <Button Content="改變分數" Name="ButtonCHangeScore" Click="ButtonCHangeScore_Click" Margin="20,0,0,0"/>
                </StackPanel>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,20,0,0">
                <TextBlock Text="學校:"/>
                <TextBlock x:Name="TextBlockSchool" Text="{Binding Path=School}"/>
                <Button x:Name="ButtonChangeSchool" Content="改變學校" Width="60" Click="ButtonChangeSchool_Click" Margin="10,0,0,0"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>
           
namespace WPFLearn.Views.DataBinding
{
    /// <summary>
    /// BindingDataDemo.xaml 的互動邏輯
    /// </summary>
    public partial class BindingDataDemo : Window
    {
        //類型1:繼承INotifyPropertyChanged的可發通知消息的類型
        private Student student;

        //類型2:依賴屬性
        public string School
        {
            get { return (string)GetValue(SchoolProperty); }
            set { SetValue(SchoolProperty, value); }
        }

        public static DependencyProperty SchoolProperty =
            DependencyProperty.Register("School", typeof(string), typeof(BindingDataDemo), new PropertyMetadata("清華大學"));

        public BindingDataDemo()
        {
            InitializeComponent();
            student = new Student() { ID = 1, Name = "小紅", Score = 98 };
            //綁定方式1:給元素對象設定DataContext屬性,然後在XAML檔案中直接引用
            this.PanelStudentInfo.DataContext = student;
            this.TextBlockSchool.DataContext = this;

            綁定方式2:手寫Binding TODO:存在問題,沒辦法更新資料,原因暫時不清楚
            //Binding bind = new Binding();
            //bind.Source = this;
            //bind.Path = new PropertyPath("School");
            //bind.Mode = BindingMode.TwoWay;
            //bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            //this.TextBlockSchool.SetBinding(TextBlock.TextProperty, bind);
            BindingOperations.SetBinding(this.TextBlockSchool, TextBlock.TextProperty, bind);
        }

        private void ButtonChangeName_Click(object sender, RoutedEventArgs e)
        {
            student.Name = "小明";
        }

        private void ButtonCHangeScore_Click(object sender, RoutedEventArgs e)
        {
            student.Score = 100;
        }

        private void ButtonChangeSchool_Click(object sender, RoutedEventArgs e)
        {
            School = "北京大學";
        }

        public class Student : INotifyPropertyChanged
        {
            private int id;

            public int ID
            {
                get { return id; }
                set
                {
                    if (id != value)
                    {
                        id = value;
                        OnPropertyChanged(new PropertyChangedEventArgs("ID"));
                    }
                }
            }

            private string name;

            public string Name
            {
                get { return name; }
                set
                {
                    if (name != value)
                    {
                        name = value;
                        OnPropertyChanged(new PropertyChangedEventArgs("Name"));
                    }
                }
            }

            private double score;

            public double Score
            {
                get { return score; }
                set
                {
                    score = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("Score"));
                }
            }



            public event PropertyChangedEventHandler PropertyChanged;
            public void OnPropertyChanged(PropertyChangedEventArgs e)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, e);
            }
        }
    }
}
           

注解:

1.Student 類型實作了 INotifyPropertyChanged,是以 Student 類型的 student 字段可以作為綁定資料源;而視窗 BindingDataDemo 内部添加了 DependencyProperty 類型的依賴屬性 School,是以 School 也可以作為綁定資料源。

2.在視窗構造函數中,給控件的資料上下文 DataContext 屬性指派,這樣在 XAML 中,控件就可以直接通過 {Binding Path=XXX} 找到XXX所屬的對象,然後建立綁定關系。

3.最終,我們在修改函數中,單純修改内部資料,不用手動更新控件展示值,控件便會自動重新整理顯示。

4.模闆

在WPF中包含三種模闆:控件模闆、資料模闆和面闆模闆,它們都繼承于FrameworkTemplate基類。

4.1控件模闆

控件模闆(System.Windows.Controls.ControlTemplate),即控件外觀,可以通過修改控件模闆來自定義控件的外觀表現,例如,可以通過修改按鈕的控件模闆使按鈕表現為圓形。控件模闆控制UI整體外觀。

1.資源定義:ControlTemplate。

2.屬性使用:Template。

3.例子:

<Window x:Class="WPFLearn.Views.Template.ControlTemplateDemo"
        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:WPFLearn.Views.Template"
        mc:Ignorable="d"
        Title="模闆之控件模闆" Height="450" Width="800">
    <Window.Resources>
        <ControlTemplate x:Key="RoundButtonTemplate" TargetType="Button">
            <Grid>
                <Ellipse Name="EllipseSection" Fill="Orange" Width="100" Height="100"/>
                <ContentPresenter Content="{TemplateBinding Button.Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="EllipseSection" Property="Fill" Value="Yellow"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>
    <Grid >
        <Button Content="Round Button" HorizontalAlignment="Center"  VerticalAlignment="Center" Template="{StaticResource RoundButtonTemplate}"/>
    </Grid>
</Window>
           

4.2資料模闆

資料模闆(System.Windows.DataTemplate),即資料外觀,用于從一個對象中提取資料,并在控件中顯示資料。隻有内容控件與條目控件支援資料模闆。資料模闆控制UI内容形式。

1.資源定義:DataTemplate 。

2.屬性使用:ContentTemplate(内容控件)、ItemTemplate(條目控件)。

3.例子:

<Window x:Class="WPFLearn.Views.Template.DataTemplateDemo"
        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:WPFLearn.Services"
        mc:Ignorable="d"
        Title="模闆之資料模闆" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="PersonDataTemplate">
            <Border Name="BorderBlue" Margin="3" BorderThickness="3" BorderBrush="Blue" CornerRadius="5">
                <Grid Margin="3">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Name="TextBlockName" FontWeight="Bold" Text="{Binding Path=Name}"/>
                    <TextBlock Grid.Row="1" Text="{Binding Path=Age}"/>
                </Grid>
            </Border>
            <DataTemplate.Triggers>
                <Trigger SourceName="BorderBlue" Property="IsMouseOver" Value="True">
                    <Setter TargetName="BorderBlue" Property="Background" Value="LightGray"/>
                    <Setter TargetName="TextBlockName" Property="FontSize" Value="20"/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>
    <Grid Margin="0,0,0,10">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="60"/>
        </Grid.RowDefinitions>
        <ListBox x:Name="ListBoxPerson" ItemsSource="{Binding}" HorizontalContentAlignment="Stretch" ItemTemplate="{StaticResource PersonDataTemplate}"/>
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10,0,0">
            <Button x:Name="ButtonAddAgeOfFirstItem" Content="遞增第一項年齡值" Height="30" Click="ButtonAddAgeOfFirstItem_Click"/>
            <Button x:Name="ButtonDeleteLastItem" Content="删除第二項" Height="30" Margin="10,0,0,0" Click="ButtonDeleteLastItem_Click"/>
        </StackPanel>
    </Grid>
</Window>
           

4.3面闆模闆

面闆模闆(System.Windows.Controls.ItemsPanelTemplate),即面闆外觀,而面闆又是用于進行布局的,是以面闆的外衣也就是布局的外衣,通過修改面闆模闆可以自定義控件的布局。例如,ListBox預設是自從向下地顯示每一項的,此時可以通過修改面闆模闆使其自左向右地顯示每一項。面闆模闆控制UI内容布局。

1.資源定義:ItemsPanelTemplate 。

2.屬性使用:ItemsPanel 。

3.例子:

<Window x:Class="WPFLearn.Views.Template.PanelTemplateDemo"
        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:WPFLearn.Views.Template"
        mc:Ignorable="d"
        Title="模闆之面闆模闆" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="PersonDataTemplate">
            <Border x:Name="BorderBlue" Margin="3" BorderThickness="3" BorderBrush="Blue" CornerRadius="5">
                <Grid Margin="3">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Name="TextBlockName" FontWeight="Bold" Text="{Binding Name}"/>
                    <TextBlock Grid.Row="1" Text="{Binding Age}"/>
                </Grid>
            </Border>
            <DataTemplate.Triggers>
                <Trigger SourceName="BorderBlue" Property="IsMouseOver" Value="True">
                    <Setter TargetName="BorderBlue" Property="Background" Value="LightGray"/>
                    <Setter TargetName="TextBlockName" Property="FontSize" Value="20"/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
        <ItemsPanelTemplate x:Key="ListItemsPanelTemplate">
            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </ItemsPanelTemplate>
    </Window.Resources>
    <Grid>
        <ListBox x:Name="ListBoxPerson" ItemsPanel="{StaticResource ListItemsPanelTemplate}" ItemTemplate="{StaticResource PersonDataTemplate}"/>
    </Grid>
</Window>
           

5.資源

WPF 資源系統是一種保管一系列對象(如常用的畫刷、樣式或模闆)的簡單辦法,進而讓使用者更容易地複用這些對象。

5.1資源定義

每一個架構級元素( FrameworkElement 或者 FrameworkContentElement )都有一個資源屬性。每一個在資源字典中的資源都有一個唯一不重複的鍵值( key ),在标簽中使用 x:Key 屬性來辨別它。一般地,鍵值是一個字元串,但你也可以用合适的擴充标簽來設定為其他對象類型。非字元鍵值資源使用于特定的WPF 區域,尤其是風格、元件資源以及樣式資料等。

5.2資源使用

為了使用XAML标記中的資源,需要一種引用資源的方法,可以通過兩個标記來引用資源:StaticResource 和 DynamicResource 。

  • StaticResource :以靜态方式引用資源。在第一次建立視窗時,一次性地設定完畢,程式運作過程中資源發生變化,程式不會更新引用資源的對象。無法實時更新程式,效率高。
  • DynamicResource :以動态方式引用資源。如果資源發生了改變,程式會重新應用資源。可以實時更新程式,效率低。

5.3資源範圍

WPF提供一個封裝和存取資源的機制,我們可将資源建立在應用程式的不同範圍上。WPF中,資源定義的位置決定了該資源的可用範圍,程式會按照範圍由小到大的順序檢索資源。資源按照範圍分類見下表。

級别 說明
物件級 資源定義在普通元素下,該資源隻能套用在這個元素及其其子元素。
檔案級 資源定義在Window或Page元素下,該資源可以套用到這個檔案中的所有元素。
應用程式級 資源定義在App.xaml中,該資源可以套用到應用程式内的任何地方。
字典級 将資源封裝成資源字典定義在資源XAML檔案中,該資源可以在套用到其他應用程式中。
系統級 資源檢索的最終層級,我們無法在這層級上自定義資源,系統級資源有系統顔色設定/字型設定/螢幕标準尺寸等。

5.4綜合示例

1.StaticResource 和 DynamicResource 的差別

<Window x:Class="WPFLearn.Views.ResourceStyle.ResourceStyleDemo"
        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:WPFLearn.Views.ResourceStyle"
        mc:Ignorable="d"
        Title="ResourceStyleDemo" Height="300" Width="300">
    <Window.Resources>
        <SolidColorBrush x:Key="RedBrush" Color="Red"></SolidColorBrush>
    </Window.Resources>
    <StackPanel Margin="5">
		<StackPanel>
			<Button Background="{StaticResource RedBrush}" Margin="5" FontSize="14" Content="Use Static Resource"/>
			<Button Background="{DynamicResource RedBrush}" Margin="5" FontSize="14" Content="Use Dynamic Resource"/>
			<Button Margin="5" FontSize="14" Content="Change the RedBrush to Yellow" Click="Button_Click"/>
		</StackPanel>
    </StackPanel>
</Window>
           
public partial class ResourceStyleDemo : Window
{
	public ResourceStyleDemo()
	{
		InitializeComponent();
	}

	private void Button_Click(object sender, RoutedEventArgs e)
	{
		this.Resources["RedBrush"] = new SolidColorBrush(Colors.Yellow);
	}
}
           
注解: 點選按鈕後,Use Static Resource 的按鈕背景顔色沒改變,Use Dynamic Resource 的按鈕背景顔色改變。

2.各種級别的資源

物件級和檔案級兩個級别的資源之前的示例都有見過,下面是字典級别的,在 App.xaml 引用字典級别資源,相當于定義了應用級别資源,另外,應用級别資源定義同其它級别資源定義一樣,是以應用級别資源也不做示範。

<!--CheckBoxStyle.xaml-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="CheckBoxStyle" TargetType="{x:Type CheckBox}">
        <Setter Property="IsChecked" Value="False"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="CheckBox">
                    <DockPanel Background="{TemplateBinding Background}" ToolTip="{TemplateBinding Content}" LastChildFill="False" Width="{TemplateBinding Width}">
                        <Image Margin="0" DockPanel.Dock="Left" x:Name="CheckImage" Stretch="Fill" Source="/Uires/複選框未選.png" Width="15" Height="15"/>
                        <TextBlock DockPanel.Dock="Left" Foreground="{TemplateBinding Foreground}" Margin="2,0,0,0" VerticalAlignment="Center" Text="{Binding Content,RelativeSource={RelativeSource TemplatedParent}}"/>
                    </DockPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter TargetName="CheckImage" Property="Source" Value="/Uires/複選框已選.png"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
           
<!--Style.xaml-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/Styles/ButtonStyle.xaml"/>
        <ResourceDictionary Source="pack://application:,,,/Styles/CheckBoxStyle.xaml"/>
        <ResourceDictionary Source="pack://application:,,,/Styles/OtherStyle.xaml"/>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
           
<!--App.xaml-->
<Application x:Class="WPFLearn.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WPFLearn"
             StartupUri="Views/Control/ControlClassificationDemo.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/Styles/Style.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
           

**注解:

1.CheckBoxStyle.xaml 定義了 CheckBox 樣式,Style.xaml 包含所有控件樣式檔案,App.xaml 引入 Style.xaml。

2.CheckBoxStyle.xaml 和 Style.xaml 都定義了字典級的資源,在 App.xaml 中定義的資源便是應用程式級的資源。

6.樣式

WPF 中通過樣式可以友善地批量設定控件外觀。

6.1樣式定義

在 XAML 中通過使用 Style 元素定義樣式,既可以在控件元素中定義樣式(樣式用于控件自身),也可以在 Resource 元素中使定義樣式(樣式可以被範圍内的所有控件引用)。

在 Style 元素中,通過 Style.Setters 元素定義樣式靜态内容,通過 Style.Triggers (觸發器)元素定義樣式動态内容。

1.WPF中的觸發器類型

(1)屬性觸發器(Property Trigger):當Dependency Property的值發生改變時觸發。Trigger 和 MultiTriiger 。

(2)資料觸發器(Data Trigger):當普通.NET屬性的值發生改變時觸發。DataTrigger 和 MultiDataTrigger。

(3)事件觸發器(Event Trigger):當路由事件發生時觸發。EventTrigger 。

注解: 觸發器的 Multi 版本适用多條件的場景,比如設定複選框 選中狀态+滑鼠移動到上面 的樣式。

6.2樣式使用

1.在控件元素中定義的樣式

不需要顯示引用,效果相當直接在控件元素中給各種樣式屬性指派。

2.在Resource元素中定義樣式

  • x:Key 屬性為空的樣式:樣式通過 TargetType 屬性指定控件類型,該類型的所有控件執行個體都會隐式引用該樣式。如果想要取消引用,需要顯示地給樣式屬性指派 Style = “{x:Null}” 。
  • x:Key 屬性不為空的樣式:需要顯示地給控件元素的樣式屬性指派 Style = “{StaticResouce StyleKey}” 。

7.路由事件

WPF 除了建立了一個新的依賴屬性系統之外,還用更進階的路由事件功能替換了普通的 .NET 事件。

路由事件是具有更強傳播能力的事件——它可以在元素樹上向上冒泡和向下隧道傳播,并且沿着傳播路徑被事件處理程式處理。

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>
           
private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}
           

8.指令模式

1.概述

在現在的使用者界面中,常常需要從多個不同位置通路通過函數,即由不同的動作觸發同樣響應。在這種情況下,如果使用路由事件,則需要定義多個事件,然後在這些事件中調用同個函數,這樣處理會很繁瑣。WPF 使用指令機制來解決上述問題:WPF 允許在一個地方定義指令,并且在所有的使用者接口控件之中根據需要地調用這些指令,例如 Menu,ToolBar 按鈕等,不同動作最終會得到相同的響應。

2.WPF 中的指令模型

  • 指令:指令表示一個程式任務,并且可跟蹤該任務是否能被執行。然而,指令實際上不包含執行應用程式的代碼,真正處理程式在指令目标中。
  • 指令源:指令源觸發指令,即指令的發送者。例如 Button、MenuItem 等控件都是指令源,單擊它們都會執行綁定的指令。
  • 指令目标:指令目标是在其中執行指令的元素。如 Copy 指令可以在 TextBox 控件中複制文本。
  • 指令綁定:指令是不包含執行程式的代碼的,真正處理程式存在于指令目标中。那指令是怎樣映射到處理程式中的呢?這個過程就是通過指令綁定來完成的,指令綁定完成的就是紅娘牽線的作用。

3.WPF 指令的核心

ICommand 接口,該接口定義了指令的工作原理。

9.MVVM

1.概述

MVVM 是 WPF 衍生出的一種優秀程式設計架構(模式),這種模式充分利用了 WPF 的資料綁定機制,最大限度地降低了 XAML 檔案和 CS 檔案的耦合度,實作了将視圖UI和業務邏輯分離的效果。在需要更換界面時,邏輯代碼修改很少,甚至不用修改。

2.定義

  • MVVM 程式設計模式通常被用于 WPF 或 Silverlight 開發,一些現代的 WEB 前端架構也使用了這種模式。
  • MVVM 是 Model-View-ViewModel 的縮寫形式。

    模型(Model):資料對象,負責存儲資料。

    視圖(View):界面對象,負責與使用者互動、接收使用者輸入、把資料展現給使用者。

    視圖模型(View Model):連接配接對象,負責連接配接 Model 層和 View 層。View Model 不僅僅是 Model 的包裝,它還包含了程式邏輯以及 Model 擴充。

    WPF基礎知識
    3.優點
  • 低耦合:視圖(View)可以獨立于 Model 變化和修改,一個 ViewModel 可以綁定到不同的 View 上,當 View 變化的時候 Model 可以不變,當 Model 變化的時候 View 也可以不變。
  • 可重用性:可以把一些視圖邏輯放在一個 ViewModel 裡面,讓很多 View 重用這段視圖邏輯。
  • 獨立開發:開發人員可以專注于業務邏輯和資料的開發(ViewModel),設計人員可以專注于頁面設計,使用 Expression Blend 可以很容易設計界面并生成XAML代碼。

4.在 WPF 中使用 MVVM 時的特點

  • 視圖的 CS 檔案包含極少的代碼,其核心邏輯都被放在 View Model 類中,進而使得程式邏輯與視圖耦合度降低。
  • ViewModel 類作為 View 的 DataContext 。
  • 在 MVVM 下,所有事件和動作都被當成指令,如按鈕的點選操作,此時不是觸發點選事件,而是綁定到一個點選指令,再由指令去執行對應的邏輯代碼。

5.示例

WPF快速入門系列(8)——MVVM快速入門,這位作者寫的這輪系列文章都很不錯,推薦大家入門 WPF 時去看,位址是WFP快速入門系列 | Learning hard | 部落格園。

10.參考資料

1.先說下自己推薦的學習路線:

(1)先用1.5倍速看 參考資料1 的視訊。

(2)認真把 參考資料2 的系列文章都過一遍。

(3)找個現有的用戶端程式練習,用到哪些控件再上網查下具體用法。

2.參考資料大緻如下:

1.《深入淺出WPF》系列高清視訊教程 | 劉鐵猛 | B站

2.WFP快速入門系列 | Learning hard | 部落格園

3.WPF入門教程系列 | DotNet菜園 | 部落格園

4.WPF基礎之資源 | SmilelyCoding | 部落格園

5.WPF教程 | vue5.com線上教程