WPF教程十一:簡單了解並使用控件模板


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模板”

這章就寫這么多拉。感謝觀看博文的新人或大佬們,很多時候雖然人生處在低谷,但是總要有些東西需要自己堅持並努力下去,不是嗎。加油!

我創建了一個C#相關的交流群。用於分享學習資料和討論問題。歡迎有興趣的小伙伴:QQ群:542633085


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM