您的位置:首页 > 其它

Silverlight 用户控件与自定义控件详解

2012-06-13 16:11 85 查看
在Silverlight中你如果想把UI封装成单独的一部分或者创建一个新的页面,你可能会在Visual Studio中通过右击 “项目-> 添加-> 添加新项->Silverlight用户控件” 这样来创建控件。如果你是这么做的,那么这篇文章非常适合你。它将适用于任何基于XAML技术:WPF、silverlight、Windows Phone 和Windows 8 Runtime。

尽管用户控件很棒,它们能快速的拼在一起,或一次又一次的重复使用,这是它们的很大一个价值所在。但是如果我告诉你还有另一种控件类型,具有干净的代码、更强大性能更好,而且比用户控件的方式更加灵活、重复的使用,那它将会是大量开发人员的最爱吗?

其实这个你早就知道,因为你已经一直在使用他们:Button、ListBox、ItemsControls、Grid、StackPanel等。你可以查看Xaml Style彻底改变控件的外观和体验,而不触及任何代码。这是多么强大的想法,看看下面一个Silverlight ListBox 行星DEMO 。在左边,你会看到一个绑定了行星名单的ListBox。在右边,你能看到一个太阳系,但事实上,这也是一个ListBox。这里没有涉及到额外的代码,完全是由修改Template达到效果。你可以按上下键,它有正常ListBox的功能。

让我重复一遍:做到这一点我没有添加任何后台代码到ListBox。事实上,该页面后台代码完全是空的。如果你不相信,这里有源码下载



解剖用户控件

首先,让我们解剖一个典型的用户控件看看,充分了解下它是怎么工作的这是关键。在下面我们控件中一部分XAML确定了布局,为了保持它是一个简单的例子,里有只一个Grid和一个Button。



1 <UserControl x:Class="MyApp.SilverlightControl1"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
4
5 <Grid x:Name="LayoutRoot" Background="White">
6 <Button Content="Click Me" Click="Button_Click" Opacity=".5" />
7 </Grid>
8 </UserControl>



我们控件的后台代码:



1 using System.Windows;
2 using System.Windows.Controls;
3 using System.Windows.Media;
4
5 namespace SolarSystemRetemplate
6 {
7 public partial class SilverlightControl1 : UserControl
8 {
9 public SilverlightControl1()
10 {
11 InitializeComponent();
12 }
13
14 private void Button_Click(object sender, RoutedEventArgs e)
15 {
16 LayoutRoot.Background = new SolidColorBrush(Colors.Red);
17 }
18 }
19 }



这里有两个地方值得注意:”LayoutRoot”是在XAML中使用X:Name定义的,我们在后台代码中通过这个名字自动获取了这个变量。 而且Button的Click事件与后台代码中的事件处理程序奇迹般的挂接了。实际上这是编译程序和调用方法InitializeComponent处理了这一切--但是有趣的是这个方法在这里不存在。实际上为了表示这是一个局部类,Visual Studio为你私底下创建了一个小(秘密)文件。你可以右击方法选择“转到定义“。下面是该文件的内容:



1 namespace MyApp {
2
3 public partial class SilverlightControl1 : System.Windows.Controls.UserControl {
4
5 internal System.Windows.Controls.Grid LayoutRoot;
6
7 private bool _contentLoaded;
8
9 /// <summary>
10 /// InitializeComponent
11 /// </summary>
12 [System.Diagnostics.DebuggerNonUserCodeAttribute()]
13 public void InitializeComponent() {
14 if (_contentLoaded)
15 return;
16 _contentLoaded = true;
17 System.Windows.Application.LoadComponent(this,
18 new System.Uri("/MyApp;component/SilverlightControl1.xaml",
19 System.UriKind.Relative));
20 this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
21 }
22 }
23 }




你会注意到LayoutRoot在这里被定义成internal,并且它的赋值使用了“FindName”方法。

这就是使用用户控件的好处之一:它会自动为你做很多工作,但自定义控件则需要你自己来完成这些工作(但是如果考虑到你的效率的话,这并不是那么糟糕)。这里说明下:用户控件只是另一种自定义控件。

解剖自定义控件

自定义控件不像用户控件会有一个xaml和一个后台代码组成,换成除了一个默认的XAML Template以外其余的全部是代码。你可以认为XAML Template和用户控件的XAML文件作用一样,但是这里要注意,XAML Template可以实现任何改变。这里要注意另外一件事件,因为Template不具有Visual Studio为您生成的隐藏代码局部类,所以任何事件处理程序不能在Template中定义。那么我们怎样重新创建上述用户控件为一个自定义控件呢?

对于Silverlight这是很容易的,右键单击您的项目,选择 “添加 -> 新建项 –> Silverlight模板化控件”。WPF 和Windows Phone不伴随此模板,所以你必须手工通过创建一个类和一个通用模板文件。你做到了这一点后你会发现两个新文件:首先一个简单的C#类,第二个是在\Themes\Generic.xaml下创建了一个新文件。第二个文件汇集了你所有控件的Template样式。它的名字必须是Generic.xaml而且必须在该目录下,这样自定义控件才能使用所有的Template。

下面让我们一起来看看Template是怎么写的,和上面用户控件一样也是添加了一个Button和一个Grid。

按 Ctrl+C 复制代码<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp">

<Style TargetType="local:TemplatedControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TemplatedControl1">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="LayoutRoot">
<Button x:Name="ClickButton" Content="Click me!" Opacity=".5" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
按 Ctrl+C 复制代码

首先第一,注意Border上TemplateBinding语句,它是控件中一个重要的功能。您可以直接在你的控件代码中定义一个依赖项属性绑定。由于自定义控件继承Control,你将自动继承Background、 BorderBrush、BorderThickness 和其他属性。请注意 我这里我没有给按钮添加click事件。如果这里添加了,模板将会加载失败。我们将在后台加上click处理程序,接下来,让我们一起看代码吧:


1 using System.Windows;
2 using System.Windows.Controls;
3 using System.Windows.Controls.Primitives;
4 using System.Windows.Media;
5
6 namespace MyApp
7 {
8 [TemplatePart(Name="LayoutRoot", Type=typeof(Control))]
9 [TemplatePart(Name = "ClickButton", Type = typeof(ButtonBase))]
10 public class TemplatedControl1 : Control
11 {
12 Control layoutRoot;
13 ButtonBase button;
14 public TemplatedControl1()
15 {
16 this.DefaultStyleKey = typeof(TemplatedControl1);
17 }
18 public override void OnApplyTemplate()
19 {
20 if (button != null) //unhook from previous template part
21 {
22 button.Click -= new RoutedEventHandler(button_Click);
23 }
24 button = GetTemplateChild("ClickButton") as ButtonBase;
25 if (button != null)
26 {
27 button.Click += new RoutedEventHandler(button_Click);
28 }
29 layoutRoot = GetTemplateChild("LayoutRoot") as Panel;
30 base.OnApplyTemplate();
31 }
32
33 private void button_Click(object sender, RoutedEventArgs e)
34 {
35 layoutRoot.Background = new SolidColorBrush(Colors.Red);
36 }
37 }38 }



首先在控件中声明”TemplatePart”,它指定预期元素的名称和和类型。在demo中 LayoutRoot的类型是Panel(Grid的类型是Control)、ClickButton的类型是ButtonBase。这些不是严格要求,但是当你调用写好的自定义控件时,它们能帮助Expression Blend了解模板的要求。我总是控件层次结构申明需要的最小类型,使Template更加灵活。比如我用ButtonBase而不是Button,因为我只要用到定义ButtonBase基类的Click事件。同样LayoutRoot也一样,我只需要它的BackGround 属性。

在构造函数中,我定义了”DefaultStyleKey”,它告诉Framework我在Themes\Generic.xaml中定义了默认Template。

最后,最重要的部分是”OnApplyTemplate”,此方法当Template加载完后被调用。这是我们早期的机会,抢先对Template中controls的引用,即控件中申明的TemplatePart。在这种情况下,我抢先引用在Template中定义ButtonBase,如果找到它,我将给它添加一个click事件处理程序。此外,如果一个新的Template被应用,一定要记住去除以前实例中的事情处理程序。同样重要要注意的是Template部件总是可选的!所以你要检查所有引用template的部件是否为null。

添加Visual States到控件

现在添加一些鼠标状态到我们的控件,并控制动画何时触发。在后台代码中我们定义的添加两个TemplateVisualState属性:

1 [TemplateVisualState(GroupName = "HoverStates", Name = "MouseOver")]
2 [TemplateVisualState(GroupName = "HoverStates", Name = "Normal")]

接下来给控件添加visual state的触发:


1 bool isMouseOver;
2 protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
3 {
4 isMouseOver = true;
5 ChangeVisualState(true);
6 base.OnMouseEnter(e);
7 }
8 protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
9 {
10 isMouseOver = false;
11 ChangeVisualState(true);
12 base.OnMouseLeave(e);
13 }
14
15 private void ChangeVisualState(bool useTransitions)
16 {
17 if (isMouseOver)
18 {
19 GoToState(useTransitions, "MouseOver");
20 }
21 else
22 {
23 GoToState(useTransitions, "Normal");
24 }
25 }
26
27 private bool GoToState(bool useTransitions, string stateName)
28 {
29 return VisualStateManager.GoToState(this, stateName, useTransitions); 30 }



这正是我们需要的所有代码。它非常简单。如果鼠标停留,则触发MouseOver状态,否则则触发正常状态。请注意,实际上我们没有真正定义什么是”MouseOver”,这是Template的工作。好接下来让我们来定义:


1 <ControlTemplate TargetType="local:TemplatedControl1">
2 <Border Background="{TemplateBinding Background}"
3 BorderBrush="{TemplateBinding BorderBrush}"
4 BorderThickness="{TemplateBinding BorderThickness}">
5 <VisualStateManager.VisualStateGroups>
6 <VisualStateGroup x:Name="HoverStates">
7 <VisualState x:Name="MouseOver">
8 <Storyboard>
9 <ColorAnimation
10 Storyboard.TargetName="BackgroundElement"
11 Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
12 To="Yellow" Duration="0:0:.5" />
13 </Storyboard>
14 </VisualState>
15 <VisualState x:Name="Normal">
16 <Storyboard>
17 <ColorAnimation
18 Storyboard.TargetName="BackgroundElement"
19 Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
20 To="Transparent" Duration="0:0:.5" />
21 </Storyboard>
22 </VisualState>
23 </VisualStateGroup>
24 </VisualStateManager.VisualStateGroups>
25 <Grid x:Name="LayoutRoot">
26 <Rectangle x:Name="BackgroundElement" Fill="Transparent" />
27 <Button x:Name="ClickButton"
28 Content="Click me!" Opacity=".5" />
29 </Grid>
30 </Border> 31</ControlTemplate>


好了,你现在有一个控件,当ButtonBase被点击以及鼠标悬停或离开时,Panel的背景色会改变,这样可以解决于很多控件,不用重写代码。

出处:http://lmyhao.cnblogs.com/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: