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

 

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

 

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

 

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

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

Get Microsoft Silverlight

解剖用户控件

首先,让我们解剖一个典型的用户控件看看,充分了解下它是怎么工作的这是关键。在下面我们控件中一部分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定义的,我们在后台代码中通过这个名字自动获取了这个变量。 而且ButtonClick事件与后台代码中的事件处理程序奇迹般的挂接了。实际上这是编译程序和调用方法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。

 1 <ResourceDictionary
 2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4     xmlns:local="clr-namespace:MyApp">
 5 
 6     <Style TargetType="local:TemplatedControl1">
 7         <Setter Property="Template">
 8             <Setter.Value>
 9                 <ControlTemplate TargetType="local:TemplatedControl1">
10                     <Border Background="{TemplateBinding Background}"
11                             BorderBrush="{TemplateBinding BorderBrush}"
12                             BorderThickness="{TemplateBinding BorderThickness}">
13                         <Grid x:Name="LayoutRoot">
14                             <Button x:Name="ClickButton" Content="Click me!" Opacity=".5" />
15                         </Grid>
16                     </Border>
17                 </ControlTemplate>
18             </Setter.Value>
19         </Setter>
20     </Style>
21 </ResourceDictionary>

首先第一,注意BorderTemplateBinding语句,它是控件中一个重要的功能。您可以直接在你的控件代码中定义一个依赖项属性绑定。由于自定义控件继承Control,你将自动继承Background BorderBrushBorderThickness 和其他属性。请注意 我这里我没有给按钮添加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的类型是PanelGrid的类型是Control)、ClickButton的类型是ButtonBase。这些不是严格要求,但是当你调用写好的自定义控件时,它们能帮助Expression Blend了解模板的要求。我总是控件层次结构申明需要的最小类型,使Template更加灵活。比如我用ButtonBase而不是Button,因为我只要用到定义ButtonBase基类的Click事件。同样LayoutRoot也一样,我只需要它的BackGround 属性。 

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

最后,最重要的部分是”OnApplyTemplate”,此方法当Template加载完后被调用。这是我们早期的机会,抢先对Templatecontrols的引用,即控件中申明的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的背景色会改变,这样可以解决于很多控件,不用重写代码。

原文地址:https://www.cnblogs.com/lmyhao/p/2375243.html