一、基本的程序结构
一个需要响应多点触控的 Silverlight 应用程序必须将一个处理程序连接到静态 Touch.FrameReported 事件:
Touch.FrameReported += OnTouchFrameReported;
FrameReported 事件是静态 Touch 类的唯一公共成员。处理程序如下所示:
void OnTouchFrameReported(
object sender, TouchFrameEventArgs args) {
...
}
您可以在应用程序中安装多个 Touch.FrameReported 事件处理程序,所有这些事件处理程序都会报告应用程序中任何位置的所有触控事件。
二、事件传入参数TouchFrameEventArgs
TouchFrameEventArgs 有一个名为 TimeStamp 的公共属性(我还没有机会使用)和三个重要的公共方法:
TouchPoint GetPrimaryTouchPoint(UIElement relativeTo)
TouchPointCollection GetTouchPoints(UIElement relativeTo)
void SuspendMousePromotionUntilTouchUp()
GetPrimaryTouchPoint 或 GetTouchPoints 的参数仅用于报告 TouchPoint 对象的位置信息。您可以将空值用于此参数,位置信息相对于整个 Silverlight 应用程序的左上角。
多点触控支持多个手指同时触摸屏幕,触摸屏幕的每个手指(数量存在上限,当前通常为两个)都是一个触摸点。主要触摸点是指在没有其他手指触摸屏幕并且未按下鼠标按钮时触摸屏幕的手指。用一个手指触摸屏幕。这是主要触摸点。
在第一个手指仍触摸着屏幕时,将第二个手指放在屏幕上。很显然,第二手指不是主要触摸点。但现在仍将第二个手指放在屏幕上,抬起第一个手指,然后再将其放回到屏幕上。这是主要触摸点吗?不,都不是。仅当没有其他手指触摸屏幕时,才会出现主要触摸点。
在实际的多点触控应用程序中,您应该注意不要依赖主要触摸点,因为用户通常不会重视第一次触摸的特定意义。仅为实际触摸屏幕的手指激发事件。对非常接近屏幕但并未触摸屏幕的手指,不会进行悬停检测。
基于一个触摸点或多个触摸点激发一个特殊的 Touch.FrameReported 事件。从 GetTouchPoints 方法返回的 TouchPointCollection 包含与特定事件关联的所有触摸点。从 GetPrimaryTouchPoint 返回的 TouchPoint 始终是一个主要触摸点。如果没有与该特定事件关联的主要触摸点,GetPrimaryTouchPoint 将返回 null。即使从 GetPrimaryTouchPoint 返回的 TouchPoint 不是 null,它也不会与从 GetTouchPoints 返回的其中一个 TouchPoint 对象是相同对象,但在传递给这些方法的参数相同时所有属性将相同。
TouchPoint 类定义以下四个只读属性,这些属性都由依赖属性支持:
Action 属性,类型为 TouchAction(一个具有 Down、Move 和 Up 成员的枚举)。
Position 属性,类型为 Point,它相对于作为参数传递给 GetPrimaryTouchPoint 或 GetTouchPoints 方法的元素(或相对于参数为 null 的应用程序的左上角)。
Size 属性,类型为 Size。
TouchDevice 属性,类型为 TouchDevice。
TouchDevice 对象具有两个也由依赖属性支持的只读属性:
DirectlyOver 属性,类型为 DirectlyOver(手指下最上面的元素)。
Id 属性,类型为 int。
DirectlyOver 不必是传递给 GetPrimaryTouchPoint 或 GetTouchPoints 的元素的子项。如果手指在 Silverlight 应用程序内(由 Silverlight 插件对象的尺寸定义),但不在可点击测试控件覆盖的范围内,则此属性可以为空。(具有空背景刷的面板不可点击测试。)
若要在多个手指之间区分,ID 属性至关重要。在特定手指触摸屏幕时,与该手指关联的一系列特定事件总是以 Down 操作开始,接着是 Move 事件,最后是 Up 事件。所有这些事件都将与相同的 ID 关联。(但不要以为主要触摸点的 ID 值将为 0 或 1。)
大多数重要的多点触控代码都会使用 Dictionary 集合,其中 TouchDevice 的 ID 属性是字典键。这是跨事件存储特定手指信息的方式。
三、涂鸦板例子
涂鸦板例子 综合运用ApplicationBar菜单、多点触摸和IsolatedStorageFile本地存储
xaml
<phone:PhoneApplicationPage
x:Class="Jot.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot contains the root grid where all other page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"
Orientation="Horizontal">
<TextBlock x:Name="ApplicationTitle" Text="JOT"
Style="{StaticResource PhoneTextNormalStyle}"
Margin="12 0 0 0" />
<TextBlock Name="pageInfoTitle"
Style="{StaticResource PhoneTextNormalStyle}"
Margin="0" />
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<InkPresenter Name="inkPresenter" />
</Grid>
</Grid>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<!--工具条-->
<shell:ApplicationBarIconButton x:Name="appbarAddButton"
IconUri="/Images/appbar.add.rest.png"
Text="add page"
Click="OnAppbarAddClick" />
<shell:ApplicationBarIconButton x:Name="appbarLastButton"
IconUri="/Images/appbar.back.rest.png"
Text="last page"
Click="OnAppbarLastClick" />
<shell:ApplicationBarIconButton x:Name="appbarNextButton"
IconUri="/Images/appbar.next.rest.png"
Text="next page"
Click="OnAppbarNextClick" />
<shell:ApplicationBarIconButton x:Name="appbarDeleteButton"
IconUri="/Images/appbar.delete.rest.png"
Text="delete page"
Click="OnAppbarDeleteClick" />
<!--菜单栏-->
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="swap colors"
Click="OnAppbarSwapColorsClick" />
<shell:ApplicationBarMenuItem Text="light stroke width"
Click="OnAppbarSetStrokeWidthClick" />
<shell:ApplicationBarMenuItem Text="medium stroke width"
Click="OnAppbarSetStrokeWidthClick" />
<shell:ApplicationBarMenuItem Text="heavy stroke width"
Click="OnAppbarSetStrokeWidthClick" />
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>
cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace Jot
{
public partial class MainPage : PhoneApplicationPage
{
JotAppSettings appSettings = (Application.Current as App).AppSettings;
Dictionary<int, Stroke> activeStrokes = new Dictionary<int, Stroke>();
public MainPage()
{
InitializeComponent();
inkPresenter.Strokes = appSettings.StrokeCollections[appSettings.PageNumber];
inkPresenter.Background = new SolidColorBrush(appSettings.Background);
// Re-assign ApplicationBar button names
appbarLastButton = this.ApplicationBar.Buttons[1] as ApplicationBarIconButton;
appbarNextButton = this.ApplicationBar.Buttons[2] as ApplicationBarIconButton;
appbarDeleteButton = this.ApplicationBar.Buttons[3] as ApplicationBarIconButton;
TitleAndAppbarUpdate();
Touch.FrameReported += OnTouchFrameReported;
//一个需要响应多点触控的 Silverlight 应用程序必须将一个处理程序连接到静态 Touch.FrameReported 事件
}
//处理多点触摸事件
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
TouchPoint primaryTouchPoint = args.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null && primaryTouchPoint.Action == TouchAction.Down)
args.SuspendMousePromotionUntilTouchUp();
/*
* foreach 循环枚举从 GetTouchPoints 返回的 TouchPointCollection 的 TouchPoint 成员。
* (您会希望多点触控感知 Silverlight 程序能处理多个手指,但您不会希望它在检测到太多手指时出现故障!)ID 将添加到 Down 事件的字典中,然后从 Up 事件的字典中删除。
* */
TouchPointCollection touchPoints = args.GetTouchPoints(inkPresenter);
foreach (TouchPoint touchPoint in touchPoints)
{
Point pt = touchPoint.Position;
int id = touchPoint.TouchDevice.Id;
switch (touchPoint.Action)
{
case TouchAction.Down:
Stroke stroke = new Stroke();
stroke.DrawingAttributes.Color = appSettings.Foreground;
stroke.DrawingAttributes.Height = appSettings.StrokeWidth;
stroke.DrawingAttributes.Width = appSettings.StrokeWidth;
stroke.StylusPoints.Add(new StylusPoint(pt.X, pt.Y));
inkPresenter.Strokes.Add(stroke);
activeStrokes.Add(id, stroke);
break;
case TouchAction.Move:
activeStrokes[id].StylusPoints.Add(new StylusPoint(pt.X, pt.Y));
break;
case TouchAction.Up:
activeStrokes[id].StylusPoints.Add(new StylusPoint(pt.X, pt.Y));
activeStrokes.Remove(id);
TitleAndAppbarUpdate();
break;
}
}
}
//新增一个页面
void OnAppbarAddClick(object sender, EventArgs args)
{
StrokeCollection strokes = new StrokeCollection();
appSettings.PageNumber += 1;
appSettings.StrokeCollections.Insert(appSettings.PageNumber, strokes);
inkPresenter.Strokes = strokes;
TitleAndAppbarUpdate();
}
//上一个页面
void OnAppbarLastClick(object sender, EventArgs args)
{
appSettings.PageNumber -= 1;
inkPresenter.Strokes = appSettings.StrokeCollections[appSettings.PageNumber];
TitleAndAppbarUpdate();
}
//下一个页面
void OnAppbarNextClick(object sender, EventArgs args)
{
appSettings.PageNumber += 1;
inkPresenter.Strokes = appSettings.StrokeCollections[appSettings.PageNumber];
TitleAndAppbarUpdate();
}
//删除当前的页面
void OnAppbarDeleteClick(object sender, EventArgs args)
{
MessageBoxResult result = MessageBox.Show("Delete this page?", "Jot",
MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
if (appSettings.StrokeCollections.Count == 1)
{
appSettings.StrokeCollections[0].Clear();
}
else
{
appSettings.StrokeCollections.RemoveAt(appSettings.PageNumber);
if (appSettings.PageNumber == appSettings.StrokeCollections.Count)
appSettings.PageNumber -= 1;
inkPresenter.Strokes = appSettings.StrokeCollections[appSettings.PageNumber];
}
TitleAndAppbarUpdate();
}
}
//设置画笔背景颜色
void OnAppbarSwapColorsClick(object sender, EventArgs args)
{
Color foreground = appSettings.Background;
appSettings.Background = appSettings.Foreground;
appSettings.Foreground = foreground;
inkPresenter.Background = new SolidColorBrush(appSettings.Background);
foreach (StrokeCollection strokeCollection in appSettings.StrokeCollections)
foreach (Stroke stroke in strokeCollection)
stroke.DrawingAttributes.Color = appSettings.Foreground;
}
//设置画笔的粗细
void OnAppbarSetStrokeWidthClick(object sender, EventArgs args)
{
ApplicationBarMenuItem item = sender as ApplicationBarMenuItem;
if (item.Text.StartsWith("light"))
appSettings.StrokeWidth = 1;
else if (item.Text.StartsWith("medium"))
appSettings.StrokeWidth = 3;
else if (item.Text.StartsWith("heavy"))
appSettings.StrokeWidth = 5;
}
void TitleAndAppbarUpdate()
{
pageInfoTitle.Text = String.Format(" - PAGE {0} OF {1}",
appSettings.PageNumber + 1,
appSettings.StrokeCollections.Count);
appbarLastButton.IsEnabled = appSettings.PageNumber > 0;
appbarNextButton.IsEnabled =
appSettings.PageNumber < appSettings.StrokeCollections.Count - 1;
appbarDeleteButton.IsEnabled = (appSettings.StrokeCollections.Count > 1) ||
(appSettings.StrokeCollections[0].Count > 0);
}
}
}
本地存储功能封装的类
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using System.Windows.Ink;
using System.Windows.Media;
using System.Xml.Serialization;
namespace Jot
{
public class JotAppSettings
{
public JotAppSettings()
{
this.PageNumber = 0;
this.Foreground = (Color)Application.Current.Resources["PhoneForegroundColor"];
this.Background = (Color)Application.Current.Resources["PhoneBackgroundColor"];
this.StrokeWidth = 3;
}
// Public properties -- the actual application settins
public List<StrokeCollection> StrokeCollections { get; set; }
public int PageNumber { set; get; }
public Color Foreground { set; get; }
public Color Background { set; get; }
public int StrokeWidth { set; get; }
public static JotAppSettings Load()
{
JotAppSettings settings;
IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication();
if (iso.FileExists("settings.xml"))
{
IsolatedStorageFileStream stream = iso.OpenFile("settings.xml", FileMode.Open);
StreamReader reader = new StreamReader(stream);
XmlSerializer ser = new XmlSerializer(typeof(JotAppSettings));
settings = ser.Deserialize(reader) as JotAppSettings;
reader.Close();
}
else
{
// Create and initialize new JotAppSettings object
settings = new JotAppSettings();
settings.StrokeCollections = new List<StrokeCollection>();
settings.StrokeCollections.Add(new StrokeCollection());
}
iso.Dispose();
return settings;
}
public void Save()
{
IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream stream = iso.CreateFile("settings.xml");
StreamWriter writer = new StreamWriter(stream);
XmlSerializer ser = new XmlSerializer(typeof(JotAppSettings));
ser.Serialize(writer, this);
writer.Close();
iso.Dispose();
}
}
}
app.xaml
<Application
x:Class="Jot.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone">
<!--Application Resources-->
<Application.Resources>
</Application.Resources>
<Application.ApplicationLifetimeObjects>
<!--Required object that handles lifetime events for the application-->
<shell:PhoneApplicationService
Launching="Application_Launching" Closing="Application_Closing"
Activated="Application_Activated" Deactivated="Application_Deactivated"/>
</Application.ApplicationLifetimeObjects>
</Application>
app.xaml.cs
using System;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace Jot
{
public partial class App : Application
{
// Application Settings
public JotAppSettings AppSettings { set; get; }
// Easy access to the root frame
public PhoneApplicationFrame RootFrame { get; private set; }
// Constructor
public App()
{
// Global handler for uncaught exceptions.
// Note that exceptions thrown by ApplicationBarItem.Click will not get caught here.
UnhandledException += Application_UnhandledException;
// Standard Silverlight initialization
InitializeComponent();
// Phone-specific initialization
InitializePhoneApplication();
}
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
AppSettings = JotAppSettings.Load();
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
AppSettings = JotAppSettings.Load();
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
AppSettings.Save();
}
// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
AppSettings.Save();
}
// Code to execute if a navigation fails
void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// A navigation has failed; break into the debugger
System.Diagnostics.Debugger.Break();
}
}
// Code to execute on Unhandled Exceptions
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// An unhandled exception has occurred; break into the debugger
System.Diagnostics.Debugger.Break();
}
}
#region Phone application initialization
// Avoid double-initialization
private bool phoneApplicationInitialized = false;
// Do not add any additional code to this method
private void InitializePhoneApplication()
{
if (phoneApplicationInitialized)
return;
// Create the frame but don't set it as RootVisual yet; this allows the splash
// screen to remain active until the application is ready to render.
RootFrame = new PhoneApplicationFrame();
RootFrame.Navigated += CompleteInitializePhoneApplication;
// Handle navigation failures
RootFrame.NavigationFailed += RootFrame_NavigationFailed;
// Ensure we don't initialize again
phoneApplicationInitialized = true;
}
// Do not add any additional code to this method
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
// Set the root visual to allow the application to render
if (RootVisual != RootFrame)
RootVisual = RootFrame;
// Remove this handler since it is no longer needed
RootFrame.Navigated -= CompleteInitializePhoneApplication;
}
#endregion
}
}