65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.59/5 (18投票s)

2008年8月20日

CPOL

19分钟阅读

viewsIcon

63892

downloadIcon

776

一步一步指南,教您如何创建一个可皮肤化、可动画的导航栏自定义控件。

概述

下面是您在本教程结束时能够创建的可皮肤化自定义控件的截图。顶部的图片是通用控件。底部的图片是应用了皮肤的相同控件。该控件是一个动画导航栏。您可以通过此链接查看动画导航栏的工作示例。

引言

本文是一篇关于如何使用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移动到该文件夹中。完成这些操作后,一切正常。

教程第一部分:使用视觉状态管理器创建导航栏

  1. 在VS2008中,选择“新建项目”,然后选择“空白网站”。将网站命名为SkinnableNavbar。注意:尽管我们将此项目命名为“skinnable”(可皮肤化),但我们在教程第一部分中创建的只是一个常规的用户控件。我们这样做是为了能够使用Expression Blend在将设计变成通用控件之前,使其所有视觉和行为方面都正确。这还将使我们有机会介绍VisualStateManager。
  2. 右键单击解决方案,单击“添加项目”,然后添加一个名为SkinnableNavbarXap的Silverlight应用程序,并勾选“添加测试页”。
  3. 在SkinnableNavbarXap中单击page.xaml,然后在Expression Blend 2.5中打开它。
  4. 第一步是创建该控件的所有视觉元素。创建一个看起来与您在下面看到的相似的东西。它由三条线、三个带有放射状渐变的矩形组成,渐变的左侧为亮绿色,右侧为透明。使用路径来创建三角形。我们将要处理的活动部分是绿色渐变矩形,我们将它们命名为Nav1HighlightNav3Highlight,三角形将命名为NavIndicator,标签将命名为Nav1LabelNav3Label。线条是为了艺术效果,不是导航栏的活动部分。
  5. image002.jpg

  6. 接下来,我们需要定义用户在与Nav1、Nav2或Nav3导航项交互时将点击或鼠标悬停的区域。要做到这一点,在整个Nav1区域上方放置一个大矩形,将其不透明度设置为0,并命名为Nav1ClickTarget。我们希望点击目标是可见的,以便客户可以看到他们想要点击的区域,但重要的是通过将不透明度设置为0来实现这一点,而不是将其设置为无填充和无描边,因为如果您将其设置为无填充和无描边,它将不会生成鼠标事件,因此显然不能用作点击目标。对Nav2和Nav3也执行相同的操作。
  7. image003.jpg

  8. 默认情况下,我们将选择Nav1,所以让我们在视觉上进行设置。选择Nav2Highlight并将其Visibility设置为Collapsed,对Nav3Highlight也执行相同的操作。
  9. image004.jpg

  10. 现在是添加每个状态的时候了。单击状态框中的+符号,然后添加状态组MouseOverStates。单击MouseOverStates组中的+符号,然后添加状态Nav1HighlightedNav2HighlightedNav3Highlighted
  11. image005.jpg

  12. 现在我们将定义每个状态的外观。单击状态Nav2Highlighted。请注意工作区周围的红色边框和右上角的文字“State recording is on”(状态记录已开启)。这正在记录从基线开始的、需要进入Nav2Highlighted状态的更改。将Nav1Highlight的可见性设置为折叠,将Nav2Highlight的可见性设置为可见,并将NavIndicator移动到Nav2下方。结果应该与下方类似。单击Nav3Highlight并以类似方式处理。
  13. image006.jpg

  14. 现在我们将开始将用户操作链接到各种状态。选择Nav1ClickTarget,然后在属性面板(事件是小闪电图标)中转到“事件”。在“MouseEnter”部分,键入Nav1MouseEnter并按Enter键。这将唤醒VS2008并在您的Page.xaml.cs代码隐藏文件中添加一个Nav1MouseEnter方法。对Nav2ClickTargetNav3ClickTarget执行相同的操作。
  15. image007.jpg

  16. 对于每个方法,添加下面的代码,以便VisualStateManager根据鼠标进入ClickTarget区域而转换到每个已定义的状态。
  17. 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);
    }
  18. 生成并查看测试页。当您将鼠标悬停在不同区域上时,您应该看到导航栏会跳转到将NavIndicator指向您悬停的项,并且高亮显示会弹出。这很酷,并且是演示状态的一种非常好的方式,但作为动画导航栏来说并不令人满意。让我们尝试一下,看看是否能通过添加状态之间的过渡来改进它。
  19. 让我们在Nav1HighlightedNav2Highlighted之间添加一个过渡。单击Nav1Highlighted旁边的+号并添加过渡。
  20. image008.jpg

  21. 将过渡时间设置为.3秒。保存更改。
  22. image009.jpg

  23. 执行清理项目,然后重新生成,然后查看测试页。当您将鼠标悬停在Nav2上时,您应该看到NavIndicator从Nav1平滑过渡到Nav2(而所有其他过渡都像之前一样跳转到状态)。问题在于平滑过渡在视觉上并不令人满意。我希望有加速效果。所以,让我们在Expression Blend中删除我们刚刚创建的过渡。为此,请单击Nav2Highlighted过渡旁边的-号。
  24. 现在,让我们单击Nav2Highlighted状态,并将关联的故事板调整为我们喜欢的样子。如果时间轴不可见,请通过单击“对象和时间线”面板中状态框旁边的箭头来使其可见。将NavIndicator关键帧移动到.3秒。单击关键帧,并在KeySpline图上将Easing设置为x1 =0,x2=1,y1=1,y2=1,如下图所示。这将使水平(即x方向)运动一开始缓慢,然后达到全速。生成项目并在测试页中查看它。您应该看到,NavIndicator过渡到Nav2应该具有上述类型的运动。请注意,无论您是从Nav1还是Nav3开始,您都能获得所需的动画效果。动画故事板用于此效果,并且它位于状态内部,而不是像我们明确定义状态之间的过渡那样是一个单独的过渡。
  25. image010.jpg

    image011.jpg

  26. Nav3Highlighted状态重复上述过程,以便它也能在.3秒内以加速运动使NavIndicator动画到位。有趣的是,似乎无法为Nav1Highlighted状态复制相同的方法,因为出于某种原因,我无法为NavIndicator关键帧调出easing KeySpline图。我找不到任何合法的理由来解释这种情况,所以这很可能是Expression Blend 2.5的2008年6月预览版中的一个bug,该bug由NavIndicator的位置在Base状态和Nav1Highlighted状态中相同这一事实触发。为了解决这个问题,我只是复制了另一个状态中动画NavIndicatorDoubleAnimationUsingKeyFrames XAML,并将其作为故事板粘贴到Nav1Highlighted VisualState下。然后,我将.3秒时的关键帧值更改为0,表示在.3秒时,我希望NavIndicator位于X位置0。请注意,在Silverlight动画中,对象相对于其在基线状态下的原始位置的坐标系进行动画。从下面的XAML中可以看出,状态就是故事板,即使有时它们是单帧故事板。
  27. <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>
  28. 现在我们已经让鼠标悬停状态按我们想要的方式进行了动画处理,最后我们需要做的是添加单击时导航到HTML页面的功能。高亮显示Nav1ClickTarget,然后转到“属性”面板并选择“事件”图标(带有一个小闪电图标的那个)。在MouseLeftButtonDown框中,键入Nav1Clicked。这将激活Visual Studio并在您的代码隐藏文件中添加一个Nav1Clicked方法。对Nav2ClickTargetNav3ClickTarget执行相同的操作。
  29. image012.jpg

  30. 在未来的教程中,我将向您展示如何创建一个自定义控件并轻松自定义导航目标,但由于本教程专注于Visual State Manager,我将硬编码点击Nav1将带您到我的网站SilverlightWebApps.com的其他教程,Nav2将带您到yahoo.com,Nav3将带您到google.com。为此,将System.Windows.Browser.HtmlPage.Window.Navigate调用添加到NavClicked方法中,如下所示:
  31. 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"));
    }
  32. 生成并用浏览器打开测试页面。当您将鼠标悬停在Nav区域上时,您应该看到一个NavIndicator加速到位,一个NavHighlight在您将鼠标悬停在Nav区域时闪烁,并且当您单击Nav区域时,您应该看到自己导航到不同的URL。就是这样,您完成了!

教程第二部分:将导航栏转换为自定义控件

  1. 单击项目的解决方案,然后选择“添加”、“新建项目”、“Silverlight类库”。将项目命名为NavbarCustomControl。
  2. image013.jpg

  3. Class1.cs重命名为Navbar.cs,并允许VS2008重构该类。让该类派生自Control。将DefaultStyleKey分配为新的Navbar类型。
  4. namespace NavbarCustomControl
    {
        public class Navbar : Control
        {
            public Navbar()
            {
                this.DefaultStyleKey = typeof(Navbar);
            }
     
        }
    }
  5. 向项目中添加一个名为generic.xaml的文本文件(显然,它确实必须这样命名)。
  6. 将“生成操作”更改为“资源”,并删除“自定义工具”框中的文本。
  7. image014.jpg

  8. 用此控件的默认内容填充generic.xaml。注意xmlns:vsm行。因为我们在项目后面使用了VisualStateManager,所以我们也需要这一行。例如,为了快速启动并运行,下面的XAML只是为我们的自定义控件显示一个矩形。
  9. <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>
  10. 生成解决方案。就是这样。您刚刚构建了一个自定义控件,它除了显示一个200x100的矩形之外,什么也不做。要查看此控件的结果,让我们构建一个可以使用新控件的自定义项目。转到解决方案,右键单击并选择“添加新项目”,然后添加一个名为ControlDemo的新Silverlight应用程序。允许VS2008添加一个启动页,不要将其设为默认页面,并允许Silverlight调试。
  11. image015.jpg

    image016.jpg

  12. 删除自动生成的ControlDemo.aspx。保留ControlDemo.html
  13. 转到Control demo中的Page.xaml,并在UserControl标签中添加<custom:Navbar>标签。要能够将此添加到页面并让系统理解它,您需要在UserControl标签中添加下面显示的xmlns:custom属性。此外,您还需要右键单击ControlDemo项目并添加对NavbareCustomControl项目的引用。
  14. <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>
  15. 生成解决方案。在浏览器中查看ControlDemoTestPage.html,您应该看到这个。 (查看自定义控件结果的另一个好方法是,在ExpressionBlend中打开ControlDemo项目的Page.xaml。它通常会提供更好的错误消息。)
  16. image017.jpg

    image018.jpg

  17. 这还不算太难,对吧?我们刚刚创建了一个几乎不做任何事情的自定义控件。现在我们把它变成一个导航栏。转到SkinnableNavbar.xap,从Page.xaml中复制整个Grid元素(包括所有子元素),并替换generic.xaml中当前的Grid。换句话说,只获取Page.xaml中除UserControl标签之外的所有内容。如果现在构建应用程序,然后在Expression Blend中查看它,您将收到错误AG_E_PARSER_BAD_PROPERTY_VALUE。这是因为,正如您在下面的摘录中所见,我们在XAML中定义了事件处理程序。这在用户控件中是完全可以的,但在generic.xaml中不允许。
  18. <Rectangle x:Name="Nav1ClickTarget" ... 
      MouseEnter="Nav1MouseEnter" MouseLeftButtonDown="Nav1Clicked"/>
    <Rectangle x:Name="Nav2ClickTarget" ... 
      MouseEnter="Nav2MouseEnter" MouseLeftButtonDown="Nav2Clicked"/>
    <Rectangle x:Name="Nav3ClickTarget" ... 
      MouseEnter="Nav3MouseEnter" MouseLeftButtonDown="Nav3Clicked"/>
  19. 删除XAML中的MouseEnterMouseLeftButtonDown属性并重新生成。现在,当您查看测试页时,如下所示。
  20. image019.jpg

  21. 当然,当您将鼠标悬停在元素上或单击它们时,什么也不会发生,但我们现在将对此进行更改。首先,将所有NavMouseEnterNavMouseClicked事件处理程序从SkinnableNavbarXappage.xaml.cs代码隐藏中剪切并粘贴。然后,我们手动连接每个点击目标的MouseEnterMouseLeftButtonDown事件到我们的事件处理程序。您还需要知道的另一个技巧是,您不能在构造函数中使用GetTemplateChild方法来查找元素。相反,您必须在之后的OnApplyTemplate方法中执行此操作。最终结果如下。
  22. 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"));
            }
        }
    }
  23. 清理并重新生成所有内容。打开演示页。您应该看到,现在导航栏就像早期教程中发布的那个一样完成了动画。恭喜!您现在拥有了一个功能齐全的自定义控件!唯一的问题是它不太可皮肤化,并且有许多硬编码的元素。现在让我们删除硬编码的URL和导航文本。
  24. 为了删除硬编码的URL,我们将添加属性Nav1UrlNav2UrlNav3Url。我们将允许控件的用户通过将它们设置为控件XAML标签的属性来指定这些URL。如果用户没有指定标签,我们还将设置一个默认URL,即www.silverlightwebapps.com(我的网站!)。
  25. 为此,添加一个Nav1Url属性,如下所示,并对Nav2UrlNav3Url重复此操作。

    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);}
    }

    更改点击事件以使用这个新创建的属性。对Nav2ClickedNav3Clicked重复此操作。

    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"/>
  26. 现在,让我们展示一下用户如何使用这些新属性。转到ControlDemo项目中的page.xaml。找到Navbar标签并按如下方式修改它。
  27. <custom:Navbar Nav1Url="http://www.msdn.com"/>
  28. 重新生成所有内容,并在浏览器中打开演示页。您应该看到,如果您单击Nav1,您将转到MSDN,如您的Navbar XAML属性中所指定的那样;如果您单击任何其他项,您将转到默认网站。
  29. 好了,让我们处理另一件事。“Skinnable”(可皮肤化)意味着控件的视觉方面可以改变,但底层代码,在本例中是Navbar.cs中的代码,不会改变。为了实现这一点,Navbar.cs保持不变;我们需要更清晰地连接事件,或者更确切地说,在连接到新框架元素之前,我们需要取消连接旧框架元素的事件。看看下面的属性。默认情况下,我们的点击目标是矩形,但也许当应用新皮肤时,它将被连接到椭圆。底层代码没有理由关心。唯一需要发生的是,当我们从默认矩形切换到新元素时,事件将从旧元素取消连接,然后连接到新元素。下面的代码完成了这一点。
  30. // 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);
            }
        }
    }

    遵循上述模型,为Nav2ClickTargetNav3ClickTarget创建类似的属性。

  31. 现在我们需要使用这些新属性。转到OnApplyTemplate并将其更改为您在下面看到的。通过以下更改,当模板更改时(就像应用新皮肤时一样),属性现在会自动处理旧事件的取消连接和新事件的连接。
  32. 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");
    }
  33. 为了完成我们的可皮肤化控件,我们需要做的最后一件事是添加一些Expression Blend可以读取的属性,以便它能够帮助我们(理论上——稍后会详细介绍)皮肤化我们的控件。这些属性对控件本身没有影响。它仅仅是开发工具通过反射能够发现的东西,以便了解您作为开发人员认为控件的关键部分。对于这个控件,关键部分是存在三个点击目标,并且存在用于鼠标悬停在这些目标上的状态。这些状态的确切内容以及点击目标的确切内容可以通过皮肤化过程由用户自定义。
  34. [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
    {
  35. 好了,就是这样。您现在拥有了一个可皮肤化的自定义控件。继续重新生成所有内容,并打开ControlDemoTestPage.html

教程第三部分:演示如何皮肤化新自定义控件

  1. 既然我们已经创建了一个可皮肤化的自定义控件,让我们对其进行皮肤化,并通过示例展示我们可以更改可皮肤化控件的外观多少,而无需重新编译底层控件。
  2. 在ExpressionBlend中打开ControlDemo项目的page.xaml。在页面上粘贴第二个导航栏控件。(要同时看到它们,您可能需要调整边距,或者它们可能会粘贴在一起。)
  3. image020.jpg

  4. 右键单击第二个导航栏控件,选择“编辑控件部件(模板)”。选择“编辑副本”。将新模板命名为“SkinnedNavbar”。
  5. image021.jpg

    如果您使用的是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。(是的,这很痛苦。)

    image022.jpg

    <UserControl.Resources>
      <ControlTemplate x:Key="SkinnedNavbar" TargetType="custom:Navbar"/>
    </UserControl.Resources>
  6. 要手动复制代码,您需要执行两项操作。一,您需要在UserControl标签中添加以下属性,并提供对视觉状态管理器的引用。
  7. xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"

    其次,您需要将generic.xaml中的控件模板粘贴到SkinnedNavbar ControlTemplate标签的子级中。

  8. 现在,回到Expression Blend,右键单击下面的Navbar,您应该看到我下面展示的内容——一个可编辑的、因此可皮肤化的控件。呼!制作可皮肤化控件的一切看起来都很干净,除了手动复制粘贴。
  9. image023.jpg

  10. 既然您已经走了这么远,您可以随意编辑。作为一个简单的演示,尝试将Nav1这个词替换成别的东西。就我个人而言,我想将其自定义,使其包含我网站的徽标。(这不仅是一个教程,也是我升级我网站的过程。)我想用我制作的一些艺术品替换背景。所以,如果您想完全复制我所做的皮肤化,首先将此图像添加到控件演示项目中。(您可以从本文附带的源代码中获得图像的更好版本。)
  11. image024.gif

  12. 接下来,删除除NavHighlight项、NavIndicatorNavClickTarget项之外的所有内容。一种简单的方法是单击每个项目的眼睛图标,使它们消失,只留下要删除的项目可见。继续删除这些项目。
  13. 现在添加背景图像。
  14. image025.jpg

  15. 接下来,我们需要定位一些关键元素,使它们与新图像对齐。定位NavHighlightClickTarget。将NavIndicator更改为白色,为每个导航目标添加新文本,并在整个下方绘制一条新线。当我这样做时,我偶尔不得不使用眼睛图标来移开挡路的东西。我还不得不暂时使一些通常不可见的NavHighlight可见。另外两个关键是,我需要将我的图像发送到后面,并将点击目标带到前面。
  16. image026.jpg

  17. 接下来,我们需要确保所有状态都已正确定义。对于Base状态,折叠Nav2HighlightNav3Highlight。接下来选择Nav2Highlighted状态。查看时间线。回想本教程的第一部分,我们在时间0定义navIndicator的位置,所以将时间线滑块拖到300ms。确保NavIndicator与第二个位置的中间对齐。对Nav3Highlighted也执行相同的操作。
  18. image027.jpg

  19. 将所需的Web地址添加到Navbar标签的Nav2UrlNav3Url属性中。生成,您就完成了。您已经皮肤化了您的控件。
© . All rights reserved.