主頁 > .NET開發 > WPF之模板

WPF之模板

2021-04-01 15:42:48 .NET開發

目錄
  • 模板的內涵
  • 資料的外衣DataTemplate
    • UserControl例子
    • DataTemplate例子
  • 控制元件的外衣 ControlTemplate
    • 解剖控制元件
    • ItemsControl的PanelTemplate
  • DataTemplate與ControlTemplate的關系與應用
    • DataTemplate與ControlTemplate的關系
    • DataTemplate與ControlTemplate的應用
      • 把ControlTemplate應用在所有目標上
      • 把DataTemplate應用在某個資料型別上
        • 顯示層級資料的模板HierarchicalDataTemplate
    • 從外界訪問Template內部的控制元件及其屬性值
      • 檢索ControlTemplate生成的控制元件
      • 檢索DataTemplate生成的控制元件
        • 簡單例子
        • 復雜例子
  • Style
    • Style中的Setter
    • Style中的Trigger
      • 基本Trigger
      • MultiTrigger
      • 由資料觸發的DataTrigger
      • 多資料條件觸發的MultiDataTrigger
      • 由事件觸發的EventTrigger

WPF系統不但支持傳統的Winfrom編程的用戶界面和用戶體驗設計,更支持使用專門的設計工具Blend進行專業設計,同時還推出了以模板為核心的新一代設計理念,

模板的內涵

“形而上者謂之道,形而下者謂之器”出自《易經》,在計算機世界中:

  • “形而上者謂之道”指的就是基于現實世界對萬物進行抽象封裝、理順它們之間的關系,這個“道”就是面向物件思想,進一步提升、總結出物件之間的最優組合關系就上升為設計模式思想了,
  • “形而下者謂之器”指的是能觀察到的世間萬物都是物質本質內容的表現形式,“本質與表現”或者說“內容與形式”是哲學范疇內的一對矛盾統一體,

WPF的全稱是Windows Presentation Foundation,Presentation 一詞的意思就是外觀、呈現、表現,在Windows GUI程式這個尺度上WPF扮演的就是“形”的角色、是程式的外在“形式”,而程式的“內容”仍然是由資料和演算法構成的業務邏輯

WPF作為一種“形式”,它要表現的“內容”是程式的資料和演算法——Binding傳遞的是資料,事件引數攜帶的也是資料;方法和委托的呼叫是演算法,事件傳遞訊息也是演算法,

控制元件(Control)是資料內容表現形式和演算法內容表現形式的雙多載體,控制元件既是資料的表現形式讓用戶可以直觀地看到資料,又是演算法的表現形式讓用戶方便地操作邏輯,
作為“表現形式”,每個控制元件都是為了實作某種用戶操作演算法和直觀顯示某種資料而生,一個控制元件看上去是什么樣子由它的“演算法內容”和“資料內容”決定,這就是內容決定形式,這里引入兩個概念:

  • 控制元件的“演算法內容”:指控制元件能展示哪些資料、具有哪些方法、能回應哪些操作、能激發什么事件,簡而言之就是控制元件的功能,它們是一組相關的演算法邏輯,
  • 控制元件的“資料內容”:控制元件所展示的具體資料是什么,

以往的GUI開發技術(如Windows Forms和ASP.NET)中,控制元件內部的邏輯和資料是固定的,程式員不能改變,
在WPF中,通過引入模板(Template)微軟將資料和演算法的“內容”與“形式”解耦了,WPF中的Template分為兩大類:

  • ControlTemplate是演算法內容的表現形式,一個控制元件怎樣組織其內部結構才能讓它更符合業務邏輯、讓用戶操作起來更舒服就是由它來控制的,它決定了控制元件“長成什么樣子”,并讓程式員有機會在控制元件原有的內部邏輯基礎上擴展自己的邏輯,
  • DataTemplate是資料內容的表現形式,一條資料顯示成什么樣子,是簡單的文本還是直觀的圖形影片就由它來決定,

WPF中的控制元件不再具有固定的形象,僅僅是演算法內容或資料內容的載體,資料顯示成什么樣子也可以自由設定,

資料的外衣DataTemplate

一樣的內容可以用不同的形式來展現,軟體設計稱之為“資料-視圖”(Data-View)模式,以往的開發技術,如MFC、Windows Forms等,視圖要靠UserControl 來實作,WPF不但支持UserControl 還支持用DataTemplate為資料形成視圖(從UserControl升級到DataTemplate也很簡單),

DataTemplate常用的地方有3處,分別是:

  • ContentControl的ContentTemplate屬性,相當于給ContentControl的內容穿衣服,
  • ItemsControl的ItemTemplate屬性,相當于給ItemsControl的資料條目穿衣服,
  • GridViewColumn的CellTemplate屬性,相當于給GridViewColumn單元格里的資料穿衣服,

用一個例子對比UserControl與DataTemplate的使用,ListBox的條目顯示汽車的廠商圖示和簡要引數,單擊某個條目后在表單的詳細內容區域顯示汽車的照片和詳細引數,
先在專案中建立資源管理目錄(/Resources/Logos和/Resources/Images)并把圖片添加進來,Logo的檔案名與廠商名稱一致,照片的檔案名則與車名一致,

UserControl例子

創建Car資料型別:

public class Car
{
    public string AutoMark { get; set; }
    public string Name { get; set; }
    public string Year { get; set; }
    public string TopSpeed { get; set; }
}

準備一個名為CarListItemView的UserControl用于在ListBox里面顯示Car型別的資料,XAML代碼如下:

<UserControl x:
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Margin="2">
        <StackPanel Orientation="Horizontal">
            <Image x:Name="imageLogo" Grid.RowSpan="3" Width="64" Height="64"/>
            <StackPanel Margin="5,10">
                <TextBlock x:Name="textBlockName" FontSize="16" FontWeight="Bold"/>
                <TextBlock x:Name="textBlockYear" FontSize="14"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>

CarlistItemView用于支持前臺顯示的屬性C#代碼為:

public partial class CarListItemView : UserControl
{
    public CarListItemView()
    {
        InitializeComponent();
    }

    private Car car;
    public Car Car
    {
        get { return car; }
        set
        {
            car = value;
            this.textBlockName.Text = car.Name;
            this.textBlockYear.Text = car.Year;
            string uirStr = string.Format(@"Resource/Logos/{0}.png", car.AutoMark);
            this.imageLogo.Source = new BitmapImage(new Uri(uirStr, UriKind.Relative));
        }
    }
}

準備一個名為CarDetailView的UserControl作為Car型別的詳細資訊視圖,XAML代碼如下:

<UserControl x:
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
        <StackPanel>
            <Image x:Name="imagePhoto" Width="400" Height="250"/>
            <StackPanel Orientation="Horizontal" Margin="5,0">
                <TextBlock Text="Name:" FontSize="20" FontWeight="Bold"/>
                <TextBlock x:Name="textBlockName" FontSize="20" Margin="5,0"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5,0">
                <TextBlock Text="AutoMark:" FontWeight="Bold"/>
                <TextBlock x:Name="textBlockAutoMark" Margin="5,0"/>
                <TextBlock Text="Year:" FontWeight="Bold"/>
                <TextBlock x:Name="textBlockYear" Margin="5,0"/>
                <TextBlock Text="Top Speed:" FontWeight="Bold"/>
                <TextBlock x:Name="textTopSpeed" Margin="5,0"/>
            </StackPanel>
        </StackPanel>
    </Border>
</UserControl>

后臺支持資料C#代碼:

public partial class CarDetailView : UserControl
{
    public CarDetailView()
    {
        InitializeComponent();
    }

    private Car car;

    public Car Car
    {
        get { return car; }
        set
        {
            car = value;
            this.textBlockName.Text = car.Name;
            this.textBlockAutoMark.Text = car.AutoMark;
            this.textBlockYear.Text = car.Year;
            this.textTopSpeed.Text = car.TopSpeed;
            string uirStr = string.Format(@"Resource/Images/{0}.jpg", car.Name);
            this.imagePhoto.Source = new BitmapImage(new Uri(uirStr, UriKind.Relative));
        }
    }
}

把它們組裝到主表單上,XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        xmlns:local="clr-namespace:WpfApp"      
        Title="MainWindow" Height="350" Width="650">
    <StackPanel Orientation="Horizontal" Margin="5">
        <local:CarDetailView x:Name="detailView" />
        <ListBox x:Name="listBoxCars" Width="200" Margin="5,0" SelectionChanged="listBoxCars_SelectionChanged"/>
    </StackPanel>
</Window>

表單的后臺代碼如下:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitialCarList();
    }   
    
    //初始化ListBox
    private void InitialCarList()
    {
        List<Car> carList = new List<Car>() {
        new Car(){ AutoMark="Lamborghini", Name="Diablo", TopSpeed="340", Year="1990"},
        new Car(){ AutoMark="Lamborghini", Name="Murcielago", TopSpeed="353", Year="2001"},
        new Car(){ AutoMark="Lamborghini", Name="Gallardo", TopSpeed="325", Year="2003"},
        new Car(){ AutoMark="Lamborghini", Name="Reventon", TopSpeed="356", Year="2008"},            
        };
        foreach (Car car in carList)
        {
            CarListItemView view = new CarListItemView();
            view.Car = car;
            this.listBoxCars.Items.Add(view);
        }
    }

    //選項變化事件的處理器
    private void listBoxCars_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        CarListItemView view = e.AddedItems[0] as CarListItemView;
        if (view != null)
        {
            this.detailView.Car = view.Car;
        }
    }
}

運行程式并單擊ListBox里的條目,效果如下(未準備相關圖片):

這種實作方法是“把WPF當作WindowsForms來用”,浪費了資料驅動界面這一重要功能,沒有借助Binding實作資料驅動界面,并且認為ListBox.Items屬性里放置的是控制元件—一這種曲解迫使資料在界面元素間交換并且程式員只能使用事件驅動方式來實作邏輯,

目前的事件驅動模式與期望中資料驅動界面模式的不同:事件驅動是控制元件和控制元件之間溝通或者說是形式與形式之間的溝通,資料驅動則是資料與控制元件之間的溝通、是內容決定形式,使用DataTemplate就可以很方便地把事件驅動模式升級為資料驅動模式,

DataTemplate例子

由UserControl升級為DataTemplate時90%的代碼可以原樣拷貝,另10%可以放心洗掉,再做一點點改動就可以了:

  • 把兩個UserControl的“芯”剪切出來,用標簽包裝,再放進主表單的資源詞典里,
  • 為DataTemplate里的每個控制元件設定Binding,告訴各個控制元件應該關注資料的哪個屬性,免去在C#代碼中訪問界面元素(去掉大多數x:Name),

汽車的廠商和名稱不能直接拿來作為圖片的路徑,需要使用Converter,有兩種辦法可以在XAML代碼中使用Converter:

  • 把 Converter以資源的形式放在資源詞典里(本例使用的方法),
  • 為Converter準備一個靜態屬性,形成單件模式,在XAML代碼里使用(x:Static)標簽擴展來訪問,

兩個Converter代碼如下:

//廠商名稱轉換為Logo路徑
public class AutoMarkToLogoPathConverter : IValueConverter
{
    // 正向轉換
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string uirStr = string.Format(@"Resource/Logos/{0}.png", (string)value);
        return new BitmapImage(new Uri(uirStr, UriKind.Relative));
    }

    // 逆向轉未用到
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

//汽車名稱轉換為照片路徑
public class NameToPhotoPathConverter : IValueConverter
{
    // 正向轉換
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string uirStr = string.Format(@"Resource/Images/{0}.jpg", (string)value);
        return new BitmapImage(new Uri(uirStr, UriKind.Relative));
    }
    // 逆向轉未用到
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

有了Converter之后就可以設計DataTemplate了,完整的XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        xmlns:local="clr-namespace:WpfApp"      
        Title="MainWindow" Height="350" Width="630">
    <Window.Resources>
        <!--Converter-->
        <local:AutoMarkToLogoPathConverter x:Key="a2l"/>
        <local:NameToPhotoPathConverter x:Key="n2p"/>
        <!--DataTemplate For Datial View-->
        <DataTemplate x:Key="carDetailViewTemplate">
            <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
                <StackPanel Margin="5">
                    <Image x:Name="imagePhoto" Width="400" Height="250" Source="{Binding Name,Converter={StaticResource n2p}}"/>
                    <StackPanel Orientation="Horizontal" Margin="5,0">
                        <TextBlock Text="Name:" FontSize="20" FontWeight="Bold"/>
                        <TextBlock FontSize="20" Margin="5,0" Text="{Binding Name}"/>
                    </StackPanel>
                    <StackPanel Orientation="Horizontal" Margin="5,0">
                        <TextBlock Text="AutoMark:" FontWeight="Bold"/>
                        <TextBlock  Margin="5,0" Text="{Binding AutoMark}"/>
                        <TextBlock Text="Year:" FontWeight="Bold"/>
                        <TextBlock Text="{Binding Year}" Margin="5,0"/>
                        <TextBlock Text="Top Speed:" FontWeight="Bold"/>
                        <TextBlock Text="{Binding TopSpeed}" Margin="5,0"/>
                    </StackPanel>
                </StackPanel>
            </Border>
        </DataTemplate>
        <!--Data Template For Item View-->
        <DataTemplate x:Key="carListItemViewTemplate">
            <Grid Margin="2">
                <StackPanel Orientation="Horizontal">
                    <Image x:Name="imageLogo" Grid.RowSpan="3" Width="64" Height="64" Source="{Binding AutoMark,Converter={StaticResource a2l}}"/>
                    <StackPanel Margin="5,10">
                        <TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/>
                        <TextBlock Text="{Binding Year}" FontSize="14"/>
                    </StackPanel>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <!--表單內容-->
    <StackPanel Orientation="Horizontal" Margin="5">
        <UserControl ContentTemplate="{StaticResource carDetailViewTemplate}" Content="{Binding Path=SelectedItem,ElementName=listBoxCars}"/>
        <ListBox x:Name="listBoxCars" ItemTemplate="{StaticResource carListItemViewTemplate}" Margin="5,0"/>
    </StackPanel>
</Window>

代碼結構非常簡單,其中最重要的兩句是:

  • ContentTemplate="{StaticResource carDetailViewTemplate}",相當于給一個普通UserControl的資料內容穿上一件外衣、讓Car型別資料以圖文并茂的形式展現出來——以x:Key="carDetailViewTemplate"標記的DataTemplate資源,
  • ItemTemplate="{StaticResource carListItemViewTemplate}",是把一件資料的外衣交給ListBox,當ListBox.ItemsSource被賦值時,ListBox會為每個條目穿上這件外衣——以xKey="carListltemViewTemplate"標記的DataTemplate資源,

表單的C#代碼只剩下這些:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitialCarList();
    }       

    //初始化ListBox
    private void InitialCarList()
    {
        List<Car> carList = new List<Car>() {
        new Car(){ AutoMark="Lamborghini", Name="Diablo", TopSpeed="340", Year="1990"},
        new Car(){ AutoMark="Lamborghini", Name="Murcielago", TopSpeed="353", Year="2001"},
        new Car(){ AutoMark="Lamborghini", Name="Gallardo", TopSpeed="325", Year="2003"},
        new Car(){ AutoMark="Lamborghini", Name="Reventon", TopSpeed="356", Year="2008"},            
        };

        //填充資料
        this.listBoxCars.ItemsSource = carList;
    }        
}

運行程式,效果如下(與使用UserControl實作的沒有任何區別):

使用DataTemplate可以讓程式結構更清晰、代碼更簡潔、維護更方便,是DataTemplate幫助徹底完成了“資料驅動界面”,讓Binding和資料關聯滲透到用戶界面的每一個細胞中,

控制元件的外衣 ControlTemplate

實際專案中,ControlTemplate主要有兩大用武之地:

  • 通過更換ControTemplate改變控制元件外觀,使之具有更優的用戶使用體驗及外觀,
  • 借助ControlTemplate,程式員與設計師可以并行作業,程式員可以先用WPF標準控制元件進行編程,等設計師的作業完成后,只需把新的ControfTemplate應用到程式中就可以了,

第一點讓程式更換皮膚變得非常容易,第二點則解決了團隊分工與合作的問題,
為控制元件設計ControlTemplate需要了解每個控制元件的內部結構,用于打碎控制元件、查看內部結構的工具就是Microsoft Expression套裝中的Blend,

解剖控制元件

先從結構簡單的TextBox和Button入手,運行Blend,新建一個WPF專案(或者打開一個由VS2008創建的WPF專案),先把表單的背景色改為線性漸變,再在表單的主容器Grid 里畫上兩個TextBox和一個Buton,

程式運行效果如下:

讓TextBox的邊框變為圓角矩形,操作如下:

  • 在TextBox上右擊,在彈出選單中選擇“編輯模板”一>“編輯副本”,不選擇“創建空白項”是因為“創建空白項”是從頭開始設計一個控制元件的ControlrTemplate(沒有改模板快),
  • 單擊選單項后彈出資源對話框,彈出對話框設定資源的x:Key是什么、打算把資源放在哪里(大多數情況下ControlTemplate是由XAML代碼撰寫的并放在資源詞典里),
  • 選擇把模板放在Application的資源詞典里以便統一管理,并命名為RoundCornerTextBoxStyle,
  • 單擊“確定”按鈕便進入了控制元件的模板的編輯狀態,

    注:ControlTemplate可以放在三個地方:Application的資源詞典里、某個界面元素的資源詞典里,或者放在外部XAML檔案中,

在“物件和時間線”面板中觀察已經解剖開的TextBox控制元件,發現它是由一個名為border的Border套著一個名為PART_ContentHost的ScrollViewer組成的,如下所示:

為了顯示圓角矩形邊框,只需要設定最外層Border的圓角弧度即可,更改后的核心代碼如下:

<Style x:Key="RoundCornerTextBoxStyle" TargetType="{x:Type TextBox}">
    <Setter Property="Background" Value="https://www.cnblogs.com/timefiles/p/{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
    <Setter Property="BorderBrush" Value="https://www.cnblogs.com/timefiles/p/{StaticResource TextBox.Static.Border}"/>
    <Setter Property="Foreground" Value="https://www.cnblogs.com/timefiles/p/{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="https://www.cnblogs.com/timefiles/p/1"/>
    <Setter Property="KeyboardNavigation.TabNavigation" Value="https://www.cnblogs.com/timefiles/p/None"/>
    <Setter Property="HorizontalContentAlignment" Value="https://www.cnblogs.com/timefiles/p/Left"/>
    <Setter Property="FocusVisualStyle" Value="https://www.cnblogs.com/timefiles/p/{x:Null}"/>
    <Setter Property="AllowDrop" Value="https://www.cnblogs.com/timefiles/p/true"/>
    <Setter Property="ScrollViewer.PanningMode" Value="https://www.cnblogs.com/timefiles/p/VerticalFirst"/>
    <Setter Property="Stylus.IsFlicksEnabled" Value="https://www.cnblogs.com/timefiles/p/False"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <Border x:Name="border" 
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        Background="{TemplateBinding Background}" 
                        SnapsToDevicePixels="True" 
                        CornerRadius="5">
                    <ScrollViewer x:Name="PART_ContentHost" Focusable="false" 
                                  HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="https://www.cnblogs.com/timefiles/p/false">
                        <Setter Property="Opacity" TargetName="border" Value="https://www.cnblogs.com/timefiles/p/0.56"/>
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="https://www.cnblogs.com/timefiles/p/true">
                        <Setter Property="BorderBrush" TargetName="border" Value="https://www.cnblogs.com/timefiles/p/{StaticResource TextBox.MouseOver.Border}"/>
                    </Trigger>
                    <Trigger Property="IsKeyboardFocused" Value="https://www.cnblogs.com/timefiles/p/true">
                        <Setter Property="BorderBrush" TargetName="border" Value="https://www.cnblogs.com/timefiles/p/{StaticResource TextBox.Focus.Border}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsInactiveSelectionHighlightEnabled" Value="https://www.cnblogs.com/timefiles/p/true"/>
                <Condition Property="IsSelectionActive" Value="https://www.cnblogs.com/timefiles/p/false"/>
            </MultiTrigger.Conditions>
            <Setter Property="SelectionBrush" Value="https://www.cnblogs.com/timefiles/p/{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
        </MultiTrigger>
    </Style.Triggers>
</Style>

這段代碼有如下幾個看點:

  • 作為資源的不是單純的ControlTemplate而是Style,實際上是把ControlTemplate包含在Style里,使用Style時,如果Value的值比較簡單,那就直接用Attribute值來表示,如果Value值不能用一個簡單的字串描述就需要使用XAML的屬性物件語法(如TextBox的Template屬性),
  • ControlTemplate最終將被應用到一個控制元件上——稱為模板目標控制元件或模板化控制元件(Templated Control),ControlTemplate里的控制元件可以使用TemplateBinding將自己的屬性值關聯在目標控制元件的某個屬性值上,必要的時候還可以添加Converter(如Background"{TemplateBinding Background)"),TemplateBinding的功能與{Binding RelativeSource={RelativeSource TemplatedParent}}一致

把設計的圓角Style應用到兩個TextBox上,代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="248.98" Width="377.551">
    <Grid>
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="Black" Offset="0"/>
                <GradientStop Color="White" Offset="1"/>
            </LinearGradientBrush>
        </Grid.Background>
        <Button Content="Button" HorizontalAlignment="Left" Margin="139.98,160.753,0,0" VerticalAlignment="Top" Width="75"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="118.449,55.653,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120" Style="{DynamicResource RoundCornerTextBoxStyle}"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="118.449,104.632,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120" Style="{DynamicResource RoundCornerTextBoxStyle}"/>
    </Grid>
</Window>


每個控制元件本身就是一棵UI元素樹,WPF的UI元素可以看作兩棵樹——LogicalTree和VisualTree,這兩棵樹的交點就是ControlTemplate,
如果把界面上的控制元件元素看作是一個結點,那元素們構成的就是LogicalTree,如果把控制元件內部由ControlTemplate生成的控制元件也算上,那構成的就是VisualrTree,

ItemsControl的PanelTemplate

ItemsControl具有一個名為ltemsPanel的屬性,它的資料型別為ItemsPanelTemplate,
ItemsPanerTemplate 也是一種控制元件Template,它的作用就是讓程式員有機會控制 ltemsControl的條目容器,

ListBox中的條目都是自上而下排列的,代碼如下:

<Grid Margin="6">
    <ListBox>            
        <TextBlock Text="Allan"/>
        <TextBlock Text="Kevin"/>
        <TextBlock Text="Drew"/>
        <TextBlock Text="Timothy"/>
    </ListBox>
</Grid>

效果如下:

制作一個條目水平排列的ListBox,只需要調整ListBox的ItemsPanel屬性,條目就會包裝在一個水平排列的StackPanel中從而橫向排列,代碼如下:

<Grid Margin="6">
    <ListBox>
        <!--ItemsPanel-->
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
         <!--條目-->
        <TextBlock Text="Allan"/>
        <TextBlock Text="Kevin"/>
        <TextBlock Text="Drew"/>
        <TextBlock Text="Timothy"/>
    </ListBox>
</Grid>

效果如下:

DataTemplate與ControlTemplate的關系與應用

DataTemplate與ControlTemplate的關系

控制元件只是個資料和行為的載體、是個抽象的概念,至于它本身會長成什么樣子(控制元件內部結構)、它的資料會長成什么樣子(資料顯示結構)都是靠Template生成的,

決定控制元件外觀的是ControlTemplate,決定資料外觀的是DataTemplate,它們正是Control類的Template和ContentTemplate兩個屬性的值,它們的作用范圍如下所示:

Template最終都是要作用在控制元件上的,這個控制元件就是Template的目標控制元件,也叫模板化控制元件(Templated Control)

DataTemplate施加在資料物件上生成的一組控制元件的載體一般是落實在一個ContentPresenter 物件上,ContentPresenter類只有ContentTemplate屬性、沒有Template屬性,這就證明了承載由DataTemplate生成的一組控制元件是它的專門用途,

由ControlTemplate生成的控制元件樹其樹根就是ControlTemplate的目標控制元件,此模板化控制元件的Template屬性值就是這個ControlTemplate實體;由DataTemplate生成的控制元件樹其樹根是一個ContentPresenter控制元件,此模板化控制元件的ContentTemplate屬性值就是這個DataTemplate實體,因為ContentPresenter 控制元件是ControlTemplate控制元件樹上的一個結點,所以DataTemplate控制元件樹是ControlTemplate控制元件樹的一棵子樹

每個控制元件都有個名為TemplatedParent的屬性,如果它的值不為null,說明這個控制元件是由Template自動生成的,而屬性值就是應用了模板的控制元件(模板的目標,模板化控制元件),如果由Template生成的控制元件使用了TemplateBinding獲取屬性值,則TemplateBinding的資料源就是應用了這個模板的目標控制元件,

回顧一下開頭的DataTemplate實體代碼:

<DataTemplate>
    <Grid>
        <StackPanel Orientation="Horizontal">
            <Grid>
                <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"/>
                <TextBlock Text="{Binding Year}"/>
            </Grid>
            <TextBlock Text="{Binding Price}" Margin="5,0"/>
        </StackPanel>
    </Grid>
</DataTemplate>

這里用到的是普通Binding而不是TemplateBinding,如果把資料物件賦值給ContentPresenter的DataContext屬性,由DataTemplate生成的控制元件自然會找到這個資料物件并把它當作自己的資料源,

DataTemplate與ControlTemplate的應用

為Template設定其應用目標有兩種方法,一種是逐個設定控制元件的Template、ContentTemplate、ItemsTemplate、CellTemplate等屬性,不想應用Template的控制元件不設定;另一種是整體應用,即把Template應用在某個型別的控制元件或資料上,

把ControlTemplate應用在所有目標上

把ControlTemplate應用在所有目標上需要借助Style來實作,但Style不能標記x:Key,如下面的代碼:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        Title="MainWindow" Height="133.333" Width="241.458">
    <Window.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBox}">
                        <!--與前面例子相同-->
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Margin" Value="https://www.cnblogs.com/timefiles/p/5"/>
            <Setter Property="BorderBrush" Value="https://www.cnblogs.com/timefiles/p/Black"/>
            <Setter Property="Height" Value="https://www.cnblogs.com/timefiles/p/25"/>
        </Style>
    </Window.Resources>
    <StackPanel>
        <TextBox/>
        <TextBox/>
        <TextBox Style="fx:Null]"Margin="5"/>
    </StackPanel>
</Window>

Style沒有x:Key標記,默認為應用到所有由x:Type指定的控制元件上,如果不想應用則需把控制元件的Style標記為{x:Null},效果如下所示:

把DataTemplate應用在某個資料型別上

把DataTemplate應用在某個資料型別上的方法是設定DataTemplate的DataType屬性,并且DataTemplate作為資源時也不能帶有x:Key標記,如下面的代碼:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:local="clr-namespace:WpfApp"
        xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
        Title="MainWindow" Height="500" Width="378.571">
    <Window.Resources>
        <!--Data Template-->
        <DataTemplate DataType="{x:Type local:Unit}">
            <Grid>
                <StackPanel Orientation="Horizontal">
                    <Grid>
                        <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"/>
                        <TextBlock Text="{Binding Year}"/>
                    </Grid>
                    <TextBlock Text="{Binding Price}" Margin="5.0"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
        <!--資料源-->
        <c:ArrayList x:Key="ds">
            <local:Unit Year="2001年" Price="100"/>
            <local:Unit Year="2002年" Price="120"/>
            <local:Unit Year="2003年" Price="140"/>
            <local:Unit Year="2004年" Price="160"/>
            <local:Unit Year="2005年" Price="180"/>
            <local:Unit Year="2006年" Price="200"/>
        </c:ArrayList>
    </Window.Resources>
    <StackPanel>
        <ListBox ItemsSource="{StaticResource ds}"/>
        <ComboBox ItemsSource="{StaticResource ds}" Margin="5"/>
    </StackPanel>
</Window>

代碼中DataTemplate的目標資料型別和ListBox的條目型別都是Unit:

public class Unit
{
    public int Price { get; set; }
    public string Year { get; set; }
}

DataTemplate會自動加載到所有Unit型別物件上,效果如下:

很多時候資料是以XML形式存盤的,DataTemplate具有直接把XML資料結點當作目標物件的功能——XML資料中的元素名(標簽名)可以作為DataType,元素的子結點和Attribute可以使用XPath來訪問,
下面的代碼使用XmlDataProvider作為資料源,代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:local="clr-namespace:WpfApp"
        xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
        Title="MainWindow" Height="500" Width="333.035">
    <Window.Resources>
        <!--Data Template-->
        <DataTemplate DataType="Unit">
            <Grid>
                <StackPanel Orientation="Horizontal">
                    <Grid>
                        <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding XPath=@Price}"/>
                        <TextBlock Text="{Binding XPath=@Year}"/>
                    </Grid>
                    <TextBlock Text="{Binding XPath=@Price}" Margin="5.0"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
        <!--資料源-->
        <XmlDataProvider x:Key="ds" XPath="Units/Unit">
            <x:XData>
                <Units xmlns="">
                    <Unit Year="2001" Price="100"/>
                    <Unit Year="2001" Price="120"/>
                    <Unit Year="2001" Price="140"/>
                    <Unit Year="2001" Price="160"/>
                    <Unit Year="2001" Price="180"/>
                    <Unit Year="2001" Price="200"/>
                </Units>
            </x:XData>
        </XmlDataProvider>
    </Window.Resources>
    <StackPanel>
        <ListBox ItemsSource="{Binding Source={StaticResource ds}}"/>
        <ComboBox ItemsSource="{Binding Source={StaticResource ds}}" Margin="5"/>
    </StackPanel>
</Window>

顯示層級資料的模板HierarchicalDataTemplate

XML最大的優勢是可以方便地表示帶有層級的資料,WPF準備了TreeView和Menultem控制元件用來顯示層級資料,能夠幫助層級控制元件顯示層級資料的模板是HierarchicalDataTemplate

第一個例子是使用TreeView顯示多層級、不同型別資料,需要為每種資料設計一個模板,有機會使每種資料型別有自己獨特的外觀,
資料保存在專案根目錄的Data.xml檔案中,內容如下:

<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
  <Grade Name="一年級">
    <Class Name="甲班">         
      <Group Name="A組"/>
      <Group Name="B組"/>
      <Group Name="C組"/>
    </Class>
  <Class Name="乙班">
    <Group Name="A組"/>
    <Group Name="B組"/>
    <Group Name="C組"/>
  </Class>
  </Grade>
  <Grade Name="二年級">
    <Class Name="甲班">
      <Group Name="A組"/>
      <Group Name="B組"/>
      <Group Name="C組"/>
    </Class>
    <Class Name="乙班">
      <Group Name="A組"/>
      <Group Name="B組"/>
      <Group Name="C組"/>
    </Class>
  </Grade>
</Data>

程式的XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         
        Title="MainWindow" Height="400" Width="333.035">    
    <Window.Resources>
        <!--資料源-->
        <XmlDataProvider x:Key="ds" Source="Data.xml" XPath="Data/Grade"/>
        <!--年級模板-->
        <HierarchicalDataTemplate DataType="Grade" ItemsSource="{Binding XPath=Class}">
            <TextBlock Text="{Binding XPath=@Name}"/>
        </HierarchicalDataTemplate>
        <!--班級模板-->
        <HierarchicalDataTemplate DataType="Class" ItemsSource="{Binding XPath=Group}">
            <RadioButton Content="{Binding XPath=@Name}" GroupName="gn"/>
        </HierarchicalDataTemplate>
        <!--小組模板-->
        <HierarchicalDataTemplate DataType="Group" ItemsSource="{Binding XPath=Student}">
            <CheckBox Content="{Binding XPath=@Name}"/>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid>
        <TreeView Margin="5" ItemsSource="{Binding Source={StaticResource ds}}"/>
    </Grid>
</Window>

效果如下:

第二個例子是同一種資料型別的嵌套結構,這種情況下只需設計一個HierarchicalDataTemplate,它會產生自動迭代應用的效果,
資料仍然存放在Data.xml檔案中,資料全都是Operation型別:

<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
  <Operation Name="檔案" Gesture="F">
    <Operation Name="新建" Gesture="N">
      <Operation Name="專案" Gesture="Control+P"/>
      <Operation Name="網站" Gesture="Control+W"/>
      <Operation Name="檔案" Gesture="Control+D"/>
    </Operation>
    <Operation Name="保存" Gesture="S"/>
    <Operation Name="列印" Gesture="P"/>
    <Operation Name="退出" Gesture="X"/>
  </Operation>
  <Operation Name="編輯" Gesture="E">
    <Operation Name="拷貝" Gesture="Control+C"/>
    <Operation Name="剪切" Gesture="Control+X"/>
    <Operation Name="粘貼" Gesture="Control+V"/>
  </Operation>
</Data>

程式的XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="300" Width="300">
    <Window.Resources>
        <!--資料源-->
        <XmlDataProvider x:Key="ds" Source="Data.xml" XPath="Data/Operation"/>
        <!--Operation 模板-->
        <HierarchicalDataTemplate DataType="Operation" ItemsSource="{Binding XPath=Operation}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding XPath=@Name}" Margin="10,0"/>
                <TextBlock Text="{Binding XPath=@Gesture}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <StackPanel MenuItem.Click="StackPanel_Click">
        <Menu ItemsSource="{Binding Source={StaticResource ds}}"/>
    </StackPanel>
</Window>

HierarchicalDataTemplate的作用目標是Menultem的Header,可以從被單擊Menultem的Header中取出XML資料,事件處理器代碼如下:

private void StackPanel_Click(object sender, RoutedEventArgs e)
{
    MenuItem mi = e.OriginalSource as MenuItem; 
    XmlElement xe = mi.Header as XmlElement; 
    MessageBox.Show(xe.Attributes["Name"].Value);
}

效果如下:

注:可以維護一個CommandHelper類,根據拿到的資料來決定執行什么RoutedCommand,

從外界訪問Template內部的控制元件及其屬性值

由ControlTemplate或DataTemplate生成的控制元件都是“由Template生成的控制元件”,ControlTemplate和DataTemplate兩個類均派生自FrameworkTemplate類,有個名為FindName的方法可以檢索其內部控制元件

檢索ControlTemplate生成的控制元件

設計一個ControlTemplate并把它應用在一個UserControl上,界面上還有一個Button,在它的Click事件處理器中檢索由ControlTemplate生成的代碼,
程式的XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="MainWindow" Height="160" Width="300">
    <Window.Resources>
        <ControlTemplate x:Key="cTmp">
            <StackPanel Background="Orange">
                <TextBox x:Name="textBox1" Margin="6"/>
                <TextBox x:Name="textBox2" Margin="6,0"/>
                <TextBox x:Name="textBox3" Margin="6"/>
            </StackPanel>                  
        </ControlTemplate>
    </Window.Resources>
    <StackPanel Background="Yellow">
        <UserControl x:Name="uc" Template="{ StaticResource cTmp}" Margin="5"/> 
        <Button Content="Find by Name" Width="120" Height="30" Click="Button_Click"/>
    </StackPanel>
</Window>

Button的Click事件處理器代碼如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
    TextBox tb = this.uc.Template.FindName("textBox1", this.uc) as TextBox; 
    tb.Text = "Hello WPF"; 
    StackPanel sp = tb.Parent as StackPanel;
    (sp.Children[1] as TextBox).Text = "Hello ControlTemplate";
    (sp.Children[2] as TextBox).Text = "I can find you!";
}

效果如下:

檢索DataTemplate生成的控制元件

尋找到一個由DataTemplate生成的控制元件后,正確做法是獲得單純與用戶界面相關的資料(比如控制元件的寬度、高度等),如果是獲取與業務邏輯相關的資料則要考慮程式的設計是不是出了問題——因為WPF采用資料驅動UI邏輯,獲取業務邏輯資料的事情在底層就能做到,一般不會跑到表層上來找,

作為業務邏輯資料的類如下:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Skill { get; set; }
    public bool HasJob { get; set; }
}

簡單例子

界面XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApp"
        Title="MainWindow" Height="175" Width="220">
    <Window.Resources>
        <!--資料物件-->
        <local:Student x:Key="stu" Id="1" Name="Timothy" Skill="WPF" HasJob="True"/>
        <!--Data Template-->
        <DataTemplate x:Key="stuDT">
            <Border BorderBrush="Orange" BorderThickness="2" CornerRadius="5">
                <StackPanel>
                    <TextBlock Text="{Binding Id}" Margin="5"/>
                    <TextBlock x:Name="textBlockName" Text="{Binding Name}" Margin="5"/>
                    <TextBlock Text="{Binding Skill}" Margin="5"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <StackPanel Background="Yellow">
        <ContentPresenter x:Name="cp" Content="{StaticResource stu}" ContentTemplate="{StaticResource stuDT}" Margin="5"/>
        <Button Content="Find" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
</Window>

Button的Click事件處理器代碼如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
    //使用DataTemplate的FindName方法獲取由DataTemplate生成的控制元件并訪問其屬性
    TextBlock tb = this.cp.ContentTemplate.FindName("textBlockName", this.cp) as TextBlock; 
    MessageBox.Show(tb.Text);

    //如果為了獲取Student的某個屬性,應該使用被注釋的代碼,直接使用底層資料
    //Student stu=this. cp. Content as Student;
    // MessageBox.Show(stu.Name);
}

效果如下:

復雜例子

DataTemplate的一個常用之處是GridViewColumn的CellTemplate屬性,把GridViewColumn放置在GridView控制元件里就可以生成表格了,
GridViewColumn的默認CellTemplate是使用TextBlock只讀性地顯示資料,想讓用戶能修改資料或者使用CheckBox顯示bool型別資料就需要自定義DataTemplate,

準備資料集合、呈現資料的作業全部由XAML代碼來完成:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApp"
        xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
        Title="MainWindow" Height="212.72" Width="280">
    <Window.Resources>
        <!--資料集合-->
        <c:ArrayList x:Key="stuList">
            <local:Student Id="1" Name="Timoty Liu" Skill="WPF" HasJob="True"/>
            <local:Student Id="2" Name="Tom Chang" Skill="BI/SQL" HasJob="True"/>
            <local:Student Id="3" Name="Guan Chong" Skill="Writing" HasJob="False"/>
            <local:Student Id="4" Name="Shanshan" Skill="C#/Java" HasJob="False"/>
            <local:Student Id="5" Name="Pingping Zhang" Skill="Writing" HasJob="False"/>
            <local:Student Id="6" Name="Kenny Tian" Skill="ASP.NET" HasJob="False"/>
        </c:ArrayList>
        <!--Data Templates-->
        <DataTemplate x:Key="nameDT">
            <TextBox x:Name="textBoxName" Text="{Binding Name}" GotFocus="TextBoxName_GotFocus"/>
        </DataTemplate>
        <DataTemplate x:Key="skillDT">
            <TextBox x:Name="textBoxSkill" Text="{Binding Skill}"/>
        </DataTemplate>
        <DataTemplate x:Key="hjDT">
            <CheckBox x:Name="checkBoxJob" IsChecked="{Binding HasJob}"/>
        </DataTemplate>
    </Window.Resources>
    <!--主體布局-->
    <Grid Margin="5">
        <ListView x:Name="listViewStudent" ItemsSource="{StaticResource stuList}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="ID" DisplayMemberBinding="{Binding Id}"/>
                    <GridViewColumn Header="姓名" CellTemplate="{StaticResource nameDT}"/>
                    <GridViewColumn Header="技術" CellTemplate="{StaticResource skillDT}"/>
                    <GridViewColumn Header="已作業" CellTemplate="{StaticResource hjDT}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

為顯示姓名的TextBox添加GotFocus事件的處理器:

private void TextBoxName_GotFocus(object sender, RoutedEventArgs e)
{
    //訪問業務邏輯資料            
    TextBox tb = e.OriginalSource as TextBox;                         //獲取事件發起的源頭
    ContentPresenter cp = tb.TemplatedParent as ContentPresenter;     //獲取模板目標
    Student stu = cp.Content as Student;                              //獲取業務邏輯資料
    this.listViewStudent.SelectedItem = stu;                          //設定ListView的選中項

    // 訪問界面元素
    //獲得包裝著指定條目資料的容器,本例中是一個包裝著Student物件的ListViewItem(Content是Student物件)
    ListViewItem lvi = this.listViewStudent.ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem;
    //遍歷各個結點
    CheckBox chb = this.FindVisualChild<CheckBox>(lvi);
    MessageBox.Show(chb.Name);
}

private ChildType FindVisualChild<ChildType>(DependencyObject obj) where ChildType: DependencyObject
{
    //借助VisualTreeHelper類檢索由DataTemplate生成的控制元件
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is ChildType)
        {
            return child as ChildType;
        }
        else
        {
            ChildType childOfChild = FindVisualChild<ChildType>(child);
            if (childOfChild != null)
            {
                return childOfChild;
            }
        }
    }
    return null;
}

效果如下:

如果真的要尋找由DataTemplate生成的控制元件,對于結構簡單的控制元件,可以使用DataTemplate物件的FindName方法;對于結構復雜的控制元件,則需要借助VisualTreeHelper來實作

Style

構成Style最重要的兩種元素是Setter和Trigger,Setter類幫助我們設定控制元件的靜態外觀風格,Trigger類則幫助我們設定控制元件的行為風格,

Style中的Setter

Setter,屬性值的設定器,給屬性賦值的時候一般都采用“屬性名=屬性值”的形式,Setter類的Property屬性用來指明想為目標的哪個屬性賦值;Setter類的Value屬性則是提供的屬性值

在Window的資源詞典中放置一個針對TextBlock的Style,Style中使用若干Setter來設定TextBlock的一些屬性,程式中的TextBlock就會具有統一的風格(除非使用{x:Null}顯示地清空Style),
XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        Title="MainWindow" Height="214.26" Width="347.742">
    <Window.Resources>
        <Style TargetType="TextBlock">
            <!--Style的內容屬性是Setters,可以直接在<Style>標簽的內容區域寫Setter-->            
            <Setter Property="FontSize" Value="https://www.cnblogs.com/timefiles/p/24"/>
            <Setter Property="TextDecorations" Value="https://www.cnblogs.com/timefiles/p/Underline"/>
            <Setter Property="FontStyle" Value="https://www.cnblogs.com/timefiles/p/Italic"/>            
        </Style>
    </Window.Resources>
    <StackPanel Margin="5">
        <TextBlock Text="Hello WPF!"/>
        <TextBlock Text="This is a sample for Style!"/>
        <TextBlock Text="by Tim 2009.12.23" Style="{x:Null}"/>
    </StackPanel>
</Window>

效果如下:

注:根如果想設定控制元件的ControlTemplate,只需要把Setter的Property設為Template并為Value提供一個ControlTemplate物件即可,

Style中的Trigger

Trigger,觸發器,即當某些條件滿足時會觸發一個行為(比如某些值的變化或影片的發生等),主要型別有:

  • 事件觸發型的EventTrigger
  • 資料變化觸發型的Trigger/DataTrigger
  • 多條件觸發型的MultiTrigger/MultiDataTrigger

基本Trigger

Trigger類是最基本的觸發器,類似于Setter,Trigger也有Property和Value這兩個屬性,Property是Trigger 關注的屬性名稱,Value是觸發條件
Trigger類還有一個Setters屬性,此屬性值是一組Setter,一旦觸發條件被滿足,這組Setter的“屬性一值”就會被應用,觸發條件不再滿足后,各屬性值會被還原,

一個針對CheckBox的Style,當CheckBox的IsChecked屬性為true的時候前景色和字體會改變,XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        Title="MainWindow" Height="185.425" Width="300">
    <Window.Resources>
        <Style TargetType="CheckBox">
            <!--Triggers不是Style的內容屬性,<Style.Triggers>...</Style.Triggers>這層標簽不能省略-->
            <Style.Triggers>
                <Trigger Property="IsChecked" Value="https://www.cnblogs.com/timefiles/p/true">
                    <!--Trigger的Setters屬性是Trigger的內容屬性,<Trigger.Setters.…</Trigger.Seters>這層標簽可以省略-->
                    <!--<Trigger.Setters>-->
                    <Setter Property="FontSize" Value="https://www.cnblogs.com/timefiles/p/20"/>
                    <Setter Property="Foreground" Value="https://www.cnblogs.com/timefiles/p/Orange"/>
                    <!--</Trigger.Setters>-->
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <CheckBox Content="悄悄的我走了" Margin="5"/>
        <CheckBox Content="正如我悄悄的來" Margin="5,0"/>
        <CheckBox Content="我揮一揮衣袖" Margin="5"/>
        <CheckBox Content="不帶走一片云彩" Margin="5,0"/>
    </StackPanel>
</Window>

效果如下:

MultiTrigger

MultiTrigger必須多個條件同時成立時才會被觸發,比Trigger多了一個Conditions屬性,需要同時成立的條件就存盤在這個集合中,

改動上面的例子,要求同時滿足CheckBox被選中且Content為“正如我悄悄的來”時才會被觸發,XAML代碼如下(僅Style部分):

<Style TargetType="CheckBox">           
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsChecked" Value="https://www.cnblogs.com/timefiles/p/true"/>
                <Condition Property="Content" Value="https://www.cnblogs.com/timefiles/p/正如我悄悄的來"/>
            </MultiTrigger.Conditions>
            <Setter Property="FontSize" Value="https://www.cnblogs.com/timefiles/p/20"/>
            <Setter Property="Foreground" Value="https://www.cnblogs.com/timefiles/p/Orange"/>                    
        </MultiTrigger>
    </Style.Triggers>
</Style>

效果如下:

由資料觸發的DataTrigger

基于資料執行某些判斷可以考慮使用DataTrigger,DataTrigger物件的Binding屬性會把資料源源不斷送過來,一旦送來的值與Value屬性一致DataTrigger即被觸發,

下面例子中,當TextBox的Text長度小于7個字符時其Border會保持紅色,XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
        xmlns:local="clr-namespace:WpfApp"
        Title="MainWindow" Height="123.435" Width="204.297">
    <Window.Resources>
        <local:L2BConverter  x:Key="cvtr"/>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{ Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text.Length, Converter={ StaticResource cvtr}}" Value="https://www.cnblogs.com/timefiles/p/false">
                    <Setter Property="BorderBrush" Value="https://www.cnblogs.com/timefiles/p/Red"/>
                    <Setter Property="BorderThickness" Value="https://www.cnblogs.com/timefiles/p/1"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <TextBox Margin="5"/>
        <TextBox Margin="5,0"/>
        <TextBox Margin="5"/>
    </StackPanel>
</Window>

為了將控制元件自己作為資料源需要使用RelativeSource,如果不明確指出Source時Binding會把控制元件的DataContext屬性當作資料源而非把控制元件自身當作資料源,

Binding的Path被設定為Text.Length,字串的長度是一個具體的數字,基于這個長度值做判斷時需要用到Converter,創建如下的Converter:

public class L2BConverter : IValueConverter 
{ 
    public object Convert(object value,Type targetype,object parameter, CultureInfo culture)
    {
        int textLength = (int)value; 
        return textLength > 6 ? true : false;
    }
    public object ConvertBack(object value, Type targetype, object parameter, CultureInfo culture) 
    {
        throw new NotImplementedException();
    }
}

經Converter轉換后,長度值會轉換成bool型別值,DataTrigger的Value被設定為false,

效果如下:

多資料條件觸發的MultiDataTrigger

遇到要求多個資料條件同時滿足時才能觸發變化的需求,此時可以考慮使用MultiDataTrigger,

用戶界面上使用ListBox顯示了一列Student資料,當Student物件同時滿足ID為2、Name為Tom的時候條目就高亮顯示,示例的XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         
        Title="MainWindow" Height="123.435" Width="300">
    <Window.Resources>
        <Style TargetType="ListBoxItem">
            <!--使用Style設定DataTemplate-->
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding ID}" Width="60"/>
                            <TextBlock Text="{Binding Name}" Width="120"/>
                            <TextBlock Text="{Binding Age}" Width="60"/>                            
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
            <!--MultiDataTrigger-->
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding Path=ID}" Value="https://www.cnblogs.com/timefiles/p/2"/>
                        <Condition Binding="{Binding Path=Name}" Value="https://www.cnblogs.com/timefiles/p/Tom"/>
                    </MultiDataTrigger.Conditions>
                    <MultiDataTrigger.Setters>
                        <Setter Property="Background" Value="https://www.cnblogs.com/timefiles/p/Orange"/>
                    </MultiDataTrigger.Setters>
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>        
    </Window.Resources>
    <StackPanel>
        <ListBox x:Name="listBoxStudent" Margin="5"/>
    </StackPanel>
</Window>

C#后臺代碼如下:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        List<Student> studentList = new List<Student>() 
        {new Student(){ ID=1, Name="Tim", Age=21},
        new Student(){ ID=2, Name="Tom", Age=22 }}; 
        
        this.listBoxStudent.ItemsSource = studentList;
    } 
}
public class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

效果如下:

由事件觸發的EventTrigger

EventTrigger是觸發器中最特殊的一個,它是由事件來觸發,被觸發后執行一段影片,U1層的影片效果往往與EventTrigger相關聯,

創建了一個針對Button的Style,這個Style包含兩個EventTrigger,一個由MouseEnter事件觸發,另一個由MouseLeave事件觸發,XAML代碼如下:

<Window x:
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         
        Title="MainWindow" Height="200" Width="300">
    <Window.Resources>
        <Style TargetType="Button">
            <Style.Triggers>
                <!--滑鼠進入-->
                <EventTrigger RoutedEvent="MouseEnter">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                            <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
                <!--滑鼠離開-->
                <EventTrigger RoutedEvent="MouseLeave">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Canvas>
        <Button Width="40" Height="40" Content="OK"/>
    </Canvas>
</Window>

效果如下:

注:觸發器并非只能應用在Style中一—各種Template也可以擁有自己的觸發器,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/270464.html

標籤:WPF

上一篇:WPF 筆跡觸摸點收集工具

下一篇:WPF教程四:欄位、屬性、依賴項屬性的演變程序

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more