WPF教程十一:簡單了解並使用控件模板
這一章梳理控件模板,每個WPF控件都設計成無外觀的,但是行為設計上是不允許改變的,比如使用Button的控件時,按鈕提供了能被點擊的內容,那么自由的改變控件外觀時,Button作為按鈕的本質是沒有改變的,同樣響應的是按鈕的邏輯。所以我們使用控件模板主要也是為了修改對應控件的顯示內容,這個比樣式資源和觸發器可改變的內容更多,同時控件模板也能結合樣式和觸發器自定義更多的顯示內容。
我平時寫控件模板的時候,我喜歡打開Blend然后再Blend下通過鼠標直接生成對應的模板,但是這里是了解,所以我們使用代碼的方式來刨析模板下都有什么,這樣會更深入的了解控件模板,而不是只會用Blend,Blend是一個很方便的工具,但是細節我們還是要關注一下的。
我們通過創建一個沒有任何修改的Button和一個設置了最小代碼量的控件模板來觀察2個的Button結構有什么不一致。來理解模板的作用。
復制以下代碼,嘗試跑起來觀察一下,代碼如下:
<Window x:Class="WPFControlTemplate.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:WPFControlTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="CustomDefaultButtonTemplate" TargetType="Button">
<Border BorderBrush="Red" BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Width="230" Height="30" Content="默認按鈕" Click="GetDefaultButtonControlTemplate_OnClick"/>
<TextBox Height="200" Text="{Binding TemplateDefaultContent}"/>
<Button Width="230" Height="30" Template="{StaticResource CustomDefaultButtonTemplate}" Content="設置了控件模板,給你展示我內部是啥樣" Click="GetCustomButtonControlTemplate_OnClick"/>
<TextBox Height="130" Text="{Binding TemplateCustomContent}"/>
</StackPanel>
</Grid>
</Window>
using System;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Xml;
namespace WPFControlTemplate
{
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
TemplateContent viewmodel = new TemplateContent();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewmodel;
}
private string GetControlTempalte(ControlTemplate template)
{
StringBuilder sb = new StringBuilder();
try
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter write = XmlWriter.Create(sb, settings);
XamlWriter.Save(template, write);
}
catch (Exception ex)
{
sb.Append($"Error generating template:{ex.Message}");
}
return sb.ToString();
}
private void GetCustomButtonControlTemplate_OnClick(object sender, RoutedEventArgs e)
{
var buttonTemplate = (sender as Button).Template;
var buttonTemplateStr = GetControlTempalte(buttonTemplate);
viewmodel.TemplateCustomContent = buttonTemplateStr;
}
private void GetDefaultButtonControlTemplate_OnClick(object sender, RoutedEventArgs e)
{
var buttonTemplate = (sender as Button).Template;
var buttonTemplateStr = GetControlTempalte(buttonTemplate);
viewmodel.TemplateDefaultContent = buttonTemplateStr;
}
}
public class TemplateContent : INotifyPropertyChanged
{
private string templateDefaultContent = string.Empty;
public string TemplateDefaultContent
{
get
{
return templateDefaultContent;
}
set
{
templateDefaultContent = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TemplateDefaultContent"));
}
}
private string templateCustomContent = string.Empty;
public string TemplateCustomContent
{
get
{ return templateCustomContent; }
set
{
templateCustomContent = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TemplateCustomContent"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
通過復制出來上下2個ControlTemplate下的內容對比,發現默認的Button中ControlTemplate包含一個Border嵌套一個ContentPresenter其中很多屬性都綁定了TemplateBinding的值,他代表的是從模板中綁定,獲取使用模板的控件對應的控件值,而我們自己創建的ControlTemplate的Border則都是自己寫的BorderThickness=1和BorderBrush=#FFFF0000(紅色),值都是我們直接在模板中寫好的,所以多處引用的話,固定的這些值都是一樣的。
最近胃不好需要健康飲食,要多吃蔬菜,勤鍛煉,所以在寫這個自定義Template的話,我們通過模板讓所有使用控件模板的Button都帶一個一樣的蔬菜圖片(不允許更換圖片,自定義控件會講使用依賴項屬性綁定圖片路徑,這里模板就一張一樣的蔬菜圖片)。
<Window x:Class="WPFControlTemplate.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:WPFControlTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="CustomDefaultButtonTemplate" TargetType="Button">
<Border BorderBrush="Red" BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
<ControlTemplate x:Key="CustomAddImageButtonTemplate" TargetType="Button" >
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Image Height="80" Source="pack://application:,,,/Images/2.png"/>
<TextBlock Text="{TemplateBinding Content}"/>
</StackPanel>
</Border>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Width="230" Height="30" Content="默認按鈕" Click="GetDefaultButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateDefaultContent}"/>
<Button Width="230" Height="30" Template="{StaticResource CustomDefaultButtonTemplate}" Content="設置了控件模板,給你展示我內部是啥樣" Click="GetCustomButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateCustomContent}"/>
<StackPanel Orientation="Horizontal">
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="蔬菜"/>
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="健康"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
我們定義了一個CustomAddImageButtonTemplate的控件模板,並在里面創建了一個Images和Textblock。一個用於放置了一個蔬菜的圖片,一個是綁定了template的顯示文本,模板綁定和普通的數據綁定類似,因為它們是專門在控件模板中使用的,它們只支持單向數據綁定,它們可以從控件向模板傳遞信息,但不能從模板向控件傳遞信息。控件模板寫到現在這個程度有點丑,我們在控件模板中加一個半透效果,鼠標移動上去圖片半透,鼠標移開,圖片顯示。
<Window x:Class="WPFControlTemplate.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:WPFControlTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="CustomDefaultButtonTemplate" TargetType="Button">
<Border BorderBrush="Red" BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
<ControlTemplate x:Key="CustomAddImageButtonTemplate" TargetType="Button" >
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Image x:Name="img" Height="80" Source="pack://application:,,,/Images/2.png"/>
<TextBlock Text="{TemplateBinding Content}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="img" Property="Opacity" Value="0.3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Width="230" Height="30" Content="默認按鈕" Click="GetDefaultButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateDefaultContent}"/>
<Button Width="230" Height="30" Template="{StaticResource CustomDefaultButtonTemplate}" Content="設置了控件模板,給你展示我內部是啥樣" Click="GetCustomButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateCustomContent}"/>
<StackPanel Orientation="Horizontal">
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="蔬菜"/>
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="健康"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
這樣修改控件模板的話,在鼠標經過的時候圖片就會有半透效果。
我這里寫的控件模板很丑,模板和樣式都可以修改外觀,樣式可以調整控件的屬性,但是不能重新定義外觀,但是通過控件模板,則可以實現這個效果,后面慢慢的視覺效果就會變得更好了。我們添加新的代碼,使用事件觸發器實現移入移出時的動畫效果,修改代碼如下:
<Window x:Class="WPFControlTemplate.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:WPFControlTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="CustomDefaultButtonTemplate" TargetType="Button">
<Border BorderBrush="Red" BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
<ControlTemplate x:Key="CustomAddImageButtonTemplate" TargetType="Button" >
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Image x:Name="img" Height="80" Source="pack://application:,,,/Images/2.png"/>
<TextBlock Text="{TemplateBinding Content}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="img" Property="Opacity" Value="0.3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate TargetType="Button" x:Key="FadeAwayAnimitionButtonTemplate">
<Border BorderBrush="Blue" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Image x:Name="img" Height="80" Source="pack://application:,,,/Images/2.png"/>
<TextBlock Text="{TemplateBinding Content}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1" To="0.3" Storyboard.TargetName="img" Storyboard.TargetProperty="Opacity" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.5" To="1" Storyboard.TargetName="img" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Width="230" Height="30" Content="默認按鈕" Click="GetDefaultButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateDefaultContent}"/>
<Button Width="230" Height="30" Template="{StaticResource CustomDefaultButtonTemplate}" Content="設置了控件模板,給你展示我內部是啥樣" Click="GetCustomButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateCustomContent}"/>
<StackPanel Orientation="Horizontal">
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="蔬菜"/>
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="健康"/>
<Button Width="140" Height="80" Template="{StaticResource FadeAwayAnimitionButtonTemplate}" Content="動畫"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
這樣就實現了移入移出時的動畫效果。
注意:因為同樣一種控件在程序中為了顯示不同的外觀,可能有對應的N個控件模板,為了不讓代碼現得雜亂無章,可以把每個類型的控件對應的模板都歸類到一個資源字典中,比如Button.xaml、TextBox.xaml請注意這個添加的是Resource Dictionary,不是WPF窗體,因為如果混在一起,內容一旦太多,無法找你到要的控件模板。記得再程序集下的ResourceDictionary下添加你的資源文件。同樣的如果控件模板內,有使用的畫刷,建議也單獨拿出來,單獨存放在這種資源下的Button.xaml或者TextBox.xaml。
這里寫一個重要的知識點,在樣式中使用控件模板,並重新設置控件模板的部分內容樣式。我們修改了FadeAwayAnimitionButtonTemplate控件模板的border的顏色和粗細使用綁定。然后在資源下使用模板,並在樣式下修改模板border的粗細和顏色。我們修改資源CustomButtonStyle。樣式中使用模板可以更高效的設計好的控件樣式和行為,在樣式中使用模板代碼修改如下:
<Window x:Class="WPFControlTemplate.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:WPFControlTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="CustomDefaultButtonTemplate" TargetType="Button">
<Border BorderBrush="Red" BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
<ControlTemplate x:Key="CustomAddImageButtonTemplate" TargetType="Button" >
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Image x:Name="img" Height="80" Source="pack://application:,,,/Images/2.png"/>
<TextBlock Text="{TemplateBinding Content}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="img" Property="Opacity" Value="0.3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate TargetType="Button" x:Key="FadeAwayAnimitionButtonTemplate">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Orientation="Horizontal">
<Image x:Name="img" Height="80" Source="pack://application:,,,/Images/2.png"/>
<TextBlock Text="{TemplateBinding Content}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1" To="0.3" Storyboard.TargetName="img" Storyboard.TargetProperty="Opacity" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.5" To="1" Storyboard.TargetName="img" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="CustomButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Control.Template" Value="{StaticResource FadeAwayAnimitionButtonTemplate}"></Setter>
<Setter Property="BorderBrush" Value="Orange"/>
<Setter Property="BorderThickness" Value="3"/>
<Setter Property="FontSize" Value="22"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Button Width="230" Height="30" Content="默認按鈕" Click="GetDefaultButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateDefaultContent}"/>
<Button Width="230" Height="30" Template="{StaticResource CustomDefaultButtonTemplate}" Content="設置了控件模板,給你展示我內部是啥樣" Click="GetCustomButtonControlTemplate_OnClick"/>
<TextBox Height="100" Text="{Binding TemplateCustomContent}"/>
<StackPanel Orientation="Horizontal">
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="蔬菜"/>
<Button Width="140" Height="80" Template="{StaticResource CustomAddImageButtonTemplate}" Content="健康"/>
<Button Width="140" Height="80" BorderBrush="Red" BorderThickness="1" Template="{StaticResource FadeAwayAnimitionButtonTemplate}" Content="動畫"/>
<Button Width="280" Height="80" Style="{StaticResource CustomButtonStyle}" Content="樣式引用控件模板並修改"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
在樣式中不包含模板,只是引用模板,所以在樣式中不能深入到可視化層去修改對應的元素。所以在設計主題或者控件庫的時候再來仔細分析這個問題。
這篇就寫這么多主要內容把,這章本來是要重點寫的,但是腦袋最近確實太迷糊了。最近鬧心的事情太多太多了,但是不想傳播負能量,不想抱怨,自己這幾天沒有寫博客的狀態,進度一拖再拖。但是任何讓你覺得不好的事情不是都已經發生了嗎,未來要充滿希望和動力呀,一起加油各位。
重點來啦!!
1)如果你把上面的代碼敲了一遍,但是你如果跟我一樣RoutedEvent="MouseLeave",Trigger Property="IsMouseOver" Value="true",后面的這些屬性名、事件名不知道怎么寫的時候,是不是一樣懵逼,告訴你一個我發現的技巧,跳轉到你要設置的對象元素上比如Button 使用F12跳轉過去。如果是屬性一般就有了,事件如果沒有的話,去看自己繼承時的父類,需要的屬性和事件一般也都在里面。
2)另外一個重點,使用Blend,可以快速的創建當前模板,然后再修改成自己需要的。具體用法就是再Blend下,右鍵對應的元素編輯模板,創建。就會出來一大串啦。
3)集合類的元素有一個容器樣式、子元素樣式。都可以定制,這里需要你自己去實驗了,我這里就不寫了,原理是一樣的,只是模板對應的內容不一樣,集合類的是容器和子元素2個單獨創建的的模板。可以自己嘗試去百度搜索一下“Blend編輯WPF模板”
這章就寫這么多拉。感謝觀看博文的新人或大佬們,很多時候雖然人生處在低谷,但是總要有些東西需要自己堅持並努力下去,不是嗎。加油!