顯示具有 Multi-touch 標籤的文章。 顯示所有文章
顯示具有 Multi-touch 標籤的文章。 顯示所有文章

2010年12月14日 星期二

Touch UI 設計準則

Touch UI 的操作行為其實和傳統使用滑鼠或鍵盤的操作有些出入, 因此, 在開發觸控軟體時, 有部分的UI設計和操作都需重新設計或調整. 而好的UI設計通常會跟使用者經驗設計(User Experience Design )有很大的關聯. 在這篇文章中, 我會整理出Windows上開發觸控軟體需要注意的事項, 提供給大家參考.

1.    控制元件要夠大 – 因為觸控UI操作方式不是透過是滑鼠游標, 而是利用手指操控, 所以控制元件的大小不能太小, 否則會很難操作, 一般最好能符合人的手指大小, 微軟提出理想尺寸最少要在40*40(Pixel)以上


                                                             [圖1]- 觸控元件的合理大小



2.      控制元件之間的間距要夠大 如果觸控UI元件的間距太小,容易造成不小心觸控到其它控制元件的問題, 所以元件之間的距離要夠大, 微軟建議這範圍至少要 5 Pixel

                                                             [圖2] - 觸控元件的間距


3.  不能要求精準控制- 由於觸控不適合作細微的操作, 所以最好不要把需要精準控制的應用軟體設計成觸控UI, 例如繪圖或建模等軟體 

4.   適當地調整元件位置 – 有些觸控技術在某些區域容易出現誤判, 例如光學觸控的右上角邊緣, 就是容易發生誤判的區域, 所以在設計觸控UI時, 要避免將元件放在容易誤判的區域

5.   不要有太多手部點選的動作 – 一般會把觸控UI設計成一個動作就立即反應, 這樣可以使操作變得簡單直覺也可減少手部動作, 所以如圖3的下拉式選單的操作(需要一層一層的選擇), 就不適合利用觸控作操作

[圖3 ]- 下拉式選單

6. 不要有Hover功能 – 利用滑鼠控制的UI, 常會利用Hover作一些highlight或提示等功能, 但觸控UI上最好不要有hover行為, 因為不可能讓使用者把手指浮在半空中作操作. 如果真的要有這樣的行為, 我覺得可以在mouse down & mouse move時發生hover, 然後等mouse up時, 在發生click.


7.   使用直覺手勢 – 觸控UI在設計時, 最好能使用一些使用者常用和直覺的手勢.
例如下面是大家常見的手勢:
 
      平移:
                                                                      [圖4] - 平移手勢(出自MSDN)
 
     放大, 縮小 :
                                                           [圖5]- 縮放手勢 (出自MSDN)        

旋轉:
                                                          [圖6] - 旋轉手勢 (出自MSDN)


7.   加上物理行為 – 由於觸控是一種直覺和自然的動作, 所以可以在觸控操作中加上一些速度, 碰撞, 力量等物理反應, 讓你的touch介面, 更符合Natural UI的概念.下面的影片中, Demo一個我寫的Touch Scroll UI(它會根據手撥動的速度快慢, 而產生不同物理反應) 


 8. 動作要流暢 –觸控UI最好能提供順暢和反應靈敏的視覺反饋,例如使用旋轉手勢時,畫面的物件也要能同時作動, 不要讓使用者操作時有卡住的感覺

參考文章: Touch(MSDN)

2010年9月8日 星期三

Touch Game - Rubik's Cube

之前開發過不少touch相關的應用程式, 其中一個就是Rubik's Cube(中文叫魔術方塊), 關於它的介紹和玩法, 可參考: 維基百科-魔術方塊
Rubik's cube繪圖的部分是透過OpenGL API, 而觸控和手勢則取得透過Win7 Touch SDK.
此遊戲可以利用 "觸控+手勢" 來作操作, 方法如下所示:

Orienting the Cube Touch one finger and move it in the direction you want the cube to rotate

[圖 1] Orienting the cube


Rotating a Cube Section - Touch one finger and move it inside the section you wish to move, and then move it in the direction you want to rotate the section

[圖 2] Rotating a cube section


Zoom In - Touch two fingers and move them farther apart.

[圖 3] Zoom in


Zoom Out - Touch two fingers and move them closer apart

[圖 4] Zoom out


Resetting All Rotations - Touch two fingers and move them in the same direction, entire cube orientation can be reset

[圖 5] Resetting all rotations


Scrambling the Cube - Touch two fingers and turn them in a circle to scramble the cube

[圖 6] Scrambling the cube

2010年4月11日 星期日

Touch Game Framework

最近要開發一些Touch的遊戲, 所以構思了整個Game Framework.

下面的表格列出之前survey的一些game engine, 不過對我來說還是有一些技術性的問題,例如: touch功能的支援, UI的建立, 軟體收費, 支援平台等等.


[圖] XNA, Unity, Blender的比較表

所以最後構思的遊戲架構如下:


應用程式 - 選用Silverlight平台, 主要優點如下:
  • 豐富的圖形及使用者互動功能: Silverlight 包含了 WPF技術的子集,可大幅擴充瀏覽器中用來建立 UI 的項目. 另外, 它具有向量式的繪圖能力, 3D繪圖和製作動畫等等.
  • 影音播放 - 它會以資料流方式處理視訊和音訊. 它可以調整視訊品質, 以配合從行動裝置到桌面瀏覽器, 甚至是到 720p HDTV 視訊模式等任何裝置.
  • 支援Windows 7Touch功能 - Silverlight 在3.0版本後就支援Windows 7的觸控功能, 可參考這篇文章的簡介 - Silverlight 3.0 + Multi-touch
  • 跨瀏覽器、跨平台的支援 - 它可以在所有常見的 Web 瀏覽器 (包括 Microsoft Internet ExplorerMozilla Firefox Apple Safari) 以及 Microsoft Windows 和 Apple Mac OS 上執行Silverlight, 可參考 =>支援的平台和瀏覽器
物理引擎 - 選用Farseer Physics Engine. 它是一個在.NET平台上使用的物理引擎(它是open source), 主要用來開發Silverlight和XNA2D遊戲. 它提供很多物理相關的特性, 例如: 重力, 作用力, 力矩,碰撞,彈簧和關節..等等.


參考文章 :
Farseer Physics Engine 3.0

msdn - Silverlight

2010年2月24日 星期三

Silverlight 3.0 + Multi-touch

Silverlight是微軟最新的Web用戶介面技術,它能夠跨流覽器、跨平臺運行.
Silverlight 3.0 上也支援多點觸控功能 , 但目前功能還是相當有限, 只能取得觸控點的資料,
若要想要有手勢的功能的話, 需要透過觸控點另外作計算.


Silverlight的多點觸控架構如下:



從上圖可知, 要寫一個Silverlight的觸控程式, 必要元素要有: Touch Device + Windows 7 + IE 8

Windows Touch API 不一樣的地方, Silverlight 不需要註冊RegisterTouchWindow, 是以整個應用程式為範圍來註冊多點觸控事件, 而不是在UIElement 加入觸控事件(WPF).

撰寫Silverlight multi-touch application的流程如下:

(1)註冊多點觸控的處理常式 - TouchFrameReported(它是一個static function)
Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);

針對多點觸控所發生的事件,Silverlight預設將多點觸控事件提升為滑鼠事件, 若不希望升級成滑鼠事件, 可以呼叫 SuspendMousePromotionUntilTouchUp

(2)取得觸控點的資料
TouchPoint GetPrimaryTouchPoint(UIElement relativeTo) --> 取得主要觸控點資料
TouchPointCollection GetTouchPoints(UIElement relativeTo) --> 取得所有觸控點資料

傳入一個UIElement將回傳相對偏移的 Position 產生包含相對座標的 TouchPoint.
如果想要獲得觸控點的的絕對座標, 請將 relativeTo 指定為 null.

觸控點的其他資訊如下所示;

example code:
foreach (TouchPoint tp in e.GetTouchPoints(_canvas))
{
// 取得作業系統針對特定觸控裝置所提供的唯一 ID
tp.TouchDevice.Id;

// 取得當觸控點產生時,滑鼠指標所停留的最上層 UIElement 物件
tp.TouchDevice.DirectlyOver

// 取得觸控點的 X, Y 座標位置(相當於觸控區域的中心點位置)
tp.Position;

// 取得觸控點接觸區域(其區域為一個矩形)
tp.Size;
}


(3) 識別觸控的動作 -TouchAction
觸控點的動作可分成下列3種:


TouchAction.Down: 來自 TOUCHEVENTF_DOWN 訊息的觸控動作
TouchAction.Move: 來自 TOUCHEVENTF_MOVE 訊息的觸控動作

TouchAction.Up:來自 TOUCHEVENTF_UP 訊息的觸控動作


下圖為利用Silverlight開發的繪圖板. 因此在網頁上也可利用觸控作繪圖.




P.S: 若要使用Silverlight 來作為開發工具. 如果您沒有 Visual Web Developer,請使用 Web Platform Installer,如果您已有 Visual Studio, 請直接下載這個工具 ( Silverlight 3 SDK )

參考文章:
Silverlight 3 Multi-touch: The Basics
Silverlight 3 中的multi-Touch程式開發
Silverlight 3 Multi-Touch with Windows 7 and HP Touch Smart

2009年11月18日 星期三

WPF + Windows7 Multi-touch (Part 3)

目前在WPF中使用Multi-touch功能,必須透過P/Invoke的方式呼叫Win32 Touch API的功能(可參考這篇文章 : WPF + Windows7 Multi-touch (Part 2) ), 或是可以直接使用微軟提供的Windows 7 Multitouch .NET Interop Sample Library, 它提供 WPF WinForms 3.5 SP1 要開發Multi-Touch 程式所需的功能, 不過利用上述這些方法開發觸控功能時, 還是有稍嫌複雜, 也不是很直覺.

因此在WPF 4.0 已經將一些觸控功能放到UIElement, UIElement3DContentElement中,
Beta 1中, 將支援高階操作的觸控功能(Manipulation), 如下所示:
而在Beta 2 中, 除了原本高階操作的觸控功能(Manipulation)外, 還提供的Touch相關的event,如下所示:

下面我們將利用WPF 4.0 Beta2 Touch功能, 撰寫一個顯示目前觸控點的應用程式, 其步驟如下:

軟體和硬體需求
程式碼如下:

XAML檔的部份:


<Window x:Class="WPFTouchPoint.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="800" Width="800"
TouchDown="Window_TouchDown"
TouchMove="Window_TouchMove"
TouchUp="Window_TouchUp">
<Canvas Background="Black">
<Ellipse
Canvas.Left="0"
Canvas.Top="0"
Name="Touch1"
Stroke="Black"
Height="60"
Width="60"
Fill="LightGreen"
Visibility="Hidden">
<Ellipse.BitmapEffect>
<DropShadowBitmapEffect ShadowDepth="10"
Direction="270"
Color="White"
Opacity="0.5"
Softness="0.25"/>
</Ellipse.BitmapEffect>
</Ellipse>

<Ellipse
Canvas.Left="0"
Canvas.Top="0"
Name="Touch2"
Stroke="Black"
Height="60"
Width="60"
Fill="LightBlue"
Visibility="Hidden">
<Ellipse.BitmapEffect>
<dropshadowbitmapeffect shadowdepth="10" direction="270" color="White"
opacity="0.5" softness="0.25">
</dropshadowbitmapeffect>
</Ellipse>
</Canvas>
</Window>


C#檔的部分:


namespace WPFTouchPoint
{
///
/// Interaction logic for MainWindow.xaml
///

public partial class MainWindow : Window
{

private int Touch1ID = 0; // id for first touch contact
private int Touch2ID = 0; // id for second touch contact

public MainWindow()
{
InitializeComponent();
}


private void Window_TouchDown(object sender, TouchEventArgs e)
{
TouchPoint p = e.GetTouchPoint(this);

if (Touch1ID == 0)
{
// Show the touch point
Touch1.Visibility = Visibility.Visible;

Touch1ID = e.TouchDevice.Id;

// move the ellipse to the given location
Touch1.SetValue(Canvas.LeftProperty, p.Position.X - Touch1.Width / 2);
Touch1.SetValue(Canvas.TopProperty, p.Position.Y - Touch1.Height / 2);
}
else if (Touch2ID == 0)
{
Touch2.Visibility = Visibility.Visible;

Touch2ID = e.TouchDevice.Id;
// move the ellipse to the given location
Touch2.SetValue(Canvas.LeftProperty, p.Position.X - Touch2.Width / 2);
Touch2.SetValue(Canvas.TopProperty, p.Position.Y - Touch2.Height / 2);
}

}

private void Window_TouchMove(object sender, TouchEventArgs e)
{
TouchPoint p = e.GetTouchPoint(this);
// determine which contact this belongs to
if (Touch1ID == e.TouchDevice.Id)
{
Touch1.SetValue(Canvas.LeftProperty, p.Position.X - Touch1.Width / 2);
Touch1.SetValue(Canvas.TopProperty, p.Position.Y - Touch1.Height / 2);
}
else if (Touch2ID == e.TouchDevice.Id)
{
Touch2.SetValue(Canvas.LeftProperty, p.Position.X - Touch2.Width / 2);
Touch2.SetValue(Canvas.TopProperty, p.Position.Y - Touch2.Height / 2);
}
}

private void Window_TouchUp(object sender, TouchEventArgs e)
{
if (e.TouchDevice.Id == Touch1ID)
{
Touch1.Visibility = Visibility.Hidden;

Touch1ID = 0;
}
else if (e.TouchDevice.Id == Touch2ID)
{
Touch2.Visibility = Visibility.Hidden;

Touch2ID = 0;
}

}
}
}

顯示結果如下所示:


[圖 1] Multi-touch points in WPF4.0 Beta 2


不過目前這些觸控功能在Beta版還是有很多bug, 可參考這些文章測試出來的結果:
Multi-touch in WPF4.0 Beta2

WPF 4, Beta 2 expands multi-touch API but is buggy


參考文章:
Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 5 )

Windows using Multi-touch using WPF

Multi-touch in WPF4.0 and VS2010


Introduction to WPF 4 Multitouch


Walkthrough: Creating Your First Touch Application

What's New in WPF Version 4

2009年10月18日 星期日

WPF + Windows7 Multi-touch (Part 2)

WPF + Windows7 Multi-touch (Part 1) 這篇文章中, 我們可以透過 P/ Invoke 的方式來取得Windows 7 的手勢訊息, 並對物體作手勢操作.

不過當你用這個方法來取得觸控點的資訊時, 你會發現收不到WM_TOUCH 的訊息, 這個是因為WPF團隊還沒把這部分加進去(這篇
WPF Windows 7 Multi-touch.文章中, WPF團隊有回答此問題)

因此, 如果要在WPF程式中取得觸控點的資訊的話, 就必須採用下面步驟:

  • 新增一個"MicrosoftTabletPenServiceProperty"屬性到視窗上
             IntPtr hWnd = new WindowInteropHelper(this).Handle;
HwndSource src = HwndSource.FromHwnd(hWnd);
Win7TouchMethod.SetProp(src.Handle,
"MicrosoftTabletPenServiceProperty",
new IntPtr(0x01000000));

  • 新增StylusDown/StylusUp/StylusMove的事件, 來處理觸控的相關事件.

this.StylusDown += new StylusDownEventHandler(Window1_StylusDown);
this.StylusMove += new StylusEventHandler(Window1_StylusMove);
this.StylusUp += new StylusEventHandler(Window1_StylusUp);

下面的範例中, 我們會寫一個WPF的觸控應用程式, 當有觸控發生時, 畫面上會出現圓點(用來表示觸控點的位置)

source code如下:

<Window x:Class="WPFTouch.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="600" Width="800">
<Canvas>
<Ellipse
Canvas.Left="0"
Canvas.Top="0"
Name="Touch1"
Stroke="Black"
Height="30"
Width="30"
Fill="Blue"
Visibility="Hidden"/>

<Ellipse
Canvas.Left="0"
Canvas.Top="0"
Name="Touch2"
Stroke="Black"
Height="30"
Width="30"
Fill="Green"
Visibility="Hidden"/>
</Canvas>

</Window>


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using Win7TouchInterop;

namespace WPFTouch
{
public partial class Window1 : Window
{

private int Touch1ID = 0; // id for first touch contact
private int Touch2ID = 0; // id for second touch contact

public Window1()
{
InitializeComponent();

Loaded += new RoutedEventHandler(Window1_Loaded);

// Add StylusDown/StylusMove/StylusUp events to handle the touch related events
this.StylusDown += new StylusDownEventHandler(Window1_StylusDown);
this.StylusMove += new StylusEventHandler(Window1_StylusMove);
this.StylusUp += new StylusEventHandler(Window1_StylusUp);
}

void Window1_Loaded(object sender, RoutedEventArgs e)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;
HwndSource src = HwndSource.FromHwnd(hWnd);

Win7TouchMethod.SetProp(src.Handle,
"MicrosoftTabletPenServiceProperty",
new IntPtr(0x01000000));

}


void Window1_StylusDown(object sender, StylusDownEventArgs e)
{
Point p = e.GetPosition(this); // get the location for this contact


if (Touch1ID == 0)
{
// Show the touch point
Touch1.Visibility = Visibility.Visible;

Touch1ID = e.StylusDevice.Id;

// move the ellipse to the given location
Touch1.SetValue(Canvas.LeftProperty, p.X - Touch1.Width / 2);
Touch1.SetValue(Canvas.TopProperty, p.Y - Touch1.Height / 2);
}
else if (Touch2ID == 0)
{
Touch2.Visibility = Visibility.Visible;

Touch2ID = e.StylusDevice.Id;
// move the ellipse to the given location
Touch2.SetValue(Canvas.LeftProperty, p.X - Touch2.Width / 2);
Touch2.SetValue(Canvas.TopProperty, p.Y - Touch2.Height / 2);
}
}

void Window1_StylusMove(object sender, StylusEventArgs e)
{
Point p = e.GetPosition(this);
// determine which contact this belongs to
if (Touch1ID == e.StylusDevice.Id)
{
Touch1.SetValue(Canvas.LeftProperty, p.X - Touch1.Width / 2);
Touch1.SetValue(Canvas.TopProperty, p.Y - Touch1.Height / 2);
}
else if (Touch2ID == e.StylusDevice.Id)
{
Touch2.SetValue(Canvas.LeftProperty, p.X - Touch2.Width / 2);
Touch2.SetValue(Canvas.TopProperty, p.Y - Touch2.Height / 2);
}
}

void Window1_StylusUp(object sender, StylusEventArgs e)
{
if (e.StylusDevice.Id == Touch1ID)
{
Touch1.Visibility = Visibility.Hidden;

Touch1ID = 0;
}
else if (e.StylusDevice.Id == Touch2ID)
{
Touch2.Visibility = Visibility.Hidden;

Touch2ID = 0;
}
}
}
}


執行畫面如下:


[圖 1] WPF Touch Application

參考文章:
Windows 7 Multi-touch using WPF

2009年9月2日 星期三

WPF + Windows7 Multi-touch(Part 1)

Win32的程式中, 可以利用WndProc來接收WM_TOUCH / WM_GESTURE 訊息, 來得到觸控點和手勢的資訊(可參考這兩篇文章Get Touch Point , Windows 7 Gesture Sample), 可是在WPF中沒有WndProc函式可以呼叫, 因此, 下面的文章將介紹如何在WPF Window上使用Windows 7 Multi-Touch function.

在WPF Window使用Windows Touch SDK的步驟如下:

Step 1: 利用PInvoke的機制呼叫user32.dll中定義Touch / Gesture的資料型態, 結構和方法
此方法可參考這篇文章
: 如何在C#中使用Unmanaged dll

例如;我們要使用Winuser.hRegisterTouchWindow函式, 其轉換如下:

C
BOOL WINAPI RegisterTouchWindow(
__in  HWND hWnd,
__in  ULONG ulFlags
);


C#
[Flags, Serializable]
public enum RegisterTouchFlags
{
TWF_NONE = 0x00000000,

TWF_FINETOUCH = 0x00000001
}
[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterTouchWindow(IntPtr hwnd,
[MarshalAs(UnmanagedType.U4)] RegisterTouchFlags flags);



Step 2: 在WPF Window中利用HwndSource物件來取得的Windows訊息


由於WPF Window不能像Win32 Window, 可利用WndProc函式處理WM_TOUCH / WM_GESTURE訊息,但WPF Window可以用HwndSource來裝載最上層HWND的所有內容.


其方法如下;


(1) 利用 System.Windows.Interop.WindowInteropHelper類別來取得任何WPF Window的HWND


example:

using System.Windows.Interop;

IntPtr _hwnd = new WindowInteropHelper(this).Handle;


(2) 利用HWND取得相關的HwndSource物件(用HwndSource.FromHwnd)

example:
HwndSource src = HwndSource.FromHwnd(_hwnd);

(3) 利用HwndSourceAddHook方法, 即可攔截視窗訊息


example:

src.AddHook(WndProc);
private IntPtr WndProc( IntPtr hwnd,
int msg,
IntPtr wParam,
IntPtr lParam,
ref bool handled)
{
switch (msg)
{
case TouchMessage.WM_GESTURE:
OnGesture(lParam);
// 記得將handle設為true,告訴系統你已經處理該訊息
handled = true;
break;

default:
break;
}
return IntPtr.Zero;
}



看完上面的介紹, 我們就來寫一個 WPF + Win7 Gesture 的應用程式, 我們會在WPF Window上繪製一個rectangle, 並透過一些手勢操作來對此rectangle作放大,縮小,平移,旋轉和改變顏色(twp finger tap)

其步驟如下:


  • 首先,我們會寫一個Win7TouchInterop.cs檔, 目的要將Winuser.h中的 touch和gesture資料作包裝
  • 在WPF的程式碼中, 使用Win7TouchInterop的命名空間 using Win7TouchInterop;
  • 利用HwndSource物件來取得的Windows訊息
  • 處理手勢訊息

程式碼如下所示;


XAML檔
:
<Window x:Class="WPFGestureMessage.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Touch SDK+WPF" Height="600" Width="800">
<Grid>

<Grid.Resources>
<Storyboard x:Key="_twoFingerTap">
<ColorAnimation
Storyboard.TargetName="rect"
Storyboard.TargetProperty=
"(Rectangle.Fill).(SolidColorBrush.Color)"
From="SkyBlue"
To="YellowGreen"
Duration="00:00:00.5"
AutoReverse="true"
FillBehavior="HoldEnd" />
</Storyboard>
</Grid.Resources>

<Rectangle  x:Name="rect"
RadiusX="5"
RadiusY="5"
Width="300"
Height="200"
Stroke="DarkBlue"
StrokeThickness="5"
Fill="YellowGreen"
RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform
x:Name="_scaleTransform"
ScaleX="1"
ScaleY="1" />
<RotateTransform
x:Name="_rotateTransform"
Angle="0" />
<TranslateTransform
x:Name="_translateTransform"
X="0"
Y="0" />
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>

</Grid>
</Window>


C#檔
:
using System;
using System.Windows;

using Win7TouchInterop;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Windows.Media.Animation;

namespace WPFGestureMessage
{
/// 
/// Interaction logic for Window1.xaml
/// 
public partial class Window1 : Window
{
private IntPtr _hwnd;
const double scaleSensitivityMagicFactor = 200.0;
const double rotateSensitivityMagicFactor = 25.0;

private long _beginScale;

private bool captureAngle;
private double _beginAngle;

private Point firstPoint;

Storyboard _twoFingerTap;

public Window1()
{
InitializeComponent();
Loaded += new RoutedEventHandler(Window1_Loaded);
_twoFingerTap = rect.FindResource("_twoFingerTap") as Storyboard;

}

void Window1_Loaded(object sender, RoutedEventArgs e)
{
_hwnd = new WindowInteropHelper(this).Handle;
HwndSource src = HwndSource.FromHwnd(_hwnd);
if (src != null)
src.AddHook(WndProc);

// Setup the gestures
var gConfig = new[]
{
new GESTURECONFIG
{
dwID = GestureID.GID_ALL,

dwWant = WantGestures.GC_ANY
}   
};

Win7TouchMethod.SetGestureConfig(_hwnd,
0,
gConfig.Length,
gConfig,
Marshal.SizeOf(gConfig[0]));
}


private Point ConvertPoint(POINTS pts)
{
var pt = new POINT { X = pts.X, Y = pts.Y };
Win7TouchMethod.ScreenToClient(_hwnd, ref pt);
return new Point(pt.X, pt.Y);
}


private IntPtr WndProc(IntPtr hwnd,
int msg,
IntPtr wParam,
IntPtr lParam,
ref bool handled)
{

switch (msg)
{
case Win7TouchMessages.WM_GESTURE:
OnGesture(lParam);
handled = true;
break;
default:
break;
}


return IntPtr.Zero;
}

private void OnGesture(IntPtr gestureMsg)
{
GESTUREINFO gesture = new GESTUREINFO();
gesture.cbSize = Marshal.SizeOf(gesture);
if (Win7TouchMethod.GetGestureInfo(gestureMsg, out gesture))
{
switch (gesture.dwID)
{
case GestureID.GID_PAN:
OnPan(gesture.dwInstanceID,
ConvertPoint(gesture.ptsLocation),
gesture.dwFlags);
break;
case GestureID.GID_ROTATE:
OnRotate(gesture.ullArguments,
gesture.dwFlags);
break;
case GestureID.GID_TWOFINGERTAP:
_twoFingerTap.Stop();
_twoFingerTap.Begin();
break;
case GestureID.GID_ZOOM:
OnZoom(ConvertPoint(gesture.ptsLocation),
gesture.ullArguments,
gesture.dwFlags);
break;
default:
break;
}
}
Win7TouchMethod.CloseGestureInfoHandle(gestureMsg);
}



private void OnZoom(Point ptCenter,
long scale,
GestureFlags gestureFlags)
{

if ((gestureFlags & GestureFlags.GF_BEGIN) > 0)
{
_beginScale = scale;
}
else
{
double delta = scale - _beginScale;
_beginScale = scale;

double scaleX = (delta / scaleSensitivityMagicFactor);
double scaleY = (delta / scaleSensitivityMagicFactor);

_scaleTransform.ScaleX += scaleX;
_scaleTransform.ScaleY += scaleY;

}
}


private void OnPan(uint id,
Point point,
GestureFlags gestureFlags)
{

if ((gestureFlags & GestureFlags.GF_BEGIN) > 0)
{
firstPoint = point;
}
else
{
Point curPoint = point;
double xDelta = curPoint.X - firstPoint.X;
double yDelta = curPoint.Y - firstPoint.Y;

_translateTransform.X += xDelta;
_translateTransform.Y += yDelta;

firstPoint = curPoint;
}
}

private void OnRotate(double radians,
GestureFlags gestureFlags)
{

double angle = Win7TouchMethod.ROTATE_ANGLE_FROM_ARGUMENT(radians) * (180.0 / Math.PI);
if ((gestureFlags & GestureFlags.GF_BEGIN) > 0)
{
captureAngle = true;
}
else
{
if (captureAngle)
{
_beginAngle = angle;
captureAngle = false;
}
else
{
double delta = angle - _beginAngle;

_rotateTransform.Angle += 0 - (delta / rotateSensitivityMagicFactor);

if ((gestureFlags & GestureFlags.GF_END) > 0)
{
captureAngle = true;
_beginAngle = 0;
}
}
}//end else
}//end
}
}


成果如下圖所示:



[圖 1] 初始畫面


[圖 2] 縮小



[圖 3] 放大


[圖 3] 平移+inertia (因為有開啟GC_PAN_INERIA)


[圖 5] 旋轉


[圖 6] Two finger tap -->改變顏色

參考文章:
Windows 7 Multi-touch Using WPF
Windows 7: Experimenting with Multi-Touch on Windows 7
MSDN -WindowInteropHelper 類別

2009年8月16日 星期日

如何在應用程式中分辨Mouse或Touch事件?

Windows 7的視窗應程式中, 不論你用mouse或是touch作click的動作, 應用程式都會收到WM_LBUTTONDOWNWM_LBUTTONUP事件. 如下面的Win32視窗程式所示,當收到滑鼠左鍵按下/放開的訊息時, 會跳出一個訊息視窗.

case WM_LBUTTONDOWN:
{

MessageBoxA(NULL, "Mouse left button down",0,0);    

};
break;

case WM_LBUTTONUP:
{

MessageBoxA(NULL, "Mouse left button up",0,0);

};
break;



                                                   [圖 1]滑鼠左鍵按下/放開



                                                       [圖 2]Touch下去/起來

從上面的圖示中看到, 不論你是用mousetouch作click, 都會收到滑鼠左鍵按下/放開的事件; 但是開發觸控應用程式時, 可能需要分辨出使用者是利用滑鼠作點擊(click), 還是利用手指作觸碰, 來作不同的設定. 因此, 我們可以在收到WM_LBUTTONDOWNWM_LBUTTONUP 時, 呼叫GetMessageExtraInfo()來判斷滑鼠事件是來自touch還是mouse, 程式碼如下所示:

1. 定義滑鼠事件來自touch的訊息, 如下所示:
#define MOUSEEVENTF_FORMTOUCH 0xFF515700


2. 利用GetMessageExtraInfo函式作判斷, 如下所示:

case WM_LBUTTONDOWN:
{
if((GetMessageExtraInfo() & MOUSEEVENTF_FORMTOUCH)
== MOUSEEVENTF_FORMTOUCH)
{
MessageBoxA(NULL, "Click was generated by Touch",0,0);
}
else
{
MessageBoxA(NULL, "Click was generated by Mouse Click",0,0);
}

};
break;

case WM_LBUTTONUP:
{
if((GetMessageExtraInfo() & MOUSEEVENTF_FORMTOUCH)
== MOUSEEVENTF_FORMTOUCH)
{
MessageBoxA(NULL, "Click was generated by Touch",0,0);
}
else
{
MessageBoxA(NULL, "Click was generated by Mouse Click",0,0);
}
};
break;



結果如下面圖示:


                                                       [圖 3]滑鼠左鍵按下/放開



                                                          [圖 4] Touch下去/起來

2009年8月10日 星期一

Multi-Touch Application - Photo Viewer

之前的文章有提到 WPF提供2D Transform的功能, 而這樣的功能很適合發展應用程式的手勢(Gesture)操控, 下面, 我就舉一個簡單的圖片瀏覽應用程式, 透過一些手勢操作, 來對圖片進行放大, 縮小, 旋轉, 翻轉和平移.

環境設定:
以下是我的環境設定:

  • 觸控裝置 - Quanta Optical Touch Monitor
  • Touch API - OTMUT(Optical Touch Monitor Utility Toolkit), 這隻Touch API是我們自行撰寫的dll, 主要用來取得觸控點和手勢的資料, 功能類似Windows 7 Touch SDK
當然, 你也可以自行使用一台具有觸控功能的電腦或螢幕, 然後搭配Windows 7 Touch SDK 來開發此程式.

視窗和控制項的設定:

開啟一個WPF專案, 然後從Toolbox中將一個Image的控制項拉到視窗中, 如下圖所示:


[圖 1] 新增Image control

在專案中新增一個Image的資料夾,並把要顯示圖片放進去, 然後在XAML檔中設定圖片的路徑, 如下所示:


[圖2] 設定圖片路徑
<Grid Background="Beige">
<Image Margin="150"
Name="image1"
Stretch="Fill"
Source="Image\Koala.jpg">

</Image>
</Grid>


設定Image 2D Transform:

我們在XAML檔中, 先設定ImageRotaeTransform, ScaleTransformTranslateTransform, 程式碼如下所示:

<Grid Background="Beige">
<Image Margin="150" Name="image1"
Stretch="Fill"
Source="Image\Koala.jpg"
RenderTransformOrigin=".5,.5">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="_rotate" Angle="0">
</RotateTransform>

<ScaleTransform x:Name="_scale"
ScaleX="1"
ScaleY="1">    
</ScaleTransform>

<TranslateTransform x:Name="_translate"
X="0"
Y="0">                        
</TranslateTransform>
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>



Gesture Function:
我們在.cs檔中加入OTMUT 的 callback function, 當有偵測到Zoom In/Out, Rotate和 Pan手勢作用時, 則對圖片作transform, 如下所示:
void GestureFunction(tOTM_Gesture gesture)
{
double k;

switch (gesture.GestureType)
{
// Detect Pan gesture
case (int)tOTM_GestureType.OTM_GT_PAN:
{
// Display gesture type in console mode
System.Console.WriteLine("Pan");


if (gesture.GestureState == (int)tOTM_GestureState.OTM_GS_BEGIN)
{
g_FirstX = gesture.ActivePoint.X;
g_FirstY = gesture.ActivePoint.Y;
}
else
{
g_SecondX = gesture.ActivePoint.X;
g_SecondY = gesture.ActivePoint.Y;


// translate the image
_translate.X += g_SecondX - g_FirstX;
_translate.Y += g_SecondY - g_FirstY;


// We have to copy second point into first one to prepare
// for next pan message.
g_FirstX = g_SecondX;
g_FirstY = g_SecondY;
}

};
break;

// Detect zoom gesture
case (int)tOTM_GestureType.OTM_GT_ZOOM:
{
// Display gesture type in console mode
System.Console.WriteLine("Zoom");

if (gesture.GestureState == (int)tOTM_GestureState.OTM_GS_BEGIN)
{
g_bBeginZoom = true;
}
else if (gesture.GestureState == (int)tOTM_GestureState.OTM_GS_INERTIA)
{
if (g_bBeginZoom)
{
g_fGEArguments = gesture.Argument1;
g_bBeginZoom = false;
}
else
{
k = (double)gesture.Argument1 / g_fGEArguments;

// Zoom in/out the image
_scale.ScaleX *= (float)k;
_scale.ScaleY *= (float)k;
}
g_fGEArguments = gesture.Argument1;
}

};
break;

// Detect rotate gesture
case (int)tOTM_GestureType.OTM_GT_ROTATE:
{

// Display gesture type in console mode
System.Console.WriteLine("Rotate");

if (gesture.GestureState == (int)tOTM_GestureState.OTM_GS_BEGIN)
{
g_bBeginRotate = true;
}
else
{
if (g_bBeginRotate)
{
_rotate.Angle = gesture.Argument1;
}
else
{
k = (float)(gesture.Argument1 - g_fGEArguments);

// Rotate the image
_rotate.Angle += k;

}
g_fGEArguments = gesture.Argument1;
}
};
break;

// Detect the Press and Tap
case (int)tOTM_GestureType.OTM_GT_PRESS_TAP:
{
// Display gesture type in console mode
System.Console.WriteLine("Press and Tap");

// Flip the image
_scale.ScaleX *= -1;
};
break;

default:
break;
}
}

下面為程式的demo結果


[圖3] 初始畫面


[圖4] 縮小



[圖5] 放大



[圖6] 旋轉


[圖7] 平移



[圖7] 翻轉