WPF——样式和行为

  如果局限于简单的、灰色外观的普通按钮以及其他常用控件,WPF将是没有新意的捆绑。WPF提供了几个特性,允许为基本元素插入一些自己的爱好,并标准化应用程序的可视化外观。本次主要学习样式和行为。样式是组织和重用格式化选项的重要工具。不是使用重复的标记填充XAML,以设置注入外边距、内边距、颜色以及字体等细节,而可以创建一系列封装所有这些细节的;样式。然后可以在需要之处通过一个属性应用样式。行为是一个重用用户界面代码的更有挑战性的工具。其基本思想是行为封装了一些通用的UI功能。如果具有适当的行为,可以使用一两行XAML标记将其附加到一个元素,从而可以为您节省便携盒调试代码的工作。

11.1 样式基础

  样式是可以应用于元素的属性值的集合。WPF样式系统和HTML标记中的层叠样式表标准扮演类似的角色。与CSS类似,通过WPF样式可以定义通用的格式化特性集合,并且为了保证一致性,在整个应用程序中应用它们。与CSS一样,WPF样式也能够自动工作,指定具体的元素类型的目标,并且通过元素树层叠起来。然而,WPF样式的功能更加强大,因为它们能够设置任何依赖项属性。这意味着可以使用它们标准化未格式化的特性,如控件的行为。WPF样式还支持触发器,当一个属性发生变化时可以通过触发器改变控件的样式,并且可以使用模板重新定义控件的内置外观。为了理解适合使用样式的场合,分析一个简单的示例。设想需要标准化在窗口中使用的字体。最简单的方法是设置包含窗口的字体属性。这些属性是在Control类中定义的,得益于这些属性值的继承特性,当在窗口级别上设置这些属性时,在窗口中的所有元素都会使用相同的属性值,除非明确的覆盖它们。现在考虑一种情况,希望只为用户界面中的一部分锁定字体。如果能在一个特定容器中隔离这些元素,可以使用本质上相同的方法,并设置容器的字体属性。如果,我们希望使所有的按钮具有抑制的字体和文本尺寸,并且使用和其他元素不同的字体设置。就需要一种方法在某个地方定义这些细节,并在所有应用它们的地方重用这些细节。如下所示:

  <Window.Resources>    
    <FontFamily x:Key="ButtonFontFamily">Times New Roman</FontFamily>
    <s:Double x:Key="ButtonFontSize">18</s:Double>
    <FontWeight x:Key="ButtonFontWeight">Bold</FontWeight>    
  </Window.Resources>

这是在窗口中定义的资源,一旦定义了资源,下一步是在元素中实际使用这些资源。

    <Button Padding="5" Margin="5"
            FontFamily="{StaticResource ButtonFontFamily}"
            FontWeight="{StaticResource ButtonFontWeight}"
            FontSize="{StaticResource ButtonFontSize}" 
              >A Customized Button</Button>

这种方式虽然将字体细节移除了标记,但还存有问题,1、除恶资源名称相似之外,没有明确指明三个资源是相关的。如果需要设置更多的字体属性,或者决定为不同类型的元素维护不同字体设置,这将变的复杂。2、需要使用资源的标记非常繁琐。我们可以通过定义一个将所有字体捆绑在一起的自定义类。样式对这一问题提供了非常好的解决方案。可以定义一个独立的包装所有希望设置的属性的样式。如下:

  <Window.Resources>
    <Style x:Key="BigFontButtonStyle">
      <Setter Property="Control.FontFamily" Value="Times New Roman" />
      <Setter Property="Control.FontSize" Value="18" />
      <Setter Property="Control.FontWeight" Value="Bold" />
    </Style>    
  </Window.Resources>

上面的标记创建了一个独立的资源:一个System.Windows.Style对象。这个样式对象包含了一个Setter集合,该集合具有三个Setter对象,每个Setter对象用于一个希望设置的属性。每个Setter对象由两部分信息组成:希望进行设置的属性和希望为该属性应用的值。与所有资源一样,样式对象有一个键名,从而当需要时可以从集合中提取它。每个WPF元素都可以使用一个样式,样式通过元素的Style属性插入到元素中。如下,将上面创建的样式配置到一个按钮:

    <Button Padding="5" Margin="5"
            Style="{StaticResource BigFontButtonStyle}" 
              >A Customized Button</Button>

也可以通过代码设置样式。需要做的全部工作就是使用熟悉的FindResource()方法,从最近的资源集合中提取样式。如下:

cmdButton.Style=(Style)cmd.FindResource("样式键名");

Setter集合是Style类中最重要的属性,但并不是唯一的属性,在Style类*有5个重要属性,如下表中介绍了这些属性

Setters 设置属性值以及自动关联事件处理程序的Setter对象或EventSetter对象的集合
Triggers 继承自TriggerBase类并且能够自动改变样式设置的对象的集合。例如,当另一个属性改变时,或者当发生某个事件时,可以修改样式
Resources 希望用于样式的资源集合。例如,可能需要使用一个对象设置多个属性。这时,作为资源创建对象,然后再在Setter对象中使用该资源,这样会更高效
BasedOn 通过该属性可以创建继承自其他样式设置的更复杂样式
TargetType 该属性表示应用样式的元素的类型。通过该属性可以创建只影响特定类型元素的设置器,并且还可以创建能够为恰当的元素类型自动起作用的设置器

11.1.1 创建样式对象

  如果希望创建具有更精细目标的样式,可以使用容器的Resoures集合定义样式,如StakPanel面板或Grid面板。如果希望在应用程序中重用样式,可以使用应用程序的Resources集合定义样式。严格来讲,不需要同时使用样式和资源。因为无法与其他元素共享该样式,如果只是使用样式设置一些属性,直接设置熟悉更加容易。但是,可以使用该方法为一个元素关联触发器。通过该方法还可以修改元素控件模板的一部分。

11.1.2 设置属性

  每个Style对象包装了一个Setter对象的集合。每个Setter对象设置元素的单个属性。唯一的限制是设置器只能改变依赖项属性——不能修改其他属性。在某些情况下,不能使用简单的特性字符创设置属性值。例如,不能使用简单的字符串创建ImageBrush对象。对于这种情况,可以使用属性的XAML技巧,使用嵌套的元素代替特性。下面是一个示例:

<Style x:key="HappyTiledElementStyle">
    <Setter Property="Control.Background">
        <Setter.Value>
            <ImageBrush TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSoure="happyface.jpg" Opacity="0.3">
            </ImageBrush>
        </Setter.Value>
    </Setter>
</Style>

为了标识希望设置的属性,需要提供类和属性的名称。然而,使用的类名不必是定义属性的类名。也可以是继承了属性的派生类。例如如下样式,该样式使用Button类的引用代替了Control类的引用

    <Style x:Key="BigFontButtonStyle">
      <Setter Property="Control.FontFamily" Value="Times New Roman" />
      <Setter Property="Control.FontSize" Value="18" />
      <Setter Property="Control.FontWeight" Value="Bold" />
    </Style>
View Code

 在WPF中还存在这样一些情况,在元素框架层次中的多个位置定义了同一个属性。例如,在Control类和TextBlock类中都定义了全部的字体属性。如果正在创建应用到TextBlock对象以及继承自Control类的元素的样式,可以按如下方式创建标记。

    <Style x:Key="BigFontButtonStyle">
      <Setter Property="Button.FontFamily" Value="Times New Roman" />
      <Setter Property="Button.FontSize" Value="18" />
      <Setter Property="TextBlock.FontFamily" Value="Arial" />
      <Setter Property="TextBlock.FontSize" Value="10" />
    </Style>
View Code

但是尽管Button.FontFamily属性和TextBlock.FontFamily属性是在他们各自的基类中分别声明,但它们都引用同一个依赖项属性。所以,当使用这个样式时,WPF设置FontFamily和FontSize属性两次。最后应用的设置具有优先权,并且被同时应用到Button对象和TextBlock对象。尽管这个问题很特别,许多属性并不存在该问题,但如果经常创建为不同的元素类型应用不同格式的样式,分析是否存在这一问题就显得很重要了。我们可以使用Style对象的TargetType属性,指定准备应用属性的类。如下

    <Style x:Key="BigFontButtonStyle" TargetType="Button">
      <Setter Property="FontFamily" Value="Times New Roman" />
      <Setter Property="FontSize" Value="18" />
      <Setter Property="FontWeight" Value="Bold" />
    </Style>

11.1.3 关联事件处理程序

  属性设置器是所有样式中最常见的要素,但是也可以创建为事件关联特定事件处理程序的EventSetter对象的集合。下面是一个示例,为MouseEnter和MouseLeave事件关联事件处理程序:

    <Style x:Key="MouseOverHighlightStyle">
      <Setter Property="TextBlock.Padding" Value="5"/>
      <EventSetter Event="FrameworkElement.MouseEnter" Handler="element_MouseEnter" />
      <EventSetter Event="FrameworkElement.MouseLeave" Handler="element_MouseLeave" />
    </Style>

下面是事件处理代码

        private void element_MouseEnter(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = new SolidColorBrush(Colors.LightGoldenrodYellow);
        }
        private void element_MouseLeave(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = null;
        }

MouseEnter和MouseLeave事件使用直接事件路由,这意味着它们不在元素树中冒泡和隧道。如果希望为大量元素应用鼠标悬停其上的效果,需要为每个元素添加MouseEnter和MouseLeave事件处理程序。基于样式的事件处理程序简化了这一任务。现在只需要应用单个样式,该样式包含属性设置器和事件设置器:

<TextBlock Style="{StaticResource MouseOverHighlightStyle}">Hover over me.</TextBlock>

在WPF中,事件设置器是一种很少使用的技术。如果需要使用此处演示的功能,可能更喜欢使用事件触发器,它以声明的方式定义了所希望的行为。事件触发器是专门为实现动画而设计的。

11.1.4 多层样式

  尽管可以砸许多不同层次蒂尼任意数量的样式,但是每个WPF元素一次只能使用一个样式对象。乍一看,好像是一种限制,但由于属性值继承和样式继承特性,因此实际上这种限制并不存在。例如,设想希望为一组控件使用相同的字体,而又不想为每个控件应用相同的样式。对于这种情况可以通过样式设置BasedOn特性,使用此类样式继承。如下

  <Window.Resources>
    <Style x:Key="BigFontButtonStyle">
      <Setter Property="Control.FontFamily" Value="Times New Roman" />
      <Setter Property="Control.FontSize" Value="18" />
      <Setter Property="Control.FontWeight" Value="Bold" />
    </Style>

    <Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}">
      <Setter Property="Control.Foreground" Value="White" />
      <Setter Property="Control.Background" Value="DarkBlue" />
    </Style>
  </Window.Resources>
View Code

11.1.5 通过类型自动应用样式

  到目前为止,已经看到了如何创建具有名称的样式以及如何在标记中引用它们。但还有另一种方法,既可以为特定类型的元素自动应用样式。只需要设置TargetType属性以指定合适的类型,并完全忽略键名。当这样做时,WPF实际上是隐式的使用类型标记扩展设置键名,如下所示:

x:Key="{x:Type Button}"

现在样式被自动应用于整个元素树中的所有按钮上。下面是一个示例,可以得到与上面相同的结果

<Window.Resources>
    <Style TargetType="Button">
      <Setter Property="FontFamily" Value="Times New Roman" />
      <Setter Property="FontSize" Value="18" />
      <Setter Property="FontWeight" Value="Bold" />
    </Style>
  </Window.Resources>

  <StackPanel Margin="5">
    <Button Padding="5" Margin="5">Customized Button</Button>
    <TextBlock Margin="5">Normal Content.</TextBlock>
    <Button Padding="5" Margin="5" Style="{x:Null}"
            >A Normal Button</Button>
    <TextBlock Margin="5">More normal Content.</TextBlock>
    <Button Padding="5" Margin="5">Another Customized Button</Button>
  </StackPanel>
View Code

不过中间的按钮通过将样式设置为Null有效的删除了样式。

尽管自动样式非常方便,但是它们会让设计变得复杂。下面是几条原因:

11.2 触发器

  WPF中的一个主题是以声明方式扩展代码的功能。当使用样式、资源以及数据绑定时,将会发现即使不使用代码,也能完成不少工作。使用触发器,可以自动完成简单的样式改变,而这通常需要使用样板事件处理逻辑。例如,当一个属性发生变化时可以进行响应,并自动调整样式。触发器通过Style.Triggers集合连接到样式。每个样式都可以有任意多个触发器,并且每个触发器都是System.Windows.TriggerBase的派生类的实例。下表列出了WPF中的选项

Trigger 这是一种最简单的触发器。它监测依赖属性的变化,然后使用设置器改变样式
MultiTrigger 与Trigger类似,但是这种触发器联合了多个条件。只有满足了所有这些条件,才会启动触发器
DataTrigger 这种触发器使用数据绑定。它与Trigger类似,只不过它监视的是所有绑定数据的变化
MultiDataTrigger 联合多个数据触发器
EventTrigger 这是最复杂的触发器。当一个事件发生时,这种触发器应用一个动画

通过FrameworkElement.Trigger集合,可以直接为元素应用触发器,而不需要创建样式。但这存在一个相当大的缺陷。这个Triggers集合只支持事件触发器。

11.2.1 简单触发器

  可以为任何依赖项属性关联一个简单触发器。例如,可以通过响应Control类的IsFocused、IsMouseOver以及IsPressed属性的变化,创建鼠标悬停效果和焦点效果。每个简单触发器都指定了正在监视的属性,以及正在等待的属性值。当该属性值出现时,将自动应用存储在Trigger.Setters集合中的设置器。但不能使用复杂的触发器逻辑。下面的触发器等待按钮获取键盘焦点,当获取焦点时或将其前景色设置为深红色。

      <Style.Triggers>
        <Trigger Property="Control.IsFocused" Value="True">
          <Setter Property="Control.Foreground" Value="DarkRed" />
        </Trigger>
        <Trigger Property="Control.IsMouseOver" Value="True">
          <Setter Property="Control.Foreground" Value="LightYellow" />
          <Setter Property="Control.FontWeight" Value="Bold" />
        </Trigger>        
        <Trigger Property="Button.IsPressed" Value="True">
          <Setter Property="Control.Foreground" Value="Red" />
        </Trigger>
      </Style.Triggers>
View Code

可以创建一次应用于相同元素的多个触发器。如果这些触发器设置不同的属性,这种情况就不会出现混乱。然而,如果多个触发器修改了相同的属性,那么最终是最后的触发器有效。如上面的示例中IsPressed属性最后触发所以它得以实现。触发器在标记中的排列顺序完全决定了最终的结果。如果希望创建只有当几个条件都为真时才激活的触发器,可以使用MultiTrigger。这种触发器提供了一个Conditions集合,可以通过该集合定义一系列属性和值的组合。如下示例中,只有按钮具有焦点而且鼠标悬停在该按钮上时,才会应用格式化信息:

<Style.Triggers>
<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="Control.IsFocused" Value="True"/>
        <Condition Property="Control.IsMouseOver" Value="True"/>
    </MultiTrigger.Conditions>
    <MultiTrigger.Setters>
        <Setters Property="Control.Foreground" Value="DarkRed"/>
    </MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
View Code

11.2.2 事件触发器

  普通的触发器等待一个属性发生变化,而事件触发器等待特定的事件被激发。事件触发器要求用户提供一系列修改控件的动作。这些动作通常被用于一个动画。下面使用一个动画效果是按钮的FontSize属性从而形成动画效果。

        <EventTrigger RoutedEvent="Mouse.MouseEnter">
          <EventTrigger.Actions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation
                  Duration="0:0:0.2"
                  Storyboard.TargetProperty="FontSize"
                  To="22"  />
              </Storyboard>
            </BeginStoryboard>
          </EventTrigger.Actions>
        </EventTrigger>
        <EventTrigger RoutedEvent="Mouse.MouseLeave">
          <EventTrigger.Actions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation
                  Duration="0:0:1"
                  Storyboard.TargetProperty="FontSize"  />
              </Storyboard>
            </BeginStoryboard>
          </EventTrigger.Actions>
        </EventTrigger>
View Code

 在这个示例中,使用了一个预先构建的DoubleAnimation类。DoubleAnimation类能够在一段给定的时间内将任何双精度数值逐渐改变为设定的目标值。因为双精度数值以较小的步长改变,所以将会发现字体逐渐的增大。当依赖项属性等于一个特定值时也可以执行动画。如果没有适合的事件可供使用而又希望执行一个动画时,需要使用之前介绍的属性触发器。不为属性触发器提供任何Setter对象。反而,设置Trigger.EnterActions和Trigger.ExitActions属性。这两个属性都有一个动作集合,例如,启动一个动画的BeginStoryboard动作。当属性到达指定的值时,执行EnterActions,而当属性离开指定的值时,执行ExitActions。

11.3 行为

  样式提供了重用一组属性设置的实用方法。属性设置仅仅是用户界面基础结构中的小部分。设置在大部分基本的程序通常需要大量的用户界面代码,这些代码与应用程序的功能无关。在许多程序中,用于用户界面的代码,无论是在数量还是复杂性上都超出了业务代码。许多这类代码是通用的,这意味着创建的每个WPF对象中需要编写相同的内容。所以开发了行为这一特征。其想法很简单:您创建一个封装了一些通用用户界面功能的行为。这一个功能可以是基本功能,也可以是负责功能。一旦构建了功能,可以将其添加到任意应用程序中的另一个控件中,具体方法是该控件连接到适当的行为并设置行为的属性。

11.3.1 获取行为支持

  重用用户界面代码通用块的基本机构不是WPF的一部分。反而,它被捆绑到Expression Blend。这是因为行为开始是作为Expression Blend的设计时特性引入的。Expression Blend仍然是通过将行为拖动到需要行为的控件上来添加行为的唯一工具。只需要付出很少的努力就可以在Visual Studio应用程序中创建和使用行为。只需要手动编写标记,而不是使用工具箱。为了获得支持行为的程序集,有两种选择。

无论采用哪种方法,在类似c:Program FilesMicrosoft SDKsExpressionBlend3InteractivityLibrariesWPF的文件夹中都将发现两个重要的程序集:

11.3.2 理解行为模型

  行为特性具有两个版本。一个版本针对Silverlight添加行为支持而设计,另一个版本是针对WPF设计的。尽管这两个版本提供了相同的特性,但是行为特性和Silverlight领域更吻合,因为它弥补了更大的鸿沟。然而,WPF支持触发器,行为特性包含自己的触发器系统,而触发器系统与WP模型不匹配。类似名称的这两个特性有部分重合但是不完全相同。在WPF中,触发器最重要的角色是构建灵活的样式和控件模板。在触发器的帮助下,样式和模板可以变得更加智能;WPF触发器支持更加强大的样式和控件模板。而Expression Blend触发器支持快速的不需要代码的应用程序设计。对于使用WPF的普通开发人员来说所有这些意味着:

11.3.3 创建行为

  行为旨在封装一些UI功能,从而可以不用编写代码就能够将其作用到元素上。从另外一个角度看,每个行为都为元素提供了一个服务。该服务通常涉及到监听几个不同的事件并执行几个相关操作。为了更好的理解行为,最好的方法是自己创建一个行为。假设为任意元素提供使用鼠标在anvas面板上拖动元素的功能。对于单个元素实现该功能的基本步骤是非常简单的,代码监听属性事件并修改设置相应Canvas坐标的附加属性。

  首先创建一个WPF类库程序集。在该程序中,添加对System.Windows.Interactivity.dll程序集的引用。然后,创建一个继承自Behavior基类的类。Behavior是一个通用类,该类使用一个类型参数。可以使用该类型参数将行为限制到特定的元素,或者可以使用UIElement或FrameworkElement将他们都包含进来,如下所示:

 public class DragInCanvasBehavior : Behavior<UIElement>
    {}

在任何行为中,第一步是覆盖OnAttached()和OnDetaching()方法。当调用OnAttached()方法时,可以访问放置行为的元素(通过AssociatedObject属性),并且可以关联事件处理程序。当调用OnDetaching()方法时,移除事件处理程序。下面是DragInCanvasBehavior类用于监视MouseLeftButtonDown、MouseMove以及MouseLeftButtonUp事件的代码:

        protected override void OnAttached()
        {
            base.OnAttached();

            // 连接事件处理程序.            
            this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            // 分离事件处理程序。
            this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
            this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
            this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
        }
View Code

最后一步是在事件处理程序中运行适当的代码。例如,当用户单击鼠标左键时,DragInCanvasBehavior开始拖动操作,记录元素左上角与鼠标指针之间的偏移,并捕获鼠标:

private Canvas canvas;
// 跟踪被拖动元素。
        private bool isDragging = false;

        // 当元素被点击时,记录的确切位置单击
        private Point mouseOffset;

        private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // 找到画布
            if (canvas == null) canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas;

            // 拖动模式开始.
            isDragging = true;

            // 得到点击的位置相对于元素(元素的左上角是(0,0)。
            mouseOffset = e.GetPosition(AssociatedObject);

            // 捕获鼠标。这样你就会保持receiveing MouseMove事件即使用户混乱鼠标元素。


            AssociatedObject.CaptureMouse();
        }
View Code

当元素处于拖动模式并且移动鼠标时,从新定位元素:

        private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                // 得到的元素的位置相对于画布上。

                Point point = e.GetPosition(canvas);

                // 移动元素。
                AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
                AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
            }
        }
View Code

当释放鼠标键时,结束拖动:

        private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (isDragging)
            {
                AssociatedObject.ReleaseMouseCapture();
                isDragging = false;
            }
        }
View Code

11.3.4 使用行为

  创建一个新的WPF应用程序项目。然后添加对定义DragInCanvasBehavior类的类库以及System.Windows.Interactivity.dll程序集的引用。在XML中映射这两个名称空间。假设DragInCanvasBehavior类的类库名为CustomBehaviorsLibrary,则需要以下标记:

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:custom="clr-namespace:CustomBehaviorsLibrary;assembly=CustomBehaviorsLibrary"

为了使用这个行为,只需要使用Interaction.Behaviors附加属性在Canvas面板中添加任意元素。下面的标记创建了一个具有三个图形的Canvas面板。两个Ellipse元素使用了DragInCanvasBehavior,并且能在Canvas面板中拖动。Rectangle元素没有使用DragInCanvasBehavior,因此无法移动。

<Canvas>
        <Rectangle Canvas.Left="10" Canvas.Top="10" Fill="Yellow" Width="40" Height="60"></Rectangle>
        <Ellipse Canvas.Left="10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">
            <i:Interaction.Behaviors>
                <custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
            </i:Interaction.Behaviors>
        </Ellipse>
        <Ellipse Canvas.Left="80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70">
            <i:Interaction.Behaviors>
                <custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
            </i:Interaction.Behaviors>
        </Ellipse>
    </Canvas>
View Code

本章节介绍了如何使用样式重用元素的格式化设置,还分析了如何使用行为开发整洁的用户界面功能包,然后可以将其连接到任意元素。这两个工提供了制作智能程度更高、具有更好维护性用户界面的方法——几种格式化细节和复杂逻辑,而不要求开发人员在整个应用程序中的不同位置使用这些细节和逻辑并多次使用。

相关推荐