WPF 详解模板

简介: 这篇博客是我再理解WPF模板的时候写的,其实不算是我写的,而是我经常看的, 我只不过修改了里面的几个句子而已,因为觉得原文很好,沁人心脾,所以就转载到自己的博客中,原文的地址也一并付出: http://www.
这篇博客是我再理解WPF模板的时候写的,其实不算是我写的,而是我经常看的, 我只不过修改了里面的几个句子而已,因为觉得原文很好,沁人心脾,所以就转载到自己的博客中,原文的地址也一并付出:

在WPF中有三大模板ControlTemplate,ItemsPanelTemplate,DataTemplate.其中ControlTemplate和ItemsPanelTemplate是控件模板,DataTemplate是数据模板,他们都派生自FrameworkTemplate抽象类。

1、ControlTemplate

ControlTemplate:控件模板主要有两个重要属性:VisualTree内容属性和Triggers触发器。所谓VisualTree(视觉树),就是呈现我们所画的控件。Triggers可以对我们的视觉树上的元素进行一些变化。一般用于单内容控件。

画一个按钮模板来举例说明:

  <Style TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse Width="100" Height="100">
                                <Ellipse.Fill>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                        <GradientStop Offset="0" Color="blue"/>
                                        <GradientStop Offset="1" Color="LightBlue"/>
                                    </LinearGradientBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                            <Ellipse Width="80" Height="80">
                                <Ellipse.Fill>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                        <GradientStop Offset="0" Color="White"/>
                                        <GradientStop Offset="1" Color="Transparent"/>
                                    </LinearGradientBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Button Content="Hello WPF"/>

 

结果:
image
ControlTemplate之子 ContentControl和ContentPresenter

我们在ControlTemplate中画了两个椭圆,应用于所有的Button按钮,但我们Button中有Content属性(内容为Hello WPF),却没有显示出来。因为这里用ControlTemplate重写了Button的样式,所以我们要在ControlTemplate中增加ContentControl,通过ContentControl中的Content来绑定父容器的Content属性。

<Style TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse Width="100" Height="100">
                                <Ellipse.Fill>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                        <GradientStop Offset="0" Color="blue"/>
                                        <GradientStop Offset="1" Color="LightBlue"/>
                                    </LinearGradientBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                            <Ellipse Width="80" Height="80">
                                <Ellipse.Fill>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                        <GradientStop Offset="0" Color="White"/>
                                        <GradientStop Offset="1" Color="Transparent"/>
                                    </LinearGradientBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                            <ContentControl VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content}"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

 

 

这下内容出来了

image

我们来看一下,ContentControl继承于Control的,用MSDN的话是:表示包含单项内容的控件、ContentControl 可以包含任何类型的公共语言运行库对象。如下ContentControl类图。

image

为了提高性能,我们可以用一个ControlPresenter来代替ContentControl,效果还是一样,那他们有什么区别呢?

来看下ControlPresenter这个类,它继承于FreameworkElement,如下图:

image

ControlPresenter 通常叫做内容占位符。所以我们可以看到

<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>来代替<ContentControl VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content}"/> 这里少了Content绑定父容器,因为ControlPresenter有个隐式的Content="{TemplateBinding Content}",也就是你可以写也可以不写它。

从他们的基类可以看出,ContentControl比ContentPresenter大多了。其实ControlPresenter是一个原始的构建块,而ContentControl是一个带控件模板的成熟控件(里面包含ControlPresenter)。

所以我们一般用ControlPresenter。

ControlTemplate的VisualTree我们讲过了,下面看下他的trigger如何运用。

我们在原来的代码中增加

<ControlTemplate.Triggers>     
        <Trigger Property="IsMouseOver" Value="true">                    









<Setter TargetName="ellipse1" Property="Fill" Value="Red"/>
</Trigger> </ControlTemplate.Triggers>

 

当我们把鼠标移上去的时候就会变成如下图所示:

image

发挥我们的想象力,我们可以根据ControlTemplate做更多的特效。如下:

 

 <Style TargetType="CheckBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="CheckBox">
                        <DockPanel>
                            <ContentPresenter DockPanel.Dock="Left" VerticalAlignment="Center" />
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="30"/>
                                    <ColumnDefinition Width="30"/>
                                </Grid.ColumnDefinitions>
                                <Rectangle Grid.Column="0" Grid.ColumnSpan="2" Fill="Gray"/>
                                <TextBlock x:Name="txtBox"  Foreground="White" />
                            </Grid>
                        </DockPanel>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="txtBox" Property="Grid.Column" Value="1"/>
                                <Setter TargetName="txtBox" Property="Text" Value="On"/>
                                <Setter TargetName="txtBox" Property="Background" Value="LightBlue"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="{x:Null}">
                                <Setter TargetName="txtBox" Property="Grid.Column" Value="0"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="false">
                                <Setter TargetName="txtBox" Property="Grid.Column" Value="0"/>
                                <Setter TargetName="txtBox" Property="Text" Value="OFF"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Grid>
            <CheckBox Width="100" Height="30" Content="Click Me"/>
        </Grid>

 

效果图:点击之前 image点击之后 image

2、ItemsPanelTemplate

ItemsPanelTemplate在MSDN的解释是:ItemsPanelTemplate 指定用于项的布局的面板。 GroupStyle 具有一个类型为 ItemsPanelTemplate 的 Panel 属性。 ItemsControl 类型具有一个类型为ItemsPanelTemplate 的 ItemsPanel 属性。

我们先讲ItemTemplate。它一般用在多个内容控件的模板。比如ListBox。

如下看ListBox应用ItemTemplate:

在xaml中:

<Style TargetType="ListBox">
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <DataTemplate>
                         <Image Source="{Binding UriSource}" Width="100" Height="100"/>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>  <ListBox x:Name="listBox" />

 

在后台代码我们给它一些图片来填充这个ListBox.

public partial class ListBoxUserControl : UserControl
    {
        public ListBoxUserControl()
        {
            InitializeComponent();
            listBox.ItemsSource = LoadImages();
        }

        public List<BitmapImage> LoadImages()
        {
            List<BitmapImage> bitmapImages=new List<BitmapImage>();
            DirectoryInfo directoryInfo = new DirectoryInfo(@"E:\WPFDEMO\ControlTest\ControlTest\Images");
            foreach (var item in directoryInfo.GetFiles("*.jpg"))
            {
                Uri uri=new Uri(item.FullName);
                bitmapImages.Add(new BitmapImage(uri));
            }
            return bitmapImages;
        }
    }

 

image

每一张图片就是一个Item。我们如果想让图片以横向显示。一开始我以为用StackPanel的Orientation=”Horiziontal”,运行时发现这不正确,这样的设置是把Items中某一个Item中的所有内容水平放置,而图片还是垂直排列,可以这样理解,因为这个item现在包括一个Image和Textblock,因为这两个是放在一个StackPanel中的,设置了Horiziontal,那么Image和TextBlock就水平了,但是整体的没有变化,如果要想得到预期的结果,这时就要用到ItemsPanelTemplate这个模板了,我们在ListBox的样式中增加如下红色区域的代码:

 <Style TargetType="ListBox">
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding UriSource}" Width="100" Height="100"/>
                            <TextBlock Text="qq" Background="Red"/>
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
           <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <StackPanel  Orientation="Horizontal" /> </ItemsPanelTemplate> </Setter.Value> </Setter>

        </Style>

 


 

这里在DataTemplate中加了StackPanel 为了说明加在这里的效果只是区分:单个item横向和整个items横向。

image

现在我们要想让布局可以随着窗体宽度变化,我们只要把ItemsPanelTemplate中的StackPanel 改成WrapPanel,并且设置ListBox的ScrollViewer.HorizontalScrollBarVisibility="Disabled",这样才可以看到效果。

image

ControlTemplate之 ItemsPresenter和ContentPresenter

我们先构造一个TreeView

xaml中:

在开始加个Loaded="UserControl_Loaded"

<Grid>        
 <TreeView x:Name="treeview" />
</Grid>

 

后台代码中:
 public class Node
    {
        private IList<Node> _childNodes;
        private string _name;
        public Node()
        {}
        public Node(string name)
        {
            _name = name;
        }
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public IList<Node> ChildNodes
        {
            get
            {
                if (_childNodes==null)
                    _childNodes=new List<Node>();
                return _childNodes;
            }
           
        }
    }


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

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            treeview.PreviewKeyDown += (o,a) => { a.Handled = true; };
            PopulateTreeView();
        }
        void PopulateTreeView()
        {
            Node rootNode=new Node("GrandFather");
            for (int i = 0; i < 2; i++)
            {
                Node child=new Node("Father");
                rootNode.ChildNodes.Add(child);
                for (int j = 0; j < 3; j++)
                {
                    Node child2=new Node("Son");
                    child.ChildNodes.Add(child2);
                }
            }

            Node dummy=new Node();
            dummy.ChildNodes.Add(rootNode);
            treeview.ItemsSource = dummy.ChildNodes;
        }

       

    }

 

 

没有任何样式的TreeView

image

下面我们如何运用ItemsPresenter和ContentPresenter来添加样式。来实现下面这幅图的效果

image
在TreeView中ItemsPresenter和ContentPresenter是什么关系:ContentPresenter是用来显示TreeView中Item的内容 。ItemsPresenter是用来显示它的子项(Item的子项,也就是说child’s Items)。

<Style TargetType="TreeViewItem">
        <Style.Resources>
            <LinearGradientBrush x:Key="ItemAreaBrush" StartPoint="0,0.5" EndPoint="0.5,1">
                <GradientStop Offset="0" Color="#66000000"/>
                <GradientStop Offset="1" Color="#22000000"/>
            </LinearGradientBrush>
            <LinearGradientBrush x:Key="SelectedItemAreaBrush" StartPoint="0.5, 0" EndPoint="0.5, 1">
                <GradientStop Color="Orange" Offset="0" />
                <GradientStop Color="OrangeRed" Offset="1" />
            </LinearGradientBrush>
            <LinearGradientBrush x:Key="ItemBorderBrush" StartPoint="0.5, 0" EndPoint="0.5, 1">
                <GradientStop Color="LightGray" Offset="0" />
                <GradientStop Color="Gray" Offset="1" />
            </LinearGradientBrush>
            <LinearGradientBrush x:Key="SelectedItemBorderBrush" StartPoint="0.5, 0" EndPoint="0.5, 1">
                <GradientStop Color="Yellow" Offset="0" />
                <GradientStop Color="Black" Offset="1" />
            </LinearGradientBrush>
            <DropShadowBitmapEffect x:Key="DropShadowEffect"/>
        </Style.Resources>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="TreeViewItem">
                    <Grid Margin="2">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Border x:Name="border" Background="{StaticResource ResourceKey=ItemAreaBrush}" 
                                BorderBrush="{StaticResource ItemBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="6">
                            <ContentPresenter  ContentSource="Header" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Border>
                        <ItemsPresenter Grid.Row="1"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="true">
                                <Setter TargetName="border" Property="Panel.Background" Value="{StaticResource SelectedItemAreaBrush}"/>
                            </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <StackPanel  Orientation="Horizontal" HorizontalAlignment="Center" IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        
    </Style>

 

看红色代码的部分,这里ContentPresenter显示了父容器的Header的内容,比如GrandFather,Father,Son.而ItemsPresenter则是否让他显示其子元素。比如GrandFather的子元素为Father,没有设置<ItemsPresenter Grid.Row="1"/> 的话。子元素Father是不会显示的。

这里为了突出层次化,运用了ItemsPanelTemplate。最后我们在资源里引用这个样式。

<UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Controls/TreeViewItemStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <HierarchicalDataTemplate DataType="{x:Type controls:Node}" ItemsSource="{Binding ChildNodes}">
                <TextBlock Text="{Binding Name}"/>
            </HierarchicalDataTemplate>
        </ResourceDictionary>
    </UserControl.Resources>

 

 

3、DataTemplate和HierarchicalDataTemplate

DataTemplate就是显示绑定数据对象的模板。
HierarchicalDataTemplate继承于DataTemplate,它专门对TreeViewItemMenuItem的一些数据对象的绑定。

想了解下DataTemplate和HierarchicalDataTemplate,就看以上的例子吧。他们中只要有控件的绑定都会用到。想了解的更深一步,请看下南柯之石的这篇文章。

关于HierarchicalDataTemplate可以参看:http://www.cnblogs.com/lxblog/archive/2012/10/24/2737876.html

相关文章
|
5月前
|
算法 C# UED
浅谈WPF之控件模板和数据模板
WPF不仅支持传统的Windows Forms编程的用户界面和用户体验设计,同时还推出了以模板为核心的新一代设计理念。在WPF中,通过引入模板,将数据和算法的“内容”和“形式”进行解耦。模板主要分为两大类:数据模板【Data Template】和控件模板【Control Template】。
94 8
WPF ComboBox 数据模板
WPF中的控件,有不少都是需要绑定数据的,例如ComboBox控件可以绑定数据,从下拉列表中进行选择。默认情况下,ComboBox控件绑定的数据从显示上比较单一,只能显示固定的文本信息。而为了更好的突出数据展现效果,这里需要使用到WPF中的另一种强大的功能,即数据模板(DataTemplate )
1113 0
WPF ComboBox 数据模板
|
C#
WPF QuickStart系列之样式和模板(Style and Template)
原文:WPF QuickStart系列之样式和模板(Style and Template) 在WPF桌面程序中,当我们想构建一个统一的UI表现时(在不同操作系统下,显示效果一致),此时我们就需要使用到WPF中的样式和模板技术。
991 0
|
C# BI
WPF中通过代码定义模板
原文:WPF中通过代码定义模板 WPF中可以再XAML中定义模板,也可以通过C#代码定义模板,通过代码可能更清楚的看清其逻辑,而且代码的好处就是可以随时动态的去操作,而在XAML中定义的一般都是静态的。
903 0
|
C#
WPF自定义控件(二)の重写原生控件样式模板
原文:WPF自定义控件(二)の重写原生控件样式模板        话外篇: 要写一个圆形控件,用Clip,重写模板,去除样式引用圆形图片可以有这三种方式。   开发过程中,我们有时候用WPF原生的控件就能实现自己的需求,但是样式、风格并不能满足我们的需求,那么我们该怎么办呢?----自定义样式与模板。
1215 0
|
C# 容器
WPF模板(二)应用
原文:WPF模板(二)应用           本次内容来源于电子书,和上一篇一样。 在WPF中有三大模板ControlTemplate,ItemsPanelTemplate,DataTemplate.其中ControlTemplate和ItemsPanelTemplate是控件模板,DataTemplate是数据模板,他们都派生自FrameworkTemplate抽象类。
788 0
|
C#
WPF中的菜单模板
原文:WPF中的菜单模板 资源字典代码如下: ...
1178 0
|
C#
WPF 自定义模板 Button闪亮效果
原文:WPF 自定义模板 Button闪亮效果 Button的选中Effect,我们看下下面的效果:   让我们再放大一点: 怎么设置上面样式呢?直接设置Button的Effect,有点问题,因为Effect不是四周环绕的。
565 0
|
C#
WPF XAML 资源样式模板属性存放位置
原文:WPF XAML 资源样式模板属性存放位置 WPF的XAML 资源申明 类似HTML。 整体来说分3种1.行类资源样式属性 1.1 行内属性 1.2 行内样式 模板(没多大意义) ...
1108 0
|
C#
WPF ListBoxItem模板中添加CheckBox选中问题
原文:WPF ListBoxItem模板中添加CheckBox选中问题 是这样的,需要一个ListBox来展示照片,并添加一个选中的CheckBox.
1596 0