WPF 的多点触控开发 - 多点触控 RSS 阅读器






4.94/5 (29投票s)
使用 Multi-Touch Vista 构建的多点触控 RSS 阅读器。
注意:需要 .NET 3.5 SP1 才能运行,并且需要 Multi-Touch Vista 输入服务 来启用多点触控。
目录
- 引言
- 选择多点触控 API
- Multi-Touch Vista 概述和基本设置
- 启动 Multi-Touch Vista 输入服务
- 开发多点触控应用程序
- 演示应用程序:多点触控 RSS 阅读器
- 总结
- 您有什么看法?
- 其他链接和参考
- 历史
简介
多点触控已正式成为一个热门词汇。受到《少数派报告》、Microsoft Surface、iPhone 和新的 Windows 7 Touch API 的启发,开发人员有许多理由投资于这一趋势。在本文中,我将首先介绍 WPF 的多点触控开发,重点介绍开发 PC 的设置以及多点触控的输入或模拟。然后,我将展示一个触摸 RSS 阅读器,并探讨一些在为触摸开发时遇到的怪癖和有趣的点。
多点触控开发本身并不难;最难的部分是正确选择 API 和设置设备。所以,我们将从...开始
选择多点触控 API
要使用 WPF 开发多点触控应用程序,我们目前有三种主要选择
- Windows 7 的官方WPF 4.0 Beta API
- Surface SDK
- 自定义解决方案,例如 Multi-Touch Vista [^]、MIRIA [^]、Breeze [^] 或 Tangibilis [^]。
第一个解决方案符合微软当前的战略。然而,WPF 4.0 Beta 2 API 仍处于非常粗糙的状态;尽管它们功能可用,但它们不提供易于使用的控件——您将不得不手动编程大部分交互逻辑。此外,您的部署平台仅限于 Windows 7。该解决方案肯定会不断发展,将来可能会成为最佳选择,但现在,我们将跳过。
Surface SDK 更有趣。它是一个非常完整的 API,具有有趣的控件和不断增长的社区。其主要限制在于硬件——它只能与 MS Surface 或其自己的模拟器应用程序一起使用。
因此,我们将采用第三种解决方案。它不仅是最灵活的解决方案(几乎适用于任何硬件和操作系统),而且易于使用且开源。在本文中,我将使用 Daniel D,又名“Nesher”开发的非常完整的软件包 Multi-Touch Vista [^]。它包含一个灵活的输入系统和一个带有许多控件的 WPF 框架,并根据 GPL 许可。因此,您必须在您的应用程序中发布源代码,并在同一许可证下。在本文中,我将把 Multi-Touch Vista 称为MTVista。
Multi-Touch Vista 概述和基本设置
下一步是理解 MTVista 的输入层,以便正确配置它。这是 MTVista 的基本架构
您可以看到,如果您不想为 MTVista 编写自定义输入提供程序,您将需要一个 TUIO 兼容的设备或多个鼠标进行模拟。TUIO 是一个被广泛采用的开放协议,用于广播触摸消息。大多数多点触控硬件开箱即不兼容 TUIO,因此我们有几种情况
- 您有一个Windows 7 兼容设备,例如 Dell XT 或 HP TouchSmart
- 使用 WM_TOUCH 转 TUIO 接口 [^]
- 您有一个Wiimote 和红外笔
- 使用 WiimoteTUIO [^]
- 您有其他多点触控设备,例如使用 CCV/TouchLib 的多点触控桌、越狱的 iPhone 或 MS Surface
- 请查看 http://www.tuio.org/?software [^] 上的其他 TUIO 跟踪器实现
- 您没有任何设备
- 将多个鼠标连接到您的 PC 并使用 MTVista 的 Multiple Mice 提供程序来模拟多点触控。
在正确设置了 TUIO 设备或多个鼠标后,您可以下载并解压 MTVista 二进制文件,或从源代码构建它们。我建议从源代码构建,这样您就能获得最新的代码。
本文包含的源代码和演示项目都包含从变更集 29484 构建的 Multi-Touch Vista 二进制文件副本。
都准备好了吗?那么,我们继续...
启动 Multi-Touch Vista 输入服务
要启动 MTVista 输入服务,您可以
- 每次直接运行它,通过运行 *MultiTouch.Service.Console.exe*,或者
- 使用 *installutil.exe* 将 *MultiTouch.Service.exe* 安装为 Windows 服务(例如,运行 *%windir%\Microsoft.NET\Framework\v2.0.50727\installutil.exe /i Multitouch.Service.exe*),然后使用 *services.msc* 设置自动启动。
启动并运行后,您应该会在屏幕上看到一个或多个红点(每个连接的鼠标一个)。发生这种情况是因为 MTVista 默认使用 Multiple Mice 提供程序。如果您想使用 TUIO,则必须运行 *MultiTouch.Configuration.WPF.exe*,选择 TUIO 提供程序,然后单击蓝色箭头应用您的选择。您可能需要解除阻止 Windows 防火墙中的服务,因为它使用 UDP 数据包在本地广播 TUIO 消息。
就这样!最难的部分完成了 :-)。
开发多点触控应用程序
使用 MTVista 创建新应用程序,路径几乎总是一样的
- 创建一个普通的 WPF 应用程序。
- 添加对 MTVista 的 *Multitouch.Framework.WPF* 库的引用。
- 在 XAML 文件中,添加对 MTVista 命名空间的引用
xmlns:mt="http://schemas.multitouch.com/Multitouch/2008/04"
- 将 `<Window>` 标签更改为 `<mt:Window>`,并将代码隐藏类从 `System.Windows.Window` 更改为 `Multitouch.Framework.WPF.Controls.Window`。
就这样!现在您有了一个空的启用多点触控的窗口
其余的开发过程与普通 WPF 应用程序相同。在下一节中,我将展示演示应用程序,详细介绍多点触控开发的一些有趣点以及在处理多点触控时可能很有用的某些模式。
演示应用程序:多点触控 RSS 阅读器
RSS 阅读器如今无处不在;它们简单、有用且熟悉——几乎每个阅读器看起来都一样:一个包含或不包含类别的 Feed 列表,每个 Feed 的新闻项列表,以及选定文章的阅读区域。但我们如何在多点触控环境中使其更有趣且易于使用呢?
这里的演示应用程序试图稍微改变常见的 RSS 阅读器布局。其主要功能是
- 与 Windows RSS 平台(Internet Explorer Feeds)集成以获取其数据。
- 一个用于文件夹、Feed 和项的基于面板的系统。可以通过多点触控手势拖动和缩放面板,并且它们可以“反弹”应用程序的“墙壁”。
- 一个“垃圾桶”图标,通过拖动到其中来关闭面板。
- Feed 可以在应用程序内部阅读,无需外部浏览器。
现在,让我们深入了解这些功能,以发现它们背后的原理。
与 Windows RSS 平台集成
为了实现与 RSS 平台的集成,我使用了 Cristian Civera,又名“Ricciolo”出色的 PaperBoy 库,它是他 PaperBoy 应用程序 [^] 的一部分。该库将 RSS 平台包装在一个简单的模型中,并通过一个名为 `FeedManager` 的单例公开数据。
在此应用程序中,我们将使用模型-视图-视图模型 (MVVM) 模式将此模型与我们的多点触控前端连接起来。MVVM 是 John Gossman 首先描述的一种模式,它广泛用于开发 WPF 应用程序。如果您想了解更多关于它的信息,请查看 Josh Smith 在 MSDN Magazine 上的文章 [^] 或 CodeProject 上的其他几篇文章 [^]。在此项目中,我使用了一组简单的 ViewModel
这些 ViewModel 继承自 Josh Smith 开发的 MvvmFoundation Framework [^] 中的 `ObservableObject`。该框架通过简化 MVVM 中的重复任务,例如继承 `INotifyPropertyChanged` 和管理 `PropertyChanged` 事件。
这些 ViewModel 中的每一个都对应于我们想要表示的数据类型(文件夹、Feed 和 Feed 项)。它们公开了我们想要显示的所有属性,如项、标题和未读项,这些属性来自模型(Windows RSS 平台)。
为了初始化应用程序,会创建一个指向 `FeedManager.Current.Root` 的 `FolderViewModel`,并将其绑定到相应的视图(有关更多信息,请参阅下一节)。
基于面板的多点触控视图
在模型和 ViewModel 设置好之后,我们必须考虑用户界面。用户期望面板支持拖动、缩放和旋转。此外,还必须使菜单能够像 iPhone 菜单一样工作(具有手指滚动和惯性)。
要使用 MTVista 创建一个可滚动视图,我们只需将 `ItemsControl` 包装在 MTVista 的 `ScrollViewer` 中即可。在这里,我们将使用 MTVista 的 `DraggableScrollViewer`,因为它还支持拖动(当您将元素拖出时,它会触发一个事件)。一个不带样式和行为的可滚动视图在此应用程序中看起来是这样的
[FolderAndFeedView.xaml] (已修改)
<Border x:Class="VirtualDreams.TouchReader.View.FolderAndFeedView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mt="http://schemas.multitouch.com/Multitouch/2008/04">
<DockPanel>
<TextBlock DockPanel.Dock="Top"
Text="{Binding Title}"/>
<Rectangle DockPanel.Dock="Bottom" />
<mt:DraggableScrollViewer x:Name="scroll">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel
Width="{Binding ElementName=scroll, Path=ActualWidth}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</mt:DraggableScrollViewer>
</DockPanel>
</Border>
关于此视图的一些有趣点
- 视图绑定到 ViewModel 中的 `ReadOnlyCollection<T>`(在本例中为 `Items`)。
- 视图是 `Border` 而不是 UserControls,因为 MTVista `ScrollViewer` 中存在与 UserControls 的一个 bug(有关详细信息,请参阅 此 [^])。
- 我们使用 `VirtualizingStackPanel` 来提高显示大量项时的性能。
应用程序中的其他视图与此类似。接下来,我们需要在屏幕上显示我们的视图并启用多点触控交互(缩放、拖动和缩放)。
MTVista 的 `TouchablePanel` 可以做到这一点——它的每个子元素都将支持这些自然的触摸手势,类似于 Surface SDK 的 `ScatterView`。*MainWindow.xaml* 看起来会是这样
[MainWindow.xaml] (已修改)
<mt:Window x:Class="VirtualDreams.TouchReader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mt="http://schemas.multitouch.com/Multitouch/2008/04"
xmlns:local="clr-namespace:VirtualDreams.TouchReader"
Title="TouchReader"
TextElement.FontFamily="Segoe, Calibri, Segoe UI"
TextElement.FontStretch="Condensed">
<Grid>
<mt:TouchablePanel x:Name="mainPanel"
AngularDamping="0.1"
LinearDamping="0.9"
EnableWalls="True"
RandomizePositions="False">
</mt:TouchablePanel>
<local:Trash HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="15" />
</Grid>
</mt:Window>
正如您所见,它没有什么特别之处
- 在 `TouchablePanel` 中,阻尼属性用于自定义惯性和摩擦。`EnableWalls` 将使元素反弹,而 `RandomizePositions` 设置为 false 以确保我们可以控制此面板子元素的初始位置。
- “垃圾桶”元素将在下一节中详细介绍。
现在我们有了 `TouchablePanel`、一组视图和 ViewModel。下一步是启用一个元素从视图中拖出并创建一个新的视图,具体取决于正在拖动的内容。
为此,核心思想是创建一个类似 工厂 的类,它能使 `TouchablePanel` 创建其子视图。一种解决方案是继承 `TouchablePanel` 并添加创建视图的方法,使其充当工厂。
但是,对于本文,我试图让这个解决方案尽可能通用:我想创建一个可重用的工厂接口,可以在每种特定情况下实现它,从而实现更高的可重用性。在此应用程序中,工厂架构看起来是这样的
工厂的实现必须根据新项的 DataContext 及其位置来决定如何创建视图。它还需要知道目标面板,以便相应地将其添加到其子项中。
在此应用程序中,实现的工厂方法的工作方式如下
- 首先,它检查 DataContext 是否是有效的 `RssViewModelBase`。
- 如果是,它会根据其类型查找正确的视图。
- 然后,它创建视图并将其与 DataContext 关联。
- 视图使用 `Canvas.SetTop` 和 `Canvas.SetLeft` 定位。这之所以可行,是因为 `TouchablePanel` 继承自 `Canvas`。
- 接下来,它为此新视图添加一个事件处理程序,使元素在被触摸时能够捕获联系。这使得视图可以在不必直接从它们开始移动的情况下被拖动。
- 最后,它将视图添加到面板的子项中,从而显示该视图。
最后,为了将工厂与视图连接起来,我们将使用附加行为(有关更多信息,请参阅 Josh Smith 的这篇文章 [^])。在此应用程序中,使用了两个行为
- 一个允许我们将 `IPanelViewFactory` 链接到 `Panel` 的行为,以及
- 一个处理 `DraggableScrollViewer` 的 `ElementDragged` 事件并调用 Factory 从被拖动元素的 DataContext 创建视图的行为。
这些行为的用法如下
<Panel b:PanelBehavior.ViewFactory="{Binding [reference to the factory]}" ... />
<mt:DraggableScrollViewer
b:ElementDraggedCreateViewBehavior.PanelViewFactory=
"{Binding [reference to the factory]}" ... />
您可以通过查看 *MainWindow.xaml* 和可滚动视图(*FolderAndFeedView.xaml*)中的源代码来查看这些行为的用法。
这种方法非常灵活:每当您想构建另一个需要控制如何在面板中创建视图的应用程序时,您只需实现一个 `IPanelViewFactory` 并以类似的方式将其附加。
在一切连接好之后,最后一步是通过调用主面板的 Factory 来创建根文件夹视图(来自 `FeedsManager.Current.RootFolder`)来初始化应用程序
[MainWindow.xaml.cs]
public MainWindow()
{
InitializeComponent();
// Initialize the form by creating the root folder view,
// using the mainPanel's ViewFactory
PanelBehavior.GetViewFactory(mainPanel).CreateViewFromDataContext(
new FolderViewModel(FeedsManager.Current.RootFolder),
new Point(100, 100)
);
}
垃圾桶图标用于关闭视图
我们想实现的下一个功能是垃圾桶图标。这里的想法是允许将视图拖动到垃圾桶中以关闭它们,然后用动画隐藏它们。在这里,您可以看到一个视图被垃圾桶“吞噬”
在此应用程序中,垃圾桶图标只是一个简单的 UserControl
[Trash.xaml]
<UserControl x:Class="VirtualDreams.TouchReader.Trash"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="200"
Width="200">
<UserControl.Background>
<ImageBrush ImageSource="/Resources/Images/trash.png"
Opacity="0.8" />
</UserControl.Background>
</UserControl>
现在,让我们分解垃圾桶的要求,找出使其工作所需的东西
- 将被拖动的面板必须位于支持拖动的面板(通常是 `TouchablePanel`)内。
- 在 MTVista 中,表示联系人已从元素中“释放”的事件是 `ContactRemoved` 事件。在这种情况下,`PreviewContactRemoved` 事件将冒泡到父面板。
- 垃圾桶不必属于该面板 - 如果它包含在该面板中,它将被“拖动”,而这正是我们不想要的。
- 一个面板可以有多个垃圾桶(例如,一个多用户表,每个用户都有自己的垃圾桶),但一个垃圾桶只能与一个面板关联。
- 根文件夹不能关闭,否则我们将得到一个空面板,无法添加新项。
- 当一个元素被拖到垃圾桶时,我们希望使用动画将其缩小到零像素,给人一种垃圾桶“吞噬”该元素的印象。
从第一个和第三个要求可以看出,如果垃圾桶知道它的源面板,那将很有用,这样我们就可以处理面板的 `PreviewContactRemoved` 事件并做出相应的反应。
在此应用程序中,我使用了另一个附加行为来向 `Trash` 控件添加 `SourcePanel` 属性。此行为的用法如下
<local:Trash b:TrashBehavior.SourcePanel="{Binding [reference to the panel]}" ... />
此行为附加到 `Trash` 控件时,会将其添加到与面板关联的垃圾桶列表中。它还处理面板的 `PreviewContactRemoved` 事件并将其附加到一个如下工作的处理程序
- 首先,我们检查被拖动的元素,看它是否不是根文件夹或其他垃圾桶。
- 然后,我们计算元素与此面板的每个垃圾桶之间的交集,看是否存在重叠。
- 如果一个项与垃圾桶重叠,将启动一个动画。此动画作用于 `ScaleX`、`ScaleY` 和 `Opacity` 属性,将它们减少到零。字典用于将动画与被拖动的元素关联起来。
- 最后,当动画结束时(`CurrentStateInvalidated` 事件触发),我们使用该字典恢复元素并将其从面板中移除。
有关此行为的更多详细信息,请参阅 *TrashBehavior.cs* 中的 `PreviewContactRemoved` 处理程序方法。
在应用程序内阅读 Feed 项,无需外部浏览器
此演示应用程序的最后一个功能是能够在多点触控环境中阅读 Feed 内容,而无需外部浏览器。为此,我使用了 Ricciolo 的 PaperBoy [^] 应用程序中的另一组类,将 HTML 代码转换为 XAML `FlowDocument`。这组类是随 WPF SDK 发布的 Microsoft 示例的演进(有关更多详细信息,请参阅 此博客文章 [^])。
要使用此转换器,您只需使用类似如下的代码
xamlString = HtmlToXamlConverter.ConvertHtmlToXaml(htmlString, true);
在此方法中,调用
- 第一个参数是 HTML 内容字符串
- 第二个参数是一个布尔值,指示内容是否应包装在 `FlowDocument` 中
- 返回值是从 HTML 源转换的 XAML 字符串
转换后,我们只需使用 `XamlReader` 解析 XAML 代码
FlowDocument document = XamlReader.Parse(xamlString) as FlowDocument;
在此演示应用程序中,`FlowDocument` 显示在 MTVista `ScrollViewer` 内的 `FlowDocumentScrollViewer` 中,以启用触摸平移
[FeedItemView.xaml]
<mt:ScrollViewer VerticalScrollBarVisibility="Hidden">
<FlowDocumentScrollViewer Foreground="White"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
Document="{Binding Content}" />
</mt:ScrollViewer>
有关 HTML 到 XAML 转换和显示的更多详细信息,请参阅 *FeedItemViewModel.cs* 和 *FeedItemView.xaml*。我还建议访问 WPF SDK 示例 [^] 以及源代码中包含的 `HTMLConverter` 类。
总结
在本文中,我们触及了 WPF 多点触控开发的基础方面,如设置和 API 选择。我们看到了一个演示应用程序,其中包含许多您可能会在多点触控应用程序中发现有趣的常见模式,例如“垃圾桶”和从拖动的元素创建视图。
希望本文能为您启动自己的多点触控应用程序开发提供一些工具和想法。我期待您的下一代多点触控体验!
展望未来
此应用程序的一些有趣后续步骤可能是
- 使用 WPF 4.0 Beta 2 API 或 Surface SDK 开发版本。
- 使用 Chris Cavanagh 出色的 WPF Chromium WebBrowser [^] 在应用程序中实现功能齐全的浏览器。
- 实现一个“书架”系统来存储收藏的项(例如,Tafiti [^])。书架的实现与垃圾桶类似。
- 实现一个“回收站”功能,允许用户查看垃圾桶中的内容并恢复项。
- 创建用户界面来订阅/取消订阅 Feed,而不是使用 Windows RSS 平台。
- 实现数据库或 Web 存储(例如,Google Reader 同步)。
您怎么看?
这个项目有用吗?您如何看待提出的架构模式?
请留下您的评论和建议,如果本文让您满意,请投票支持。谢谢!
其他链接和参考文献
- 文件夹图标来自 DeviantArt 上的 Deleket 的 Sleek XP: Folders Icons Pack [^],根据 CC-BY-NC-ND 许可证授权
- 垃圾桶图标来自 DeviantArt 上的 Deleket 的 Folder Icons Pack [^],根据 CC-BY-NC-ND 许可证授权
- Feed 图标来自 DeviantArt 上的 Deleket 的 Sleek XP: Software Icons Pack [^],根据 CC-BY-NC-ND 许可证授权
历史
- v1.0 (2009/01/11) - 初始发布。