使用 C# 在 Windows* 8 商店应用中支持触摸

By David Medawar

下载文章

Download Enabling Touch in Windows 8* Store Apps with C# [PDF 314KB]

 

目标

本文向您介绍了如何在 Visual Studio* 11 IDE 中使用 C# 编程语言在 Windows 8* 商店应用中支持触摸。之前对触摸 API 是否熟悉都无关紧要,本文面向之前未接触过触摸 API 的新手。列出的 API 截止到 Windows 8 Release Preview 版操作系统和相应的Microsoft Visual Studio 2012 RC IDE。本文概述了如何使用可扩展标记语言 (XAML) 和"code-behind"(C# 代码) perspective(透视)绑定用户触摸事件,以实现丰富的用户体验。我们将使用儿童数学游戏作为触摸使用案例进行介绍。



1. 内容简介

由于用户对"亲自动手"体验的需求,支持触摸的应用愈发增多,也愈加普遍。为此,Windows 8 Metro 风格应用及相应的 Visual Studio (VS) IDE 为开发人员提供了触摸 API,支持为最终用户提供一致的触摸体验。本文向开发人员介绍了通过 XAML 和 code-behind C# perspective 在 商店应用中支持触摸功能的优选方法。UI 元素 XAML 标记和 C# code-behind 的结合在用户体验与幕后工作之间架起了一座桥梁。

本文档中的源代码是使用 2012 年 5 月 31 日 发布的 Windows 8 Release Preview (8400 build)、Visual Studio Ultimate 2012 RC (11.0.50522.1 RCEL) 和.NET* Framework 4.5.50501 版编写的。

在大致了解了触摸 API 之后,本文档将使用一个游戏实例加以说明。

 

2. XAML 中处理"点击 (Tap) 事件"

举例来说,用户点击 某个 UI 元素时,应用将会执行一些操作。让我们为图片为例。从图片的 XAML 声明开始,各自的"点击"事件处理程序可做如下定义:

<Image x:Name="c29" Height="100" Width="100" Tapped="Image_Tapped" Grid.Row="1" Grid.Column="8" />

在此范例中,定义了一张名为"c29"的图片,并提供了其尺度参数和网格位置参数。注意使用:

Tapped="Image_Tapped"

这其实表示点击这一特定图片时,支持的 C# code-behind 中的"Image_Tapped"方法将被调用。

 

3. C# 中的点击事件处理程序代码

使用之前章节的范例图片 XAML 定义,多个 UI 元素可以使用指定的点击处理程序方法。如要执行该操作,运行时代码需确定点击哪个 UI 元素。不过可以使用"FrameworkElement"轻松实现这一进程,开发人员代码看起来可能类似:

        //called when one of the game images is clicked
        private void Image_Tapped(object sender, RoutedEventArgs e)
        {
            //find out which game object was clicked
            FrameworkElement source_image = e.OriginalSource as FrameworkElement;

            for (int row = 0; row < ROWS; row++)
            {
                for (int col = 0; col < COLS; col++)
                {
                    //we found a match!
                    if (source_image.Name.Equals(images[row, col].Name))
                    { …//handler logic

 

在此使用了一组图片。使用进入例行程序中的事件参数,开发人员可以获取触摸的元素然后使用它的名称在其中找到一个匹配项。

在 XAML 中除了将"Tapped"作为关键字之外,还可以使用"DoubleTapped"。"Clicked"等其他关键字不在本文的讨论范围之内。

 

4. "指针"(Pointer) 事件

关键字"Tapped"使用起来非常简单。现在,听我细细道来。请参阅以下链接:

http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh465387#using_manipulation_events

请阅读"Using pointer events"(使用"指针"事件)章节。可以使用这一方法检测触摸和鼠标输入。但是此 API 仅限指触摸。下一章节会讲述多点触摸处理。

在 XAML 中使用 Pointer 就像使用关键字"Tapped"那样简单!文档中介绍了如何使用 XAML 关键字 PointerPressed、PointerReleased 和 PointerExited。参阅 MSDN 文章,了解相应的 code-behind 处理程序如何工作的范例。

请注意事件处理程序签名。请注意代码范例中使用的是"PointerRoutedEventArgs"。在之前的章节中介绍了"RoutedEventArgs"。这些不同的事件处理级别使得开发人员可以灵活分辨各种触摸事件类型!



5. "操作"(Manipulation) 事件

从这儿开始变得愈加有意思起来。"操作"事件甚至具有更多 XAML 关键字,这没有什么奇怪的。(请参阅上文提到的 MSDN 链接中的"Using manipulation events"(使用"操作"事件)章节。

为了简化,请将"操作"事件视为发生在以下事件阶段

第 I 阶段:ManipulationStarted。这在"操作"开始时执行。发生这一情形时表明用户将一只手指放在了 UI 元素上。

第 II 阶段:ManipulationDelta。用户继续按住 UI 元素并执行了拖拽,"操作"继续进行。即使是简单的"按住-拖拽-松开"顺序,也会多次调用该事件。这需要仔细考虑代码。

第 III 阶段:ManipulationInertiaStarting。在用户松开屏幕上的所有手指时会调用它,因此会将 UI 元素发送至惯量。

MSDN 链接中还介绍了其他"操作"事件类型。此外还需注意:使用滚轮时可以使用鼠标对"操作"进行测试。

请访问 MSDN 链接,了解 XAML 中的关键字用法。.此 API 最有意思的一点是在"操作"的过程中,开发人员可以检索速度并更改 UI 元素的位置。还支持通过矩阵变换操作 UI 元素,这在链接中也有所描述。

处理"操作"事件时还支持多点触摸,但是我们不在此对其进行具体讨论。请访问 MSDN 链接,了解更多详细信息。

 

6. 在设备部署之前测试:VS 模拟器

在编写完触摸检测 glue 后,就该进行测试了!在每个代码构建后通过有线方式将应用包手动传输至设备可能会很繁琐。从工作效率角度来看,最好采用以下两种方法:使用 VS 远程工具(包括远程调试程序)或者只是将代码部署到 VS 模拟器。我们将在本文中讨论第二种方法。

提示 VS "Debug"(调试菜单中在下拉栏中选择"<Project name> Properties")(<项目名称>属性选项然后选择左侧窗格中的"Debug"(调试选项。在此,可以将目标设备选作本地设备(VS 中的模拟)或者远程设备(通过远程调试在实际设备上进行无线调试)。

Visual Studio 允许开发人员向源代码中添加断点。一个调试情形需要确保"触摸"(touch) 事件例程满足以下两个属性:发生"触摸"事件时例程一直不能进入以及发生"触摸"事件时例程可可以一直进入。将 VS 中的断点添加至例程 glue 的开头,如下所示:

 

成功构建项目并从 VS 的"Debug"(调试)菜单中选择"Start Debugging"(开始调试)(或者按下 F5 键)后,应用开始运行。然后在进入 Image_Tapped 例程后,在执行到以下行时, VS 显示断点:

 

7. 使用案例:儿童数学游戏

在这一章节,我们将之前章节中介绍的概念应用于使用案例中:使用一个非常有说服力的儿童数学游戏。游戏的创意如下。用户从显示的选项中选择一个难度级别。在每个级别中,用户选择操作数(数字)和运算符号(+、-、*、/)。目标是使用操作数和运算符号构建一个运算顺序,创建一个合理的数学等式。如果等式正确,方片将消失;否则将仍然存在。目标是使得所有的操作数方片全部消失。如果等式构建不正确就可能长期困在这一级别!这在游戏开发之前需要考虑到。

应用开启后,用户会看到:

 

例如,如果用户选择第 3 级,将加载游戏面板 XAML 并开始游戏:

在这一游戏面板上,要构建的正确等式如下:

(1+2)-2 = 1

注意:游戏不使用正常的运算顺序--等式从左到右进行计算。所以用户可以在此按以下顺序选择以下方片:

1, + , 2, - , 2, 1

游戏不需要等号,游戏逻辑随着用户选择方片追踪累积总数。下图显示选择了除最终结果以外的所有方片。用户只需点击每张方片进行选择即可:

选择的方片会出现阴影,所以它们以更暗的颜色显示。现在,用户选择了"1",等式构建成功,这些方片全部消失!

但是有一个问题。游戏没有办法在算数上消除所有剩余的操作数方片。在以上显示的初级结构中赢得胜利不会进入下一阶段!

 

 

8. 数学逻辑预览

处理等式构建的逻辑具有两个主要要素:

队列选择队列上需要的方片,可以根据需要清除方片。

有限状态机 (FSM):保持状态,确定何时完成等式。

FSM 的工作原理如下。显然,等式的第一个元素必须是一个操作数。接下来用户必须选择一个运算符号。然后,必须再选择一个操作数。操作数-运算符号-操作数-运算符号的顺序至少发生一次,而且对于更加密集型的操作可能会发生多次。状态机检测到选择了两个连续操作数时,这表明等式构建完成。现在呈现部分相关代码:

//after selecting num in Q0, user must now specify an operator 
                case selection_state.Q1:
                    if (v == value.DIV || v == value.MINUS || v == value.MULT || v == value.PLUS) 
                    {

                        sel_state = selection_state.Q2; //move to next state
                        move_queue.Enqueue(v); //add last user operation to the queue
                        //set update flag for redraw
                        updates_pending[row, col] = true;
                    }

                    //else, user selected a number, so we just stay in Q1
                    else
                    {
                        clearSelection();
                        move_queue.Clear(); //wipe the moves the user has made
                        state[row, col] = SELECTED.YES;
                        sel_state = selection_state.Q1;
                        move_queue.Enqueue(v); //add last user operation to the queue

                        //set update flag for redraw
                        updates_pending[row, col] = true;
                    }
                    break;

在样本代码中,Q0 表示等式的第一个操作数期望的首个状态。我们看到在 Q1 中,代码期待用户选择一个运算符号。如果这样的话,将会排列选项,对方片进行标记以便更新。更新逻辑是一个阵列,为每个方片设置一个布尔值,因此在重绘时,由于性能原因,只有标记了的方片才能更新。

现在,如果用户选择了一个操作数,状态将不会改变。这将仅更新等式,因此现在的操作数是最近的选择。

 

9. 使用"操作"删除 UI 元素

这一章是使用儿童数学游戏创意的另外一个使用案例。如之前章节所述,该代码在三个阶段(开始、增量、惯量)进行"操作"。

这一章节中的代码片段提供了开发人员如何使用"删除"(fling) 手势的样本。这包括用户按住游戏方片并使用手指在屏幕上"删除它"(不讨论多点触摸)。

回忆之前章节中讨论的数学游戏。随着数据类型存储多个 UI 元素(图片),得到一个图片阵列。以下代码展示了如何定义这些图片的操作。

                //specify the manipulation events to be invoked for each game tile 

                for (x = 0; x < images.GetLength(0); x++)
                {
                    for (y = 0; y < images.GetLength(1); y++)
                    {
                        images[x, y].ManipulationStarted += manip_start;
                        images[x, y].ManipulationDelta += manip_delta;
                        images[x, y].ManipulationInertiaStarting += manip_complete;
                    }
                }

在 += 右边,指定了事件处理程序。例如,在一张图片上开始"操作"时,将调用以下例程:

//invoked when user begins a gesture on game tile
        void manip_start(object sender, ManipulationStartedRoutedEventArgs e)
        {
            //store the elapsed time to mark the beginning of this event
            start_ms = game_clock.Elapsed.TotalMilliseconds + (1000 * game_clock.Elapsed.TotalSeconds);

            //grab the image (game tile) where manipulation began!!!
            img = sender as Image;

            //retain image location in canvas at start of gesture
            initial_x = Canvas.GetLeft(img);
            initial_y = Canvas.GetTop(img);
…

使用参数发射器检索触摸的图片。代码还显示了如何捕获"操作"开启时间的样本(这有助于在给定的方向随时间变化计算位置变化时进行速度计算)。此外,Canvas 类用于在操作开始时捕获触摸图像的 x 和 y 坐标。注意,如之前章节所述,开发人员可以指定例程中的特定事件参数。在这一案例中,样本代码使用 ManipulationStartedRoutedEventArgs。

此处未显示 manip_delta 例程的主体。用户松开屏幕上的手指时,以下片段显示了触发的事件以及如何计算速度。然后,它让开发人员决定如何创建摩擦力 (friction) 事件,如随着时间的推移降低线性速度,以减缓删除对象的速度。

//invoked when user ends a gesture on game tile...use final velocity for projection
        void manip_complete(object sender, ManipulationInertiaStartingRoutedEventArgs e)
        {
            //we will compute the average linear velocity as
            //change in position over time of the manipulation interval
            //note that velocity is a vector, not a scalar, as it
            //has both magnitude and directional components

            finish_ms = game_clock.Elapsed.TotalMilliseconds + (1000 * game_clock.Elapsed.TotalSeconds);

            vel_x = e.Cumulative.Translation.X / (finish_ms - start_ms);
            vel_y = e.Cumulative.Translation.Y / (finish_ms - start_ms);
…

在此,速度的计算等于总位移的变化除以时间的变化。位移是相对于调用操作时图片的开始位置。此外,e.Velocities 可用于直接访问进入事件的速度。请注意,与标量速度不同,此处的速度是具有量级 方向的矢量。标记 vel_x 和 vel_y 对此进行了阐明。

 

 

10. 总结

本文向开发人员提供了在 Windows 8 商店 风格应用中启用触摸的指南。本文首先介绍了 XAML 中 UI 元素触摸事件的基本定义,然后概述了如何定义支持的代码"glue"(C# code-behind)。讨论以简单的单击检测开始,此外还介绍了定义和使用双击检测间隔时间的范例。

除了解释 商店应用的一些细节外,还介绍了易于从 API 库和运行时调试功能中使用的 VS IDE。这一令人兴奋的软件包对于希望从 UI 和触摸传感器中实现最具吸引力的用户体验的创新者来说还有极大的空间。本文介绍了一个令人兴奋的范例:使用触摸 API 开发儿童学习游戏。开始寻找其中的乐趣吧!

 

作者简介

David Medawar 是英特尔公司的一名软件工程师。他已在英特尔公司供职七年,他之前的开发经验包括系统 BIOS 和启动加载器启用。David 目前负责开发 Windows 8 和安卓 (Android) 支持的应用。

 

*文中涉及的其它名称及商标属于各自持有者。

** 该样本源代码根据"英特尔样本源代码许可协议"发布。

Для получения подробной информации о возможностях оптимизации компилятора обратитесь к нашему Уведомлению об оптимизации.
Теги: