UI 前沿技术

Silverlight、Windows Phone 7 和多点触控 Thumb

Charles Petzold

下载代码示例

Charles Petzold
对于大多数 Silverlight 编程人员来说,有关 Windows Phone 7 的最激动人心的消息就是:它支持 Silverlight 作为它的两个编程接口之一(另一个是 XNA)。Silverlight 编程人员不但可以利用他们在为手机编写新应用程序方面的现有知识和技能,还应该能为共享代码的 Web 和手机构建 Silverlight 程序。

当然,共享代码,特别是 UI 代码,并不像表面看上去那么简单。手机中所使用的 Silverlight 版本称为 Silverlight for Windows Phone,其通常是 Silverlight 3 的精简实现。在构思共享代码应用程序时,您需要仔细阅读以下文档:对于每个 Silverlight 类,联机文档将指示支持该类的环境。在每个类中,属性、方法和事件的列表都使用图标来指示 Windows Phone 7 支持。

适用于 Web 的 Silverlight 应用程序通过键盘、鼠标和多点触控来获取用户输入。在 Windows Phone 7 程序中,多点触控是主要输入方式。由于没有鼠标,因此即使手机上可能有硬件键盘,Silverlight 程序也只能依赖于虚拟键盘(即软件输入面板,简称 SIP),且只能通过 TextBox 控件执行。

如果现有的 Silverlight 程序从不直接获取键盘或鼠标输入,并且完全依赖于控件,则不必为转换为多点触控而费心。并且,如果您的程序包含自己的鼠标逻辑,那么实际上在将该程序迁移到手机上时可以保留该逻辑。

在手机上,主要触控事件会被转换为鼠标事件,以便现有的鼠标逻辑能正常运转。(主要触控事件是指在没有其他手指触摸屏幕时第一个触摸屏幕的手指的整个活动。)

从鼠标转换到多点触控需要注意一些问题:Silverlight for the Web 和 Silverlight for Windows Phone 都支持静态 Touch.FrameReported 事件,但此事件是多点触控的相当低级别的接口。我在 2010 年 3 月发表的“手指之舞:探讨 Silverlight 中的多点触控支持”(msdn.microsoft.com/magazine/ee336026) 一文中着重讨论过此事件。

Silverlight for Windows Phone 支持源自 Surface SDK 的操作事件的子集,并且此后已成为 Windows Presentation Foundation (WPF) 的一部分。这是多点触控如何逐步成为主流的示例。手机仅支持转换和缩放功能,并不支持旋转,且未实现延时,尽管您自己拥有可实现延时的足够多的信息。Web 版本的 Silverlight 中尚不 支持这些操作事件。

总之,如果要想在 Silverlight for the Web 和 Silverlight for Windows Phone 之间共享代码,则您将总需使用鼠标事件或 Touch.FrameReported。

考虑使用 Thumb

然而,这里还有另一个选择:如果您只需要操作事件的转换支持,并且您不想为判断输入是来自鼠标还是触控而费心,则可以使用一种能以非常纯粹的形式提供这种支持的控件。这个控件就是 Thumb。

您可能从未实际接触过 Thumb。Thumb 控件在 System.Windows.Controls.Primitives 命名空间中是隐藏的,主要用于 ScrollBar 和 Slider 模板。但您还可将其用于其他工作,最近我在考虑将 Thumb 用作操作事件的转换功能的高级实现。

现在,Thumb 不再是真正的“多点”触控控件,其一次仅支持一个触控。但是,通过深入研究 Thumb,您将有机会尝试支持触控计算以及在 Silverlight 应用程序和 Windows Phone 7 应用程序之间共享代码。

Thumb 可定义三个事件:

  • DragStarted 在用户使用手指或鼠标首次触摸控件时触发。
  • DragDelta 指示鼠标或手指相对于屏幕的移动。
  • DragCompleted 指示鼠标或手指已抬起。

DragDelta 事件附带了具有属性 HorizontalChange 和 VerticalChange 的事件参数,自最后一次事件起,这两个属性开始指示鼠标或手指的移动。通常,您会将增量变化添加到 TranslateTransform(设置为某些可拖动元素的 RenderTransform 属性)的 X 和 Y 属性或添加到 Canvas.Left 和 Canvas.Top 附加属性,以便处理此事件。

默认状态下,Thumb 非常简单。与其他控件一样,我们通常将 HorizontalAlignment 和 VerticalAlignment 属性设置为 Stretch,以使 Thumb 在一般情况下填充所允许的区域。否则,Silverlight Thumb 仅适用于 4 个单位面积像素数。在 Silverlight for Windows Phone 中,Thumb 有 48 个单位面积像素数,但在视觉上,其只有 24 个单位面积像素数,因为所有四个侧面上都有 12 个像素宽的透明边框。

您最起码在 Thumb 上要设置显式高度和宽度。图 1 并列显示了 Silverlight 和 Windows Phone 7 版本,并使用手机的默认配色方案“在深色背景中显示浅色文本”。对于这两个版本,我已将高度和宽度设置为 72 并将背景设置为蓝色,在 Silverlight 版本中按 Thumb 则会变成渐变色。Thumb 不会关注 Foreground 属性。

图 1 Silverlight 和 Windows Phone Thumb 控件

通常,您将不但要调整 Thumb 的大小,还要应用用于重定义控件的视觉效果的 ControlTemplate。此 ControlTemplate 也可以是非常简单的。

共享控件

假设您需要一个允许用户在屏幕上随处拖动位图的控件。最简单的方法就是将一个 Image 元素和一个 Thumb 放入一个单元格网格,保持 Thumb 的大小与图像的大小一致,并覆盖该图像。如果 Thumb 的 ControlTemplate 只是一个透明的矩形,则 Thumb 是不可见的,但它仍可以触发拖动事件。

让我们尝试创建一个既可用于常规 Silverlight 又可用于 Windows Phone 7 项目的控件。我将假定您已经安装了 Windows Phone 7 DeveloperTool (xbox.http://xbox.create.msdn.com)。您可以利用这些工具从 Visual Studio 创建 Windows Phone 7 项目。

首先,创建一个名为 DragImage 的常规 Silverlight 4 项目。生成的 DragImage 解决方案包含惯用的 DragImage 项目(Silverlight 程序本身)和一个 DragImage.Web 项目(在 HTML 或 ASP.NET 页中承载 Silverlight 程序)。

接下来,向解决方案中添加一个类型为 Windows Phone 应用程序的新项目。将此项目命名为 DragImage.Phone。(可能您不希望手机的程序列表或手机仿真器中显示该名称,因此可以在 WMAppManifest.xml 文件中的 APP 标记的 Title 特性中更改显示名称。)

通过右键单击 DragImage.Web 项目或 DragImage.Phone 项目,您可获取一个上下文菜单,可从该菜单中选择“设为启动项目”并运行常规 Silverlight 程序或 Windows Phone 7 程序。利用 Visual Studio 中的工具栏下拉列表,您可将手机程序部署到实际的手机设备或手机仿真器中。(如果对 Windows Phone 7 设备设置了此下拉列表,并且未附加任何手机,则 Visual Studio 将不会构建项目。)

在 DragImage 项目(常规 Silverlight 项目)中,添加类型为 Silverlight 用户控件的新项目。将其命名为 DraggableImage。如同往常一样,Visual Studio 将为此控件创建 DraggableImage.xaml 和 DraggableImage.xaml.cs 文件。

图 2 以控件的可视化树的形式显示了 DraggableImage.xaml。名为 LayoutRoot 的标准外部网格将占用该控件的容器的全部维度;内部网格则设置为左上角对齐,但有一个 TranslateTransform 分配给了其 RenderTransform 属性,目的是在外部网格中移动它。此内部网格保留了一个 Image 元素,该元素的顶部有一个 Thumb 控件,该控件的 Template 属性设置为只包含一个透明矩形的可视化树。

图 2 DraggableImage.xaml

<UserControl x:Class="DragImage.DraggableImage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  Name="ctrl">
    
  <Grid x:Name="LayoutRoot">
    <Grid HorizontalAlignment="Left"
          VerticalAlignment="Top">
      <Image Name="image" Stretch="None"
             Source="{Binding ElementName=ctrl, Path=Source}" />
      <Thumb DragDelta="OnThumbDragDelta">
        <Thumb.Template>
          <ControlTemplate>
            <Rectangle Fill="Transparent" />
          </ControlTemplate>
        </Thumb.Template>
      </Thumb>
      <Grid.RenderTransform>
        <TranslateTransform x:Name="translate" />
      </Grid.RenderTransform>
    </Grid>
  </Grid>
</UserControl>

请注意,Image 元素的 Source 属性绑定到了该控件本身的 Source 属性。 该属性在 DraggableImage.xaml.cs 文件中进行定义,如图 3 所示。 该文件可通过更改 TranslateTransform 的 X 和 Y 属性从 Thumb 中处理 DragDelta 事件。

图 3 DraggableImage.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace DragImage {
  public partial class DraggableImage : UserControl {
    public static readonly DependencyProperty SourceProperty =
      DependencyProperty.Register("Source",
      typeof(ImageSource),
      typeof(DraggableImage),
      new PropertyMetadata(null));

    public DraggableImage() {
      InitializeComponent();
    }

    public ImageSource Source {
      set { SetValue(SourceProperty, value); }
      get { return (ImageSource)GetValue(SourceProperty); }
    }

    void OnThumbDragDelta(object sender, DragDeltaEventArgs args) {
      translate.X += args.HorizontalChange;
      translate.Y += args.VerticalChange;
    }
  }
}

若要与 Windows Phone 7 项目共享该控件,请右键单击 DragImage.Phone 项目并选择“添加”|“现有项”,以显示“添加现有项”对话框。 导航到 DragImage 项目目录。 选择 DraggableImage.xaml 和 DraggableImage.xaml.cs,但不单击“添加”按钮, 而应单击“添加”按钮右侧的小箭头,并选择“添加为链接”。 DragImage.Phone 项目中显示的文件的图标上带有小箭头则表明该文件可在两个项目之间共享。

现在您可对 DraggableImage 文件做出更改,并且两个项目都将使用修订后的版本。

若要对其进行测试,则您将需要一个位图。 将该位图存储在每个项目内的 Image 目录中。 (您无需备份该位图的副本,可以使用一个链接将该位图添加到 Image 目录。)

此处应该有两个 MainPage.xaml 文件。 一个来自常规 Silverlight 项目,另一个来自 Windows Phone 7 项目。 在 Silverlight 项目的 MainPage.xaml 中,添加一个(传统上)名为“local”的 XML 命名空间绑定:

xmlns:local="clr-namespace:DragImage"

现在您可将 DraggableImage 添加到页面:

<Grid x:Name="LayoutRoot" Background="White">
  <local:DraggableImage 
    Source="Images/BuzzAldrinOnTheMoon.png" />
</Grid>

Windows Phone 7 项目的 MainPage 类位于名为 DragImage.Phone 的命名空间中,但是共享的 DraggableImage 类则位于名为 DragImage 的命名空间中。 您需要一个用于 DragImage 命名空间的 XML 命名空间绑定,可将其命名为“shared”:

xmlns:shared="clr-namespace:DragImage"

现在您可将 DraggableImage 添加到页面的内容区域:

<Grid x:Name="ContentPanel" 
  Grid.Row="1" Margin="12,0,12,0">
  <shared:DraggableImage 
    Source="Images/BuzzAldrinOnTheMoon.png" />
</Grid>

这可能是您在两个 Silverlight 项目(其中一个用于 Web,另一个用于 Windows Phone 7)之间共享控件的最简单方法。因为该控件使用 Thumb,所以两个程序都使用鼠标或触控。

DragImage 解决方案的可下载代码还包括一个名为 DragImage.Wpf 的项目,该项目也是使用此控件的 WPF 程序。但在一般情况下,在 Silverlight 和 WPF 之间共享控件比在 Silverlight 和 Windows Phone 7 之间共享控件要困难。

颜色和分辨率

当尝试在 Silverlight 和 Windows Phone 7 之间共享代码时,除了鼠标和触控输入,您还需要处理另外两个问题:颜色和视频分辨率。

在台式机上,Silverlight 将在白色的背景上显示黑色文本。(但 Silverlight 程序可使用 SystemColors 类来显示用户选择的 Windows 颜色。)默认情况下,Windows Phone 7 将在黑色的背景上显示白色文本,除非用户将配色方案更改为在白色的背景上显示黑色文本。Windows Phone 7 可提供一些方便的预定义资源键(如 PhoneForegroundBrush 和 PhoneBackgroundBrush),以帮助某个程序使用选定的配色方案。

在使用显式颜色的 Silverlight 和 Windows Phone 7 之间共享的任意代码或标记将会找到一些方法来确定其运行的平台,以便获取正确的颜色。

视频分辨率问题则有些棘手。所有 Silverlight 坐标都以像素为单位,并且此规则也适用于手机。普通台式机视频显示器所具有的分辨率约为 100 点/英寸 (DPI)。(例如,假如有一个 21 英寸的视频显示器处理 1600 × 1200 像素(或对角方向 2000 像素),则其分辨率为 105 DPI。)默认情况下,Windows 会假定显示器分辨率为 96 DPI,但用户可更改此分辨率,以使屏幕更易于读取。

Windows Phone 7 设备有一个 480 × 800 像素的屏幕,且对角线为 933 像素。但是,屏幕的对角线只有 3.5 英寸长,也就是说,其分辨率约为 264 DPI,这是台式机显示器分辨率的 2.75 倍。

这就意味着,在台式机上视觉效果好的特定大小的共享元素若放到手机上则会过小。然而,手机的观看距离通常比台式机显示器的观看距离要短,因此,若要使元素在手机上可见,不必将其整个放大 2.75 倍。

Thumb 应该多大才便于触摸呢?我看到的一条标准指示触摸目标的宽度和高度应该为 9 毫米(0.25 英寸)。在台式机显示器上的分辨率为 96 像素/英寸(即 34 像素),但是在手机上的分辨率为 93 像素。

另一方面,Windows Phone 7 设备的标准按钮的高度仅为 72 像素,这看来已经足够了。或许最好的方法就是不停尝试直至找到易于使用的分辨率,但这种方法太笨了。

进行调整

传统上,程序通过使用条件编译的预处理器指令针对各种平台对自身进行调整。Silverlight 程序定义了条件编译符号 ILVERLIGHT,Windows Phone 7 程序则定义了条件编译符号 SILVERLIGHT 和 PHONE。(您可通过选择“项目属性”页上的“生成”选项卡查看这些。)这就意味着您可以具有以下形式的代码:

#if PHONE
  // Code for Windows Phone 7
#else
  // Code for regular Silverlight
#endif

或者,您可通过访问 Environment.OSVersion 对象在运行时进行区分。 如果 Platform 属性为 PlatformID.WinCE,并且 Version.Major 属性为 7 或更高,则表示您的代码正在 Windows Phone 7 设备(也有可能是 Windows Phone 8 或 9)上运行。

理论上,通过使用在标记兼容性 (mc) 命名空间中定义的 AlternateContent 和 Choice 标记,就可能定义 XAML 文件的条件部分,但是,Silverlight 中却并不支持这些标记。

但是 XAML 可包含数据绑定,且这些绑定能够根据平台引用不同的对象。 XAML 也可具有针对不同平台检索不同对象的 StaticResource 引用。 我在 TextTransform 程序中使用的就是这种方法。

我按照创建 DragImage 解决方案的相同方法创建了 TextTransform 解决方案。 该解决方案包含三个项目:TextTransform(Silverlight 程序)、TextTransform.Web(承载 Silverlight 程序的 Web 项目)和 TextTransform.Phone (Windows Phone 7)。

然后,我在 Silverlight 项目中创建了一个派生自 UserControl 的 TextTransformer 控件。 此控件已在 Silverlight 项目和 Windows Phone 7 项目中共享。 TextTransformer 控件包含一个硬编码文本字符串(单词“TEXT”),字符串用边框包围起来,边框的四个角各有一个 Thumb 控件。 移动 Thumb 将导致边框和文本块发生非仿射转换。 (仅当由边框构成的四边形不具有凹角时,系统才会正常运行。)

TextTransformer.xaml 文件未为 Thumb 创建新的模板,但定义了一个样式,如图 4 所示。

图 4 TextTransformer.xaml 中的 Thumb 样式

<Style x:Key="thumbStyle" TargetType="Thumb">
  <Setter Property="HorizontalAlignment" 
          Value="Left" />
  <Setter Property="VerticalAlignment" 
          Value="Top" />
  <Setter Property="Width" 
          Value="{StaticResource ThumbSize}" />
  <Setter Property="Height" 
          Value="{StaticResource ThumbSize}" />
  <Setter Property="RenderTransform">
    <Setter.Value>
      <TranslateTransform 
        X="{StaticResource HalfThumbOffset}"
        Y="{StaticResource HalfThumbOffset}" />
    </Setter.Value>
  </Setter>
</Style>

请注意对 ThumbSize 和 HalfThumbOffset 的引用。 尽管用于显示文本的文本块通过属性继承获取了正确的 Foreground 属性,但是,边框必须使用相同的前景色进行显式着色:

<Border Name="border"
        BorderBrush="{StaticResource ForegroundBrush}"
        BorderThickness="1">

在何处定义这些资源? 在 App.xaml 中定义它们。 常规 Silverlight 项目在其包含以下内容的 App.xaml 文件中包含了一个 Resources 集合:

<Application.Resources>
  <SolidColorBrush x:Key="BackgroundBrush" Color="White" />
  <SolidColorBrush x:Key="ForegroundBrush" Color="Black" />
  <system:Double x:Key="ThumbSize">36</system:Double>
  <system:Double x:Key="HalfThumbOffset">-18</system:Double>
</Application.Resources>

Windows Phone 7 程序的 App.xaml 文件引用了两个画笔的预定义资源,并定义了更大的 ThumbSize 和 HalfThumbOffset 值:

<Application.Resources>
  <SolidColorBrush x:Key="BackgroundBrush"
     Color="{StaticResource PhoneBackgroundColor}" />
  <SolidColorBrush x:Key="ForegroundBrush"
     Color="{StaticResource PhoneForegroundColor}" />
  <system:Double x:Key="ThumbSize">96</system:Double>
  <system:Double x:Key="HalfThumbOffset">-48</system:Double>
</Application.Resources>

图 5 演示在浏览器中运行的程序,图 6 演示在 Windows Phone 7 仿真器上运行的程序。仿真器按照完整尺寸的百分之五十显示,以补偿手机上更高的像素密度。

图 5 浏览器中的 TextTransform 程序

图 6 手机仿真器上的 TextTransform 程序

这些技术表明,在台式机和手机之间共享代码已成为现实。如果您想要更加深入地探讨本主题,则可以看看 Surface Toolkit for Windows Touch,它包括针对 WPF 开发人员的 SurfaceThumb 控件。这就类似于 Thumb 控件,但它会为真正的多点触控添加支持并在缩略图转动时添加事件。有关详细信息,请参阅 Surface Toolkit for Windows Touch beta 页,网址为 msdn.microsoft.com/library/ee957351

Charles Petzold  《MSDN 杂志》*的长期特约编辑。*他的新书《Programming Windows Phone 7》可从 bit.ly/cpebookpdf 免费下载获得。

衷心感谢以下技术专家对本文的审阅:Doug Kramer 和 Robert Levy