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"/> 结果: ControlTemplate之子 ContentControl和ContentPresenter 我们在ControlTemplate中画了两个椭圆,应用于所有的Button按钮,但我们Button中有Content属性(内容为Hello WPF),却没有显示出来。因为这里用ControlTemplate重写了Button的样式,所以我们也要在ControlTemplate中增加ContentControl。通过ContentControl中的Content来绑定父容器的Content属性。 file:///C:\Users\kt123\AppData\Local\Temp\ksohtml\wps20.tmp.jpg <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> 这下内容出来了看一下,ContentControl继承于Control的,用MSDN的话是:表示包含单项内容的控件、ContentControl 可以包含任何类型的公共语言运行库对象。如下ContentControl类图。
为了提高性能,我们可以用一个ControlPresenter来代替ContentControl,效果还是一样,那他们有什么区别呢? 来看下ControlPresenter这个类,它继承于FreameworkElement,如下图: 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> 当我们把鼠标移上去的时候就会变成如下图所示:
发挥我们的想象力,我们可以根据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> 2、ItemsPanelTemplate 我们先讲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. file:///C:\Users\kt123\AppData\Local\Temp\ksohtml\wps46.tmp.jpg 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; } } 我们如果想让图片以横向显示。一开始我以为用StackPanel的Orientation=”Horiziontal”,发现犯了个错误。这样设置是现在把Items中某一个item中的内容水平显示啊。这时就要用到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 /> </ItemsPanelTemplate> </Setter.Value> </Setter> </Style>
这里我在DataTemplate中加了StackPanel 为了说明加在这里的效果只是区分:Items整体横向和Item中内容的区别。
现在我们要想让布局可以随着窗体宽度变化,我们只要把ItemsPanelTemplate中的StackPanel 改成WrapPanel,并且设置ListBox的ScrollViewer.HorizontalScrollBarVisibility="Disabled",这样才可以看到效果。 ControlTemplate之 ItemsPresenter和ContentPresenter 我们先构造一个TreeView xaml中: 在开始加个Loaded="UserControl_Loaded" <Grid> <TreeView x:Name="treeview" /> </Grid> 后台代码中: file:///C:\Users\kt123\AppData\Local\Temp\ksohtml\wps5A.tmp.jpg 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 下面我们如何运用ItemsPresenter和ContentPresenter来添加样式。来实现下面这幅图的效果 file:///C:\Users\kt123\AppData\Local\Temp\ksohtml\wps6D.tmp.jpg <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。最后我们在资源里引用这个样式。 file:///C:\Users\kt123\AppData\Local\Temp\ksohtml\wps6E.tmp.jpg <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,它专门对TreeViewItem 或 MenuItem的一些数据对象的绑定。
|