WPF教程(译文)(第二部分)

(第一部分)

样式和控件模板

创建界面时,我们经常重复性地为很多控件设置外观属性,比如,我们希望所有的 Label 都显示为“Bold Trebuchet 12px”的字体,如果是 HTML,我们很容易用 CSS 实现,但如果是 WinForm,则会比较麻烦。WPF 引入了 Style 标签来满足这一常见的需求。先来看一个例子:

<StackPanel>
   <StackPanel.Resources>
      <Style TargetType="{x:Type Label}">
         <Setter Property="FontFamily" Value="Trebuchet" />
         <Setter Property="FontSize" Value="12" />
         <Setter Property="FontWeight" Value="Bold" />
      </Style>
   </StackPanel.Resources>

   <Label>Here is some text.</Label>
   <Label>More text.</Label>
   <Label>The last bit of text.</Label>
</StackPanel>

Style 标签中最常用的是 Setter 语法,从示例中你看到的,Setter 标签指定了样式相关的属性名和我们赋给该属性的值。是不是很简单?上一节中,我们讨论过的数据模板,它可以根据设定的数据类型来匹配,也可以根据指定的 key 来精确匹配,另外,数据模板的书写位置决定了它的作用范围。Style 标签同样适用类似的规则。这个例子中,Style 标签处在 StackPanel 的资源中,匹配类型为 Label 的标签,则此 StackPanel 内的所有标签都将适用此 Style 标签指定的样式。当然如果需要,你也可以为其中一些 Label 单独设定样式。另外一个有用的属性是 BaseOn,它允许 Style 标签间的“继承”关系:

<StackPanel>
  <StackPanel.Resources>
    <Style x:Key="baseStyle" TargetType="{x:Type Control}">
      <Setter Property="FontFamily" Value="Trebuchet" />
      <Setter Property="FontSize" Value="12" />
      <Setter Property="FontWeight" Value="Bold" />
    </Style>
    <Style BasedOn="{StaticResource baseStyle}" TargetType="{x:Type Label}">
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Foreground" Value="Red" />
        </Trigger>
      </Style.Triggers>
    </Style>
  </StackPanel.Resources>

  <Label>Here is some text.</Label>
  <Label>More text.</Label>
  <Label>The last bit of text.</Label>
</StackPanel>

此例中定义了两个 Style:一个将对所有 Control 类型有效,第二个只对所有的 Label 有效。我在此例中展示了另一个很有用的语法,Trigger。示例的效果将是,此 StackPanel 内的 Label,在鼠标悬停其上时,背景变为红色。

有了 Style 标签,你可以很容易地让你的界面代码变得风格统一、易于维护。它有很多好处。不过有时候,你或许需要更强大的。比如,你希望界面中的 Button 多一点立体感,或者卡通一些。仅使用基本的 Setter 语法,你无法做到这些,而需要使用控件模板(ControlTemplate)。先试着把控件想得抽象一些,WPF 中,比如一个 Button 控件,它只是一个抽象意义上的按钮,你在不同的软件中见过各种各样、不同外观的按钮,它们都算得上 Button,它们外观各异,有些还在鼠标滑过时变化一些效果,按下它们后会触发一些事件。WPF 中,你可以将这些按钮都用 Button 来实现,分别给它们指定不同的控件模板,它们就将显示出不同的外观效果。一些控件的模板写法简单,另一些控件的模板写法或许复杂一些。下面的例子来自 SDK:

<StackPanel>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <Setter Property="Foreground" Value="white" />
      <Setter Property="Margin" Value="1" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type Button}">
            <Grid>
              <Rectangle x:Name="GelBackground"
                Opacity="1" RadiusX="9"
                RadiusY="9"
                Fill="{TemplateBinding Background}"
                StrokeThickness="0.35">
                <Rectangle.Stroke>
                  <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Color="White" Offset="0" />
                    <GradientStop Color="#666666" Offset="1" />
                  </LinearGradientBrush>
                </Rectangle.Stroke>
              </Rectangle>
              <Rectangle x:Name="GelShine"
                Margin="2,2,2,0"
                VerticalAlignment="Top"
                RadiusX="6"
                RadiusY="6"
                Opacity="1"
                Stroke="Transparent"
                Height="15px">
                <Rectangle.Fill>
                  <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Color="#ccffffff" Offset="0"/>
                    <GradientStop Color="Transparent" Offset="1"/>
                  </LinearGradientBrush>
                </Rectangle.Fill>
              </Rectangle>
              <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Grid>
            <ControlTemplate.Triggers>
              <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Fill" TargetName="GelBackground">
                  <Setter.Value>
                    <RadialGradientBrush>
                      <GradientStop Color="Lime" Offset="0" />
                      <GradientStop Color="DarkGreen" Offset="1" />
                    </RadialGradientBrush>
                  </Setter.Value>
                </Setter>
              </Trigger>
              <Trigger Property="IsPressed" Value="true">
                <Setter Property="Fill" TargetName="GelBackground">
                  <Setter.Value>
                    <RadialGradientBrush>
                      <GradientStop Color="#ffcc00" Offset="0"/>
                      <GradientStop Color="#cc9900" Offset="1"/>
                    </RadialGradientBrush>
                  </Setter.Value>
                </Setter>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Foreground" Value="Black"/>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
          <Setter Property="Foreground" Value="Black"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </StackPanel.Resources>

  <Button Height="35" Width="125" Background="Black">Normal</Button>
  <Button Height="35" Width="125" Background="Black">Mouse Over</Button>
</StackPanel>

图10:使用模板的 Button

此例中,ControlTemplate 标签写在 Style 标签的内部(也可以单独写,在不同的 Style 间共享),定义了 Button 的外观效果,包含了若干个叠放着的矩形,它们有着不同的渐变效果。从这个例子中,你多少看到了控件模板的威力了吧,如果你乐意,你还可以在模板中加入 3D 形状或者动画效果。类似地,控件模板也使用类型匹配和 key 匹配,当然要注意不要将类型匹配错,驴唇是装不到马嘴上的。编写控件模板或许不太容易,因此最好先从好用的示例代码开始学习。SimpleStyles 的示例就是不错的资源,Kaxaml 编辑器(一个不错的 XAML 编辑器,或许你该试试)中就提供了此链接,Blend 中也有类似的资源。更多的探讨就超出了这篇入门教程的范畴了,我们进入下一个话题。

动画和 3D

动画和 3D 同样是很大的范畴,本文中我只能简单的介绍一些,并提出一些资源供你进一步的学习。

开始学习 WPF 动画之前,你应该对 DependencyProperties 有一个深入的理解,这样的属性都支持动画。前文已经提到过,Charles Petzold 的那本书在这些话题上有深入的解释,很有助于你的理解。

初步的印象是,WPF 使用 Storyboard 标签来组织动画效果。这种规则便于用 XAML 形式来记述,也能够承载越来越多的各种复杂动画效果。典型的动画以 BeginStoryboard 标签作为它的容器,用于容纳一个或多个 Storyboard:

<BeginStoryboard> 
  <Storyboard TargetProperty="Opacity"> 
    <DoubleAnimation From="1" To="0" Duration="0:0:1" /> 
  </Storyboard> 
</BeginStoryboard>

这个例子中的动画效果将是,Opacity 不透明度属性的值从 1 变化到 0,动画效果的时长是 1 秒钟,也就是在一秒钟内逐渐“消失”掉。哪些控件可以应用这个动画效果呢?又如何指定该动画效果的触发时机呢?请看完整的写法:

<Button Height="40" Width="125"> 
  <Button.Triggers> 
    <EventTrigger RoutedEvent="Button.Click"> 
      <BeginStoryboard> 
        <Storyboard TargetProperty="Opacity"> 
          <DoubleAnimation From="1" To="0" Duration="0:0:1" /> 
        </Storyboard> 
      </BeginStoryboard> 
    </EventTrigger> 
  </Button.Triggers> 
</Button>

我们让这个 Button 在被点击的时候逐渐“消失”掉。触发器定义使用的是 Button 控件的 Click 事件这个 RoutedEvent。下面的例子中,这个动画效果作为样式定义的一部分出现,而触发方式则换了另一种方式:

<Window.Resources> 
  <Style TargetType="{x:Type RadioButton}"> 
    <Style.Triggers> 
      <Trigger Property="IsChecked" Value="True"> 
        <Trigger.EnterActions> 
          <BeginStoryboard> 
            <Storyboard TargetProperty="Opacity"> 
              <DoubleAnimation From="1" To="0" Duration="0:0:1" /> 
            </Storyboard> 
          </BeginStoryboard> 
        </Trigger.EnterActions> 
      </Trigger> 
    </Style.Triggers> 
  </Style> 
</Window.Resources> 
<RadioButton Height="40" Width="125"/>

嗯,它将在 IsChecked 属性变为 True 值时启动该动画效果,这是 Trigger.EnterActions。相对地,还有 Trigger.ExitActions,如果上例改为 ExitActions,则将在 IsChecked 属性从 True 值变为其他值时启动该动画。请注意,上面两例中的动画效果的类型为 DoubleAnimation,这里的“Double”是指被逐渐变化的属性(例子中的 Opacity)的类型为 Double 数字类型。WPF 中定义了 22 中类型的动画效果,如 ColorAnimation, VectorAnimation, PointAnimation 等。这些动画效果都提供了很多可设定的属性选项,用于动画效果的细节控制,比如 AccelerationRatio, DecelerationRatio, SpeedRatio, RepeatBehavior 等。此外,还可以使用 AnimationUsingPath(路径动画),先定义一个“路径”(比如一个正弦曲线),然后让动画的变化规则根据“路径”指示的规则来变化,比如让一个“灯泡”的亮度变化呈正弦曲线的规律,比如让一个“小球”的位置变化沿着一条抛物线的轨迹移动,等等。如果你希望更大的自定义能力,WPF 中还有 AnimationUsingKeyFrames (关键帧动画),可以定义复杂的、缺乏规则的、缺乏连贯性的变化效果。如果你对 WPF 中的动画有兴趣,我建议你读一读这两个博客,Charles Petzold (在他的文章里检索一下)和 theWPFblog,那里还有不少 3D 方面的示例。开始介绍 3D 之前,我得提示你,WPF 动画的结构并不限于平面的动画,它同样适用于 3D 物体的动画。

在这个教程即将收尾之前,我们来看一看 3D。前文提到的 Daniel Lehenbauer 写的那本“Windows Presentation Foundation Unleashed”书中,关于 3D 的那一章写的很精彩,应该是截至目前最好的 WPF 3D 介绍。Petzold 正在写的一本书,将全部用于介绍 WPF 3D。当然还有上面介绍的两个博客,还有以下的几个站点:The WPF3D Team Blog, Five Great WPF 3D Nuggets, 3D Tutorial,都是很好的资源。关于 3D,第一件事必须知道的是,所有的 3D 内容都必须写在 Viewport3D 控件内部,它好像一个窥视 3D 世界的窗口,你可以在这个 3D 世界里放上 Camera,调好角度,再放上光源,这样你就在 Viewport3D 控件里看到那个 Camera “拍”到的图像。WPF 提供了两种主要的 Camera,PerspectiveCamera 和 OrthographicCamera,关于这两者的差别,请看这里的讨论。光源你可以选择 AmbientLight 和/或 DirectionalLight。

<Viewport3D> 
   <Viewport3D.Camera> 
      <PerspectiveCamera   
         FarPlaneDistance="20" 
         LookDirection="5,-2,-3" 
         UpDirection="0,1,0" 
         NearPlaneDistance="1" 
         Position="-5,2,3" 
         FieldOfView="45" /> 
   </Viewport3D.Camera> 
   <ModelVisual3D> 
      <ModelVisual3D.Content> 
         <Model3DGroup> 
            <DirectionalLight Color="White" Direction="-3,-4,-5" /> 
            <GeometryModel3D> 
               <GeometryModel3D.Geometry> 
                  <MeshGeometry3D   
                     Positions="-1 -1 0  1 -1 0  -1 1 0  1 1 0" 
                     Normals="0 0 1  0 0 1  0 0 1  0 0 1" 
                     TextureCoordinates="0 1  1 1  0 0  1 0" 
                     TriangleIndices="0 1 2  1 3 2" /> 
               </GeometryModel3D.Geometry> 
               <GeometryModel3D.Material> 
                  <DiffuseMaterial> 
                     <DiffuseMaterial.Brush> 
                        <SolidColorBrush Color="Blue"/> 
                     </DiffuseMaterial.Brush> 
                  </DiffuseMaterial> 
               </GeometryModel3D.Material> 
            </GeometryModel3D> 
         </Model3DGroup> 
      </ModelVisual3D.Content> 
   </ModelVisual3D> 
</Viewport3D>

在这里例子里,我只放了一个平面,你可以添加自己的 3D 物体。和前面介绍的那样,这里用了 PerspectiveCamera,有光源 DirectionalLight,使用 MeshGeometry3D 定义了平面的位置,然后还给这个平面指定了 Material 材质。例子中使用了 DiffuseMaterial 材质,WPF 中还提供有 EmmisiveMaterial 和 SpecularMaterial 供你选用。如果你之前使用过其他 3D 软件,你应该了解材质的重要性。例子中的 DiffuseMaterial 使用了 Brush 来“粉刷”,如果你善于联想,你说我们是否可以把软件界面也放在某个 3D 物体的表面上?来看看这个网页吧,来看看 WPF 3D 潜在的神奇。

WPF 现状和未来

WPF 是一个威力十足的用户界面框架,我向 WPF 的设计团队致意,我相信它是到目前为止最棒的界面框架,无论在任何平台。它还只是一个 Version 1,它还有成长的空间。下面,我将以我的一些想法来总结本文,我希望在未来版本中看到以下的功能:

  • 对话框是常见的需求,我希望看到和 WPF 普通窗口同样风格的对话框;
  • 数据绑定是我的最爱,不过当我用 Reflector 查看其内部结构时发现,它使用反射发出功能来实现(并不是很意外),我希望 WPF 团队应该尝试更快的简单属性存取(setter/getter)和 LCG(译注:不知道此处的 LCG 具体是什么的缩写),我不确定这样做是否有可行性,也不确定是否能提高运行效率,不过我想这是值得去深入研究的;
  • 希望有更多通用的控件,如 DatePicker, Calendar, MaskedTextBox, DataGrid, PropertyGrid 等;
  • 还有,3D 的运行效率希望能够得到改善,也希望更多材质类型,对 pixel shader 的支持;
  • 如果是 XBAP 方式运行,希望有“最小化 WPF”的功能,希望能有更多的 WPF 特性被移植到 XBAP 中来。

随着 Orcas 发布的临近,我希望其中的一部分能够尽早的看到。

展望未来,Microsoft 通过 Silverlight 已经开始将 WPF 和 .NET Framework 的触角伸向其他的操作系统平台。在 MIX07 展示时,Microsoft 声称 Silverlight 1.1 将包含一个跨平台的 CLR 和 BCL(基础类库)的一个子集版本,对于 .NET 开发者来说,这是一个很好的消息,我们将有可能使用喜欢的编程语言,为 Mac 和 PC 机编写基于浏览器的 RIA 程序。Silverlight 1.0 不包含 CLR,如果你希望使用 .NET 语言来编写,那你得用 Silverlight 1.1(译注:目前已改称 2.0 版本),其中包含的 BCL 子集包含了多线程、数据绑定、Web服务等重要功能。我看好这项技术,也坚信 Microsoft 正引领我们走向正确的未来方向。

结语

我希望我的这篇简介能够帮助你了解 WPF 的全貌。这是一个全新的、丰富功能的界面框架,针对 Windows 平台,并且有一个“子集”面向浏览器。它从此前的各种界面框架中获取了很多灵感,它不露痕迹的整合了那些框架的优点,为开发者提供了强大的能量来构建新一代的用户界面。

(作者 Rob Eisenberg 的博客)

posted on 2008-03-31 14:07  破宝  阅读(502)  评论(0编辑  收藏  举报

导航