VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > 批处理教程 >
  • 深入浅出 WPF教程之WPF中的控件模板(ControlTemplat

WPF包含数据模板和控件模板,其中控件模板又包括ControlTemplate和ItemsPanelTemplate,这里讨论一下ControlTemplate。

   其实WPF的每一个控件都有一个默认的模板,该模板描述了控件的外观以及外观对外界刺激所做出的反应。我们可以自定义一个模板来替换掉控件的默认模板以便打造个性化的控件。

    与Style不同,Style只能改变控件的已有属性值(比如颜色字体)来定制控件,但控件模板可以改变控件的内部结构(VisualTree,视觉树) 来完成更为复杂的定制,比如我们可以定制这样的按钮:在它的左办部分显示一个小图标而它的右半部分显示文本。

    要替换控件的模板,我们只需要声明一个ControlTemplate对象,并对该ControlTemplate对象做相应的配置,然后将该ControlTemplate对象赋值给控件的Template属性就可以了。

ControlTemplate包含两个重要的属性:
1,VisualTree,该模板的视觉树,其实我们就是使用这个属性来描述控件的外观的
2,Triggers,触发器列表,里面包含一些触发器Trigger,我们可以定制这个触发器列表来使控件对外界的刺激发生反应,比如鼠标经过时文本变成粗体等。

参考以下代码
<Button>
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
</ControlTemplate>
</Button.Template>
 
</Button>在上面的代码中,我们修改了Button的Template属性,我们定义了一个ControlTemplate,在 <ControlTemplate> ... </ControlTemplate>之间包含的是模板的视觉树,也就是如何显示控件的外观,我们这里使用了一个Ellipse(椭圆)和一 个TextBlock(文本块)来定义控件的外观。

    很容易联想到一个问题:控件(Button)的一些属性,比如高度、宽度、文本等如何在新定义的外观中表现出来呢?
我 们使用TemplateBinding 将控件的属性与新外观中的元素的属性关联起来Width="{TemplateBinding Button.Width}" ,这样我们就使得椭圆的宽度与按钮的宽度绑定在一起而保持一致,同理我们使用Text="{TemplateBinding Button.Content}"将TextBlock的文本与按钮的Content属性绑定在一起。
除了定义控件的默认外观外,也许我们想还定义当外界刺激我们的控件时,控件外观做出相应的变化,这是我们需要触发器。参考以下代码:
<Button Content="test btn" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" >
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Button.Template>
 
</Button>在上面的代码中注意到<ControlTemplate.Triggers>... </ControlTemplate.Triggers>之间的部分,我们定义了触发器 <Trigger Property="Button.IsMouseOver" Value="True">,其表示当我们Button的IsMouseIOver属性变成True时,将使用设置器<Setter Property="Button.Foreground" Value="Red" /> 来将Button的Foreground属性设置为Red。这里有一个隐含的意思是:当Button的IsMouseIOver属性变成False时,设 置器中设置的属性将回复原值。

你可以粘贴以下代码到XamlPad查看效果:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControlTemplateTest" Height="300" Width="300"

<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="0.6*"/>
<ColumnDefinition Width="0.2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.4*"/>
</Grid.RowDefinitions>
<Button Content="test btn" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" >
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Window>
 
接下来的一个问题是:如果我要重用我的模板,应该怎么办呢?
你需要将模板定义为资源,其实大多数情况下,我们也是这样做的
参考以下代码:
<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources>
 
上面的代码将我们原来的模板定义为窗体范围内的资源,其中TargetType="Button"指示我们的模板作用对象为Button,这样在整个窗体范围内的按钮都可以使用这个模板了,模板的使用方法也很简单:
<Button Content="test btn" Template="{StaticResource ButtonTemplate}" />其中的ButtonTemplate是我们定义的模板的x:Key

你可以粘贴以下代码到XamlPad查看效果:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControlTemplateTest" Height="300" Width="300"
>

<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources>

<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="0.6*"/>
<ColumnDefinition Width="0.2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.4*"/>
</Grid.RowDefinitions>
<Button Content="test btn1" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" />
<Button Content="test btn2" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />
<Button Content="test btn2" Grid.Column="2" Grid.ColumnSpan="1" Grid.Row="2" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />
</Grid>
</Window>

 

额外提一下的是,我们也可以在触发器中,调用一个故事板来达到对事件响应时的动画效果
参考以下代码 <!--定义动画资源-->
<ControlTemplate.Resources>
<Storyboard x:Key="MouseClickButtonStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="faceEllipse" Storyboard.TargetProperty="Width" BeginTime="00:00:00">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="50"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.3" Value="100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
 
</ControlTemplate.Resources>我们为模板定义了一个动画资源,此后在模板的触发器中我们就可以调用该资源来实现一个动画效果了:
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="faceEllipse">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>你可以粘贴以下代码到XamlPad查看效果:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControlTemplateTest" Height="300" Width="300"

<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义动画资源-->
<ControlTemplate.Resources>
<Storyboard x:Key="MouseClickButtonStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="faceEllipse" Storyboard.TargetProperty="Width" BeginTime="00:00:00">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="50"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.3" Value="100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<!--定义动画资源_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="faceEllipse">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="txtBlock">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="0.6*"/>
<ColumnDefinition Width="0.2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.4*"/>
</Grid.RowDefinitions>
<Button Content="test btn1" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" />
<Button Content="test btn2" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />
<Button Content="test btn2" Grid.Column="2" Grid.ColumnSpan="1" Grid.Row="2" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />
</Grid>
</Window>
 
1
最好的模板示例:我们知道每个控件都有自己默认的模板,这是MS编写的,如果我们能够得到这些模板的XAML代码,那么它将是学习模板的最好的示例,
要想获得某个控件ctrl的默认模板,请调用以下方法:
string GetTemplateXamlCode(Control ctrl)
{
FrameworkTemplate template = ctrl.Template;
string xaml = "";
if (template != null)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = new string(' ', 4);
settings.NewLineOnAttributes = true;
StringBuilder strbuild = new StringBuilder();
XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
try
{
XamlWriter.Save(template, xmlwrite);
xaml = strbuild.ToString();
}
catch (Exception exc)
{
xaml = exc.Message;
}
}
else
{
xaml = "no template";
}
return xaml;
}
 

揭露WPF SDK“不能说的秘密”

IT168 专稿】 如果经历过.NET的1.0,1.1以及2.0版本,你就很可能发现.NET 3.0中的WPF区域中的一些文档有点不同。具体来说,WPF负责介绍几个CLR和托管代码封装方面的新概念。WPF SDK团队为在参考资料中展示这些新概念而做的努力是很大的进步,主要致力于改变,因为其它的技术也在它们的API中采用了相同或者类似的范例。

System.Reflection.NET 3.0 SDK 

    微软用于创建托管SDK框架的“魔法”其实就是映射进程(典型的就是采用托管映射,但有时也有非托管的映射,这取决于API)。编译工具映射所有.NET 3.0的API,SDK作者就在纲要中填充映射告诉我们的关于API的消息(至少这是一个目标)。但是对于比System.Reflection本身更新 的编程概念的映射,System.Reflection做得不是特别好。你可以通过查看定制特性来得到一些更具体的信息。但对于那些像XAML一样的语 言,或者独立属性,由于System.Reflectio从1.1开始就没有本质的改变,所以不能为3.0提供好的API。如果你要编写自己的映射,你不 得不做些额外的创造工作(我不会在这里描述这个问题,因为太复杂了,而且超出了本主题的范围)。对我们的SDK开发团队来说,他们也不得不非常熟悉映射代 码。然后,其他的各种各样的人,包括一些像我们这样的程序员不得不跟上表示策略——怎么在更好,更标准的托管代码段文档中表示额外的信息,比如异常和返回 值等。
                    
    正在讨论的隐藏的秘密是参考主题部分,这部分是大量的设计会议和开发工作的结果。每一个隐藏秘密都代表着编程的一个具体方面,和WPF非常相关并且对 WPF来说是新内容。所有的信息都在SDK页面,等待你的发掘。但是,我们还没搞清楚这些突然在WPF参考文档中出现的额外段落是什么,以及他们意味着什 么。
  
   抛开这些吧。让我们来照亮这些WPF文档中隐藏的秘密。一切都会得到解释!

独立属性

    很多时候会被问道一个问题“独立属性到底是什么?”或者一个相关的更尖锐的问题“我为什么要去关心一个东西是不是独立属性呢?”我们的答案是:独立属性总览。

    跨过这个概念上的障碍之后,下一个逻辑上的问题可能是“好吧,独立属性就是一种支持CLR属性的方式,那我该怎么分别那些属性是通过这种方式被支持的呢?”我们遇到的第一个隐藏秘密是:独立属性,和独立属性信息段。
   
    在文档中提到两种方法来判断某个给定的属性是否是独立属性:
1)当你在浏览任何类型的成员表,并且看到公共属性,读描述的时候。如果这个属性是独立属性,那么描述的最后一句就是“这是一个独立特性。”
2)假设你在说明CLR属性的SDK的主题页面。同样,在成员表的相同的描述中,你可以找到这句话“这是一个独立属性”。除了描述外,每一个独立属性的属性页面内还有一段可以被合适地成为独立属性信息段。这一段在表格中包含两点:

    一个到含有独立属性标志符的域的链接。很多属性系统API函数都需要这个标志符以执行一个属性或者获得关于它的信息。有人说,对那些只是获取或设置某个已 经存在的具体属性的应用程序编程来说,使用属性系统API并不总是必须的,因为你可以调用更简单的独立属性CLR“封装”。这样,标志符主要是和包含独立 属性的高级编程任务相关,比如处理属性元数据,或者追踪属性值。

   如果一个属性是框架层的属性,那么独立属性信息段会把元数据中“标记”为true的列出来。当独立属性被WPF框架层解释时,标记报告独立属性的某些共同 特征,尤其是子特征,比如,整体系统,数据绑定,或属性值继承。SDK报告这些标记,因为这有助于实例了解改变属性是否会强迫修改整体设计,或是否你可以 省略指定绑定的两个状态,因为这是独立属性的默认设置。
默认值

    横跨独立属性和“常规”属性的一个概念是默认值。一般来说,.NET文档会在属性值段告诉你属性的默认值,并且为保持一致性,这也是默认属性被报告给独立 属性的地方。但是,独立属性默认值实际上来自属性元数据,尽管在一个平面上CLR属性可能来自一个类或者是拓展执行。这样,通过重写属性元数据,独立属性 就可以被子类或新的属性拥有者轻易改变。在已有的WPF独立属性中偶尔会发生这种情况。当发生时,每一个类的重写都会被记录在Remark段。
Routed事件

    在你不可避免地问“什么是routed事件?”之前,可以去这里看看Routed Events Overview。现在我们解决这个问题了。
不 像独立属性,routed事件没有像“这是一个routed事件”之类的习惯性描述。基本元素(UIElement, FrameworkElement, ContentElement, FrameworkContentElement)有很多的routed事件:可能它们的事件有75%都是routed的。其它的类,像控件也会有少数的 routed事件,也许和着一些不route的标准CLR事件。为了区分一个事件是否route,你需要在事件主题看看是否有Routed事件信息段。
    Routed事件信息段是一个有如下三项的表: 

   *一个指向包含有routed 事件标志符的域的链接。和属性系统API以及独立属性标志符一样,事件系统API也需要这个标志符以接入事件。像增加或移除句柄一类的简单操作并不是必须 标志符,这也是因为routed事件会有一个“包装”,这样就可以支持CLR语言中增加/移除事件句柄的CLR语法。但,如果直接使用 AddHandler或调用RaiseEvent,就需要知道routed事件的标志符。

    *Routing策略。有三种可能:Bubbling,Tunneling,和Direct。这儿有个小秘密:如果之前查看过事件名字,那么 routing策略就总是Tunneling,这是一个惯例。但区分Bubbling和Direct就有点困难了,因为没有不同的命名习惯。所以,我们提 供了参照页的信息。Direct事件实际上没有在它的元素之外route,但他们仍然服务于WPF-specific目的,在这里有描述Routed Events Overview。
  
* Delegate类型。你可以在声明事件的语法中找到列出的delegate。我们在这里重复是为了方便,所以你可以直接跳到“写恰当的句柄”的地方。

附带属性

    严格地说,附带属性是XAML的概念,而不是WPF的。但WPF 是第一个发行的采用了XAML语言的工具。所以WPF是用文档来描述XAML语法和XAML语言的先锋。如果你是代码编写员,并看了某个附带属性的叫做 DockPanel.Dock的文档,就会注意到一些特别的东西:没有XAML语法,没有代码语法。如果浏览DockPanel类,去看映射或对象就可以 证实:对CLR没有类似于“Dock”属性的东西。甚至为了在其它的产生映射的参照中使这个页面存在,SDK开发团队不得不注入这个页面。然而,对我们这 些在标记和代码间可以随意转换的人来说,附带属性的语法段确实提供了一个到附带属性的“真实”代码API的链接,这可以成为一系列获取和设置的接入方法。 对DockPanel 类来说有 GetDock和SetDock。(不幸的是,在MSDN版本的这些页面中似乎没有这些链接;在Visual Studio和offline Windows SDKs中有…相信我...)

附属事件

   类似地,这是一个XAML的概念,但在WPF得到具体的应用。XAML语法,但对同等代码接入来说是一个“秘密”接入方法。在附属事件情况下,这些方法的 类型是Add*Handler和Remove*Handler。例如,Mouse.MouseDown调用AddMouseDownHandler就可以 有附带到给定的UIElement实例的句柄,并可以通过RemoveMouseDownHandler移除。对实际应用来说,附属事件不一定那么重要, 因为大多数都是被UIElement以更方便的方式重新使用而已。但如果你在写一套相关的控件,或者在实现一个服务,那么附属事件就很可能进入你的视野。

相关教程