Silverlight 教程:如何创建可皮肤化的自定义控件






3.59/5 (18投票s)
一步一步指南,教您如何创建一个可皮肤化、可动画的导航栏自定义控件。
概述
下面是您在本教程结束时能够创建的可皮肤化自定义控件的截图。顶部的图片是通用控件。底部的图片是应用了皮肤的相同控件。该控件是一个动画导航栏。您可以通过此链接查看动画导航栏的工作示例。
引言
本文是一篇关于如何使用Silverlight 2.0创建可皮肤化自定义控件的分步教程——具体来说,是一个类似于silverlight.net网站顶部动画导航栏的控件。在本教程结束时,您将获得以下所有方面的实践经验:
- Visual state manager (视觉状态管理器)
- 自定义控件
- Skinning (一种无需更改底层代码即可改变控件外观的方法)
- Dependency properties (一种为自定义控件添加自定义属性的方法)
本文分为三个部分。在第一部分中,我们创建了一个功能齐全的动画导航栏,从用户角度来看,它与我们最终要创建的控件完全相同。如果您只制作一个控件,您可能会在第一部分结束后就停止。然而,如果您想将该控件打包成易于重用的形式,那么您需要继续学习本教程的第二部分,我将在其中向您展示如何将导航栏变成一个“可皮肤化”的自定义控件。“可皮肤化”意味着您可以更改控件的外观而不更改底层代码,这就引出了本教程的第三部分。在第三部分中,我将演示新开发的自定义控件的皮肤化。
注意:本教程中的项目使用了Silverlight 2 beta 2、Visual Studio 2008和Expression Blend 2.5的2008年6月预览版。
更新:我已经使用Silverlight 2的完整发布候选版重新编译了这个项目,我唯一需要做的更改是创建一个名为“themes”的自定义文件夹,并将generic.xaml移动到该文件夹中。完成这些操作后,一切正常。
教程第一部分:使用视觉状态管理器创建导航栏
- 在VS2008中,选择“新建项目”,然后选择“空白网站”。将网站命名为SkinnableNavbar。注意:尽管我们将此项目命名为“skinnable”(可皮肤化),但我们在教程第一部分中创建的只是一个常规的用户控件。我们这样做是为了能够使用Expression Blend在将设计变成通用控件之前,使其所有视觉和行为方面都正确。这还将使我们有机会介绍VisualStateManager。
- 右键单击解决方案,单击“添加项目”,然后添加一个名为SkinnableNavbarXap的Silverlight应用程序,并勾选“添加测试页”。
- 在SkinnableNavbarXap中单击page.xaml,然后在Expression Blend 2.5中打开它。
- 第一步是创建该控件的所有视觉元素。创建一个看起来与您在下面看到的相似的东西。它由三条线、三个带有放射状渐变的矩形组成,渐变的左侧为亮绿色,右侧为透明。使用路径来创建三角形。我们将要处理的活动部分是绿色渐变矩形,我们将它们命名为
Nav1Highlight
到Nav3Highlight
,三角形将命名为NavIndicator
,标签将命名为Nav1Label
到Nav3Label
。线条是为了艺术效果,不是导航栏的活动部分。 - 接下来,我们需要定义用户在与Nav1、Nav2或Nav3导航项交互时将点击或鼠标悬停的区域。要做到这一点,在整个Nav1区域上方放置一个大矩形,将其不透明度设置为0,并命名为
Nav1ClickTarget
。我们希望点击目标是可见的,以便客户可以看到他们想要点击的区域,但重要的是通过将不透明度设置为0来实现这一点,而不是将其设置为无填充和无描边,因为如果您将其设置为无填充和无描边,它将不会生成鼠标事件,因此显然不能用作点击目标。对Nav2和Nav3也执行相同的操作。 - 默认情况下,我们将选择Nav1,所以让我们在视觉上进行设置。选择
Nav2Highlight
并将其Visibility
设置为Collapsed
,对Nav3Highlight
也执行相同的操作。 - 现在是添加每个状态的时候了。单击状态框中的+符号,然后添加状态组
MouseOverStates
。单击MouseOverStates
组中的+符号,然后添加状态Nav1Highlighted
、Nav2Highlighted
和Nav3Highlighted
。 - 现在我们将定义每个状态的外观。单击状态
Nav2Highlighted
。请注意工作区周围的红色边框和右上角的文字“State recording is on”(状态记录已开启)。这正在记录从基线开始的、需要进入Nav2Highlighted
状态的更改。将Nav1Highlight
的可见性设置为折叠,将Nav2Highlight
的可见性设置为可见,并将NavIndicator
移动到Nav2下方。结果应该与下方类似。单击Nav3Highlight
并以类似方式处理。 - 现在我们将开始将用户操作链接到各种状态。选择
Nav1ClickTarget
,然后在属性面板(事件是小闪电图标)中转到“事件”。在“MouseEnter”部分,键入Nav1MouseEnter并按Enter键。这将唤醒VS2008并在您的Page.xaml.cs代码隐藏文件中添加一个Nav1MouseEnter
方法。对Nav2ClickTarget
和Nav3ClickTarget
执行相同的操作。 - 对于每个方法,添加下面的代码,以便
VisualStateManager
根据鼠标进入ClickTarget区域而转换到每个已定义的状态。 - 生成并查看测试页。当您将鼠标悬停在不同区域上时,您应该看到导航栏会跳转到将
NavIndicator
指向您悬停的项,并且高亮显示会弹出。这很酷,并且是演示状态的一种非常好的方式,但作为动画导航栏来说并不令人满意。让我们尝试一下,看看是否能通过添加状态之间的过渡来改进它。 - 让我们在
Nav1Highlighted
和Nav2Highlighted
之间添加一个过渡。单击Nav1Highlighted
旁边的+号并添加过渡。 - 将过渡时间设置为.3秒。保存更改。
- 执行清理项目,然后重新生成,然后查看测试页。当您将鼠标悬停在Nav2上时,您应该看到
NavIndicator
从Nav1平滑过渡到Nav2(而所有其他过渡都像之前一样跳转到状态)。问题在于平滑过渡在视觉上并不令人满意。我希望有加速效果。所以,让我们在Expression Blend中删除我们刚刚创建的过渡。为此,请单击Nav2Highlighted
过渡旁边的-号。 - 现在,让我们单击
Nav2Highlighted
状态,并将关联的故事板调整为我们喜欢的样子。如果时间轴不可见,请通过单击“对象和时间线”面板中状态框旁边的箭头来使其可见。将NavIndicator
关键帧移动到.3秒。单击关键帧,并在KeySpline图上将Easing设置为x1 =0,x2=1,y1=1,y2=1,如下图所示。这将使水平(即x方向)运动一开始缓慢,然后达到全速。生成项目并在测试页中查看它。您应该看到,NavIndicator
过渡到Nav2应该具有上述类型的运动。请注意,无论您是从Nav1还是Nav3开始,您都能获得所需的动画效果。动画故事板用于此效果,并且它位于状态内部,而不是像我们明确定义状态之间的过渡那样是一个单独的过渡。 - 对
Nav3Highlighted
状态重复上述过程,以便它也能在.3秒内以加速运动使NavIndicator
动画到位。有趣的是,似乎无法为Nav1Highlighted
状态复制相同的方法,因为出于某种原因,我无法为NavIndicator
关键帧调出easing KeySpline图。我找不到任何合法的理由来解释这种情况,所以这很可能是Expression Blend 2.5的2008年6月预览版中的一个bug,该bug由NavIndicator
的位置在Base
状态和Nav1Highlighted
状态中相同这一事实触发。为了解决这个问题,我只是复制了另一个状态中动画NavIndicator
的DoubleAnimationUsingKeyFrames
XAML,并将其作为故事板粘贴到Nav1Highlighted
VisualState下。然后,我将.3秒时的关键帧值更改为0,表示在.3秒时,我希望NavIndicator
位于X位置0。请注意,在Silverlight动画中,对象相对于其在基线状态下的原始位置的坐标系进行动画。从下面的XAML中可以看出,状态就是故事板,即使有时它们是单帧故事板。 - 现在我们已经让鼠标悬停状态按我们想要的方式进行了动画处理,最后我们需要做的是添加单击时导航到HTML页面的功能。高亮显示
Nav1ClickTarget
,然后转到“属性”面板并选择“事件”图标(带有一个小闪电图标的那个)。在MouseLeftButtonDown
框中,键入Nav1Clicked
。这将激活Visual Studio并在您的代码隐藏文件中添加一个Nav1Clicked
方法。对Nav2ClickTarget
和Nav3ClickTarget
执行相同的操作。 - 在未来的教程中,我将向您展示如何创建一个自定义控件并轻松自定义导航目标,但由于本教程专注于Visual State Manager,我将硬编码点击Nav1将带您到我的网站SilverlightWebApps.com的其他教程,Nav2将带您到yahoo.com,Nav3将带您到google.com。为此,将
System.Windows.Browser.HtmlPage.Window.Navigate
调用添加到NavClicked
方法中,如下所示: - 生成并用浏览器打开测试页面。当您将鼠标悬停在Nav区域上时,您应该看到一个
NavIndicator
加速到位,一个NavHighlight
在您将鼠标悬停在Nav区域时闪烁,并且当您单击Nav区域时,您应该看到自己导航到不同的URL。就是这样,您完成了!
private void Nav1MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "Nav1Highlighted", true);
}
private void Nav2MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "Nav2Highlighted", true);
}
private void Nav3MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "Nav3Highlighted", true);
}
<vsm:VisualState x:Name="Nav1Highlighted">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="NavIndicator"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0">
<SplineDoubleKeyFrame.KeySpline>
<KeySpline ControlPoint1="0,1" ControlPoint2="1,1"/>
</SplineDoubleKeyFrame.KeySpline>
</SplineDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
private void Nav1Clicked(object sender, MouseButtonEventArgs e)
{
System.Windows.Browser.HtmlPage.Window.Navigate(
new Uri("http://www.silverlightwebapps.com/Tutorials.aspx"));
}
private void Nav2Clicked(object sender, MouseButtonEventArgs e)
{
System.Windows.Browser.HtmlPage.Window.Navigate(new Uri("http://www.yahoo.com"));
}
private void Nav3Clicked(object sender, MouseButtonEventArgs e)
{
System.Windows.Browser.HtmlPage.Window.Navigate(new Uri("http://www.google.com"));
}
教程第二部分:将导航栏转换为自定义控件
- 单击项目的解决方案,然后选择“添加”、“新建项目”、“Silverlight类库”。将项目命名为NavbarCustomControl。
- 将Class1.cs重命名为Navbar.cs,并允许VS2008重构该类。让该类派生自
Control
。将DefaultStyleKey
分配为新的Navb
ar类型。 - 向项目中添加一个名为generic.xaml的文本文件(显然,它确实必须这样命名)。
- 将“生成操作”更改为“资源”,并删除“自定义工具”框中的文本。
- 用此控件的默认内容填充generic.xaml。注意
xmlns:vsm
行。因为我们在项目后面使用了VisualStateManager
,所以我们也需要这一行。例如,为了快速启动并运行,下面的XAML只是为我们的自定义控件显示一个矩形。 - 生成解决方案。就是这样。您刚刚构建了一个自定义控件,它除了显示一个200x100的矩形之外,什么也不做。要查看此控件的结果,让我们构建一个可以使用新控件的自定义项目。转到解决方案,右键单击并选择“添加新项目”,然后添加一个名为ControlDemo的新Silverlight应用程序。允许VS2008添加一个启动页,不要将其设为默认页面,并允许Silverlight调试。
- 删除自动生成的ControlDemo.aspx。保留ControlDemo.html。
- 转到Control demo中的Page.xaml,并在
UserControl
标签中添加<custom:Navbar>
标签。要能够将此添加到页面并让系统理解它,您需要在UserControl
标签中添加下面显示的xmlns:custom
属性。此外,您还需要右键单击ControlDemo项目并添加对NavbareCustomControl项目的引用。 - 生成解决方案。在浏览器中查看ControlDemoTestPage.html,您应该看到这个。 (查看自定义控件结果的另一个好方法是,在ExpressionBlend中打开ControlDemo项目的Page.xaml。它通常会提供更好的错误消息。)
- 这还不算太难,对吧?我们刚刚创建了一个几乎不做任何事情的自定义控件。现在我们把它变成一个导航栏。转到SkinnableNavbar.xap,从Page.xaml中复制整个
Grid
元素(包括所有子元素),并替换generic.xaml中当前的Grid
。换句话说,只获取Page.xaml中除UserControl
标签之外的所有内容。如果现在构建应用程序,然后在Expression Blend中查看它,您将收到错误AG_E_PARSER_BAD_PROPERTY_VALUE
。这是因为,正如您在下面的摘录中所见,我们在XAML中定义了事件处理程序。这在用户控件中是完全可以的,但在generic.xaml中不允许。 - 删除XAML中的
MouseEnter
和MouseLeftButtonDown
属性并重新生成。现在,当您查看测试页时,如下所示。 - 当然,当您将鼠标悬停在元素上或单击它们时,什么也不会发生,但我们现在将对此进行更改。首先,将所有
NavMouseEnter
和NavMouseClicked
事件处理程序从SkinnableNavbarXap
的page.xaml.cs代码隐藏中剪切并粘贴。然后,我们手动连接每个点击目标的MouseEnter
和MouseLeftButtonDown
事件到我们的事件处理程序。您还需要知道的另一个技巧是,您不能在构造函数中使用GetTemplateChild
方法来查找元素。相反,您必须在之后的OnApplyTemplate
方法中执行此操作。最终结果如下。 - 清理并重新生成所有内容。打开演示页。您应该看到,现在导航栏就像早期教程中发布的那个一样完成了动画。恭喜!您现在拥有了一个功能齐全的自定义控件!唯一的问题是它不太可皮肤化,并且有许多硬编码的元素。现在让我们删除硬编码的URL和导航文本。
- 为了删除硬编码的URL,我们将添加属性
Nav1Url
、Nav2Url
和Nav3Url
。我们将允许控件的用户通过将它们设置为控件XAML标签的属性来指定这些URL。如果用户没有指定标签,我们还将设置一个默认URL,即www.silverlightwebapps.com(我的网站!)。 - 现在,让我们展示一下用户如何使用这些新属性。转到ControlDemo项目中的page.xaml。找到
Navbar
标签并按如下方式修改它。 - 重新生成所有内容,并在浏览器中打开演示页。您应该看到,如果您单击Nav1,您将转到MSDN,如您的
Navbar
XAML属性中所指定的那样;如果您单击任何其他项,您将转到默认网站。 - 好了,让我们处理另一件事。“Skinnable”(可皮肤化)意味着控件的视觉方面可以改变,但底层代码,在本例中是Navbar.cs中的代码,不会改变。为了实现这一点,Navbar.cs保持不变;我们需要更清晰地连接事件,或者更确切地说,在连接到新框架元素之前,我们需要取消连接旧框架元素的事件。看看下面的属性。默认情况下,我们的点击目标是矩形,但也许当应用新皮肤时,它将被连接到椭圆。底层代码没有理由关心。唯一需要发生的是,当我们从默认矩形切换到新元素时,事件将从旧元素取消连接,然后连接到新元素。下面的代码完成了这一点。
- 现在我们需要使用这些新属性。转到
OnApplyTemplate
并将其更改为您在下面看到的。通过以下更改,当模板更改时(就像应用新皮肤时一样),属性现在会自动处理旧事件的取消连接和新事件的连接。 - 为了完成我们的可皮肤化控件,我们需要做的最后一件事是添加一些Expression Blend可以读取的属性,以便它能够帮助我们(理论上——稍后会详细介绍)皮肤化我们的控件。这些属性对控件本身没有影响。它仅仅是开发工具通过反射能够发现的东西,以便了解您作为开发人员认为控件的关键部分。对于这个控件,关键部分是存在三个点击目标,并且存在用于鼠标悬停在这些目标上的状态。这些状态的确切内容以及点击目标的确切内容可以通过皮肤化过程由用户自定义。
- 好了,就是这样。您现在拥有了一个可皮肤化的自定义控件。继续重新生成所有内容,并打开ControlDemoTestPage.html。
namespace NavbarCustomControl
{
public class Navbar : Control
{
public Navbar()
{
this.DefaultStyleKey = typeof(Navbar);
}
}
}
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:NavbarCustomControl;assembly=NavbarCustomControl"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
<Style TargetType="custom:Navbar">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="custom:Navbar">
<Grid x:Name="LayoutRoot">
<Rectangle x:Name="BodyElement" Width="200" Height="100" Stroke="Black"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<UserControl x:Class="ControlDemo.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:NavbarCustomControl;assembly=NavbarCustomControl"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<custom:Navbar/>
</Grid>
</UserControl>
<Rectangle x:Name="Nav1ClickTarget" ...
MouseEnter="Nav1MouseEnter" MouseLeftButtonDown="Nav1Clicked"/>
<Rectangle x:Name="Nav2ClickTarget" ...
MouseEnter="Nav2MouseEnter" MouseLeftButtonDown="Nav2Clicked"/>
<Rectangle x:Name="Nav3ClickTarget" ...
MouseEnter="Nav3MouseEnter" MouseLeftButtonDown="Nav3Clicked"/>
namespace NavbarCustomControl
{
public class Navbar : Control
{
public Navbar()
{
this.DefaultStyleKey = typeof(Navbar);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Get pointers to each of our click target elements
FrameworkElement Nav1ClickTarget =
(FrameworkElement)GetTemplateChild("Nav1ClickTarget");
FrameworkElement Nav2ClickTarget =
(FrameworkElement)GetTemplateChild("Nav2ClickTarget");
FrameworkElement Nav3ClickTarget =
(FrameworkElement)GetTemplateChild("Nav3ClickTarget");
// Manually add the MouseEnter events
Nav1ClickTarget.MouseEnter += new MouseEventHandler(Nav1MouseEnter);
Nav2ClickTarget.MouseEnter += new MouseEventHandler(Nav2MouseEnter);
Nav3ClickTarget.MouseEnter += new MouseEventHandler(Nav3MouseEnter);
// Manually add the MouseEnter events
Nav1ClickTarget.MouseLeftButtonDown +=
new MouseButtonEventHandler(Nav1Clicked);
Nav2ClickTarget.MouseLeftButtonDown +=
new MouseButtonEventHandler(Nav2Clicked);
Nav3ClickTarget.MouseLeftButtonDown +=
new MouseButtonEventHandler(Nav3Clicked);
}
private void Nav1MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "Nav1Highlighted", true);
}
private void Nav2MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "Nav2Highlighted", true);
}
private void Nav3MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "Nav3Highlighted", true);
}
private void Nav1Clicked(object sender, MouseButtonEventArgs e)
{
System.Windows.Browser.HtmlPage.Window.Navigate(
new Uri("http://www.silverlightwebapps.com/Tutorials.aspx"));
}
private void Nav2Clicked(object sender, MouseButtonEventArgs e)
{
System.Windows.Browser.HtmlPage.Window.Navigate(
new Uri("http://www.yahoo.com"));
}
private void Nav3Clicked(object sender, MouseButtonEventArgs e)
{
System.Windows.Browser.HtmlPage.Window.Navigate(
new Uri("http://www.google.com"));
}
}
}
为此,添加一个Nav1Url
属性,如下所示,并对Nav2Url
和Nav3Url
重复此操作。
public static readonly DependencyProperty Nav1UrlProperty =
DependencyProperty.Register(
"Nav1Url", typeof(string),
typeof(Navbar), null
);
public string Nav1Url
{
get { return (string)GetValue(Nav1UrlProperty); }
set { SetValue(Nav1UrlProperty, value);}
}
更改点击事件以使用这个新创建的属性。对Nav2Clicked
和Nav3Clicked
重复此操作。
private void Nav1Clicked(object sender, MouseButtonEventArgs e)
{
System.Windows.Browser.HtmlPage.Window.Navigate(new Uri(Nav1Url));
}
属性的默认值已添加到generic.xaml中,如下所示。
<Style TargetType="custom:Navbar">
<Setter Property="Nav1Url" Value="http://www.silverlightwebapps.com"/>
<Setter Property="Nav2Url" Value="http://www.silverlightwebapps.com"/>
<Setter Property="Nav3Url" Value="http://www.silverlightwebapps.com"/>
<custom:Navbar Nav1Url="http://www.msdn.com"/>
// Nav1ClickTargetProperty
private FrameworkElement m_nav1ClickTarget = null;
private FrameworkElement Nav1ClickTarget
{
get { return m_nav1ClickTarget;}
set
{
FrameworkElement old_click_target = m_nav1ClickTarget;
if (old_click_target != null)
{
old_click_target.MouseEnter -= new MouseEventHandler(Nav1MouseEnter);
old_click_target.MouseLeftButtonDown -=
new MouseButtonEventHandler(Nav1Clicked);
}
m_nav1ClickTarget = value;
if (m_nav1ClickTarget != null)
{
m_nav1ClickTarget.MouseEnter += new MouseEventHandler(Nav1MouseEnter);
m_nav1ClickTarget.MouseLeftButtonDown +=
new MouseButtonEventHandler(Nav1Clicked);
}
}
}
遵循上述模型,为Nav2ClickTarget
和Nav3ClickTarget
创建类似的属性。
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Get pointers to each of our click target elements
Nav1ClickTarget = (FrameworkElement)GetTemplateChild("Nav1ClickTarget");
Nav2ClickTarget = (FrameworkElement)GetTemplateChild("Nav2ClickTarget");
Nav3ClickTarget = (FrameworkElement)GetTemplateChild("Nav3ClickTarget");
}
[TemplateVisualState(Name = "Nav1Highlighted", GroupName = "MouseOverStates"),
TemplateVisualState(Name = "Nav2Highlighted", GroupName = "MouseOverStates"),
TemplateVisualState(Name = "Nav3Highlighted", GroupName = "MouseOverStates"),
TemplatePart(Name = "Nav1ClickTarget", Type = typeof(FrameworkElement)),
TemplatePart(Name = "Nav2ClickTarget", Type = typeof(FrameworkElement)),
TemplatePart(Name = "Nav3ClickTarget", Type = typeof(FrameworkElement))]
public class Navbar : Control
{
教程第三部分:演示如何皮肤化新自定义控件
- 既然我们已经创建了一个可皮肤化的自定义控件,让我们对其进行皮肤化,并通过示例展示我们可以更改可皮肤化控件的外观多少,而无需重新编译底层控件。
- 在ExpressionBlend中打开ControlDemo项目的page.xaml。在页面上粘贴第二个导航栏控件。(要同时看到它们,您可能需要调整边距,或者它们可能会粘贴在一起。)
- 右键单击第二个导航栏控件,选择“编辑控件部件(模板)”。选择“编辑副本”。将新模板命名为“SkinnedNavbar”。
- 要手动复制代码,您需要执行两项操作。一,您需要在
UserControl
标签中添加以下属性,并提供对视觉状态管理器的引用。 - 现在,回到Expression Blend,右键单击下面的Navbar,您应该看到我下面展示的内容——一个可编辑的、因此可皮肤化的控件。呼!制作可皮肤化控件的一切看起来都很干净,除了手动复制粘贴。
- 既然您已经走了这么远,您可以随意编辑。作为一个简单的演示,尝试将Nav1这个词替换成别的东西。就我个人而言,我想将其自定义,使其包含我网站的徽标。(这不仅是一个教程,也是我升级我网站的过程。)我想用我制作的一些艺术品替换背景。所以,如果您想完全复制我所做的皮肤化,首先将此图像添加到控件演示项目中。(您可以从本文附带的源代码中获得图像的更好版本。)
- 接下来,删除除
NavHighlight
项、NavIndicator
和NavClickTarget
项之外的所有内容。一种简单的方法是单击每个项目的眼睛图标,使它们消失,只留下要删除的项目可见。继续删除这些项目。 - 现在添加背景图像。
- 接下来,我们需要定位一些关键元素,使它们与新图像对齐。定位
NavHighlight
和ClickTarget
。将NavIndi
cator更改为白色,为每个导航目标添加新文本,并在整个下方绘制一条新线。当我这样做时,我偶尔不得不使用眼睛图标来移开挡路的东西。我还不得不暂时使一些通常不可见的NavHighlight
可见。另外两个关键是,我需要将我的图像发送到后面,并将点击目标带到前面。 - 接下来,我们需要确保所有状态都已正确定义。对于
Base
状态,折叠Nav2Highlight
和Nav3Highlight
。接下来选择Nav2Highlighted
状态。查看时间线。回想本教程的第一部分,我们在时间0定义navIndicator
的位置,所以将时间线滑块拖到300ms。确保NavIndicator
与第二个位置的中间对齐。对Nav3Highlighted
也执行相同的操作。 - 将所需的Web地址添加到
Navbar
标签的Nav2Url
和Nav3Url
属性中。生成,您就完成了。您已经皮肤化了您的控件。
如果您使用的是Expression Blend 2.5 6月预览版(在我撰写本教程时可用的最新版本),您现在应该看到一个空框,左上角有一个小黄色的正方形,当然这看起来不对。然而,如果您查看XAML,您会明白为什么。复制过来的Navbar模板是完全空的。与皮肤化内置控件(如滚动条)时的行为进行比较,当您编辑副本时,您会发现模板完全填充了默认控件模板。当我第一次看到这个时,这让我感到困惑,但根据我从微软员工Li-Lun Lou在http://silverlight.net/forums/t/22965.aspx得到的答案,这是Expression Blend 2.5 6月预览版中的一个已知bug,并且有望在正式发布版本中得到修复。由于这个bug,Expression Blend只复制内置控件的XAML。这意味着我们将不得不手动复制我们generic.xaml中的默认XAML。(是的,这很痛苦。)
<UserControl.Resources>
<ControlTemplate x:Key="SkinnedNavbar" TargetType="custom:Navbar"/>
</UserControl.Resources>
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
其次,您需要将generic.xaml中的控件模板粘贴到SkinnedNavbar ControlTemplate
标签的子级中。