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年10月13日 星期二

如何讓WPF控制項產生陰影效果

WPF中可以利用BitmapEffect(點陣圖效果), 使控制項呈現一些影像特效;如: Blur(模糊效果), Shadow(陰影效果), Bevel(斜面), Emboss(凹凸)和色彩光暈等....

在這篇文章中, 我們先來介紹會使控制像看起來比較有立體感的陰影效果.

由於BitmapEffect Visual 物件上的屬性, 因此, 我們可將BitmapEffect套用至任何視覺物件(例如 Button,Image, DrawingVisual 或 UIElement)上. 例如我們可以利用DropShadowBitmapEffect 物件建立 WPF 物件的各種下拉式陰影效果.

它提供下列屬性來讓你自訂陰影效果:
  • Color - 設定陰影的顏色
  • Opacity - 設定陰影的透明度, 設定範圍為0 至1, 預設值是1
  • ShadowDepth - 控制陰影的寬,有效值的範圍為 0 至 300。預設值為 5
  • Direction - 控制陰影的方向。請將這個屬性的方向值設定為介於 0 與 360 之間的度數. 0表示陰影會顯示在物件的正右方, 隨著Direction的增加, 陰影會逆時針方向移動
  • Softness - 控制陰影的柔和度或模糊,值 0.0 表示沒有模糊,而值 1.0 則表示全部模糊

下面的範例中, 我們將圖1透過DropShadowBitmapEffect 的效果, 產生出如圖片2的陰影效果:

XAML檔如下所示:

<Image Source="tulips.jpg"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="80, 50, 80, 50"
Name="image1"
Stretch="Fill" >
<Image.BitmapEffect>
<DropShadowBitmapEffect ShadowDepth="20"
Direction="300"
Color="Black"
Opacity="0.5"
Softness="0.25" />
</Image.BitmapEffect>
</Image>



[圖1]沒有陰影效果



[圖2]加上陰影效果



需要注意的地方:
WPF 點陣圖效果是在軟體模式中呈現. 而且是由UI執行緒執行, 而不是呈現的執行緒, 因此使用點陣圖效果會對效能有很大的影響(效能和元素的數量成正比), 所以最好在少量而且是靜態的內容才使用這些效果. 尤其在大型視覺物件上使用點陣圖效果, 或者以點陣圖效果的屬性建立動畫時, 效能會降低最多.


參考文章 :
MSDN - 點陣圖效果概觀
MSDN - 建立含陰影的文字

2009年9月23日 星期三

C# - Delgate(委派)和Event(事件)

首先, 先來釐清DelgateEvent的不同之處:

Delegate : 類似C語言中的函數指標(function pointer), 它包含了一個函数的原型(參數,返回值,呼叫方式)和該函数的内存位址. delegate是一種型別(class), 要宣告後才能使用.

例如, 我們在C語言中定義一個 callback函式, 可寫成:
typedef void (_stdcall* TouchFunc) (tOTM_Touch Touch);

等同於下面C#的寫法:
delegate void TouchEventFunc (tOTM_Touch Touch); 

Event: event是C#是關鍵字, 這個關鍵字是一個修飾詞, 類似const,static, 而event是用來修飾delegate. Event的實現需要透過delegate.

一個delegate被宣告為event(事件)後,除了在宣告事件的類別(Publisher)外,在其他的地方,只能利用+=-=操作(即subscrib或unsubscribe), 而delegate則沒有這樣的限制

若要訂閱某事件, 可利用“+=", 訂閱或註冊某事件指定的delegate(所代理的函数).

publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);


publisher.RaiseCustomEvent += HandleCustomEvent; //C# 2.0 內新增的語法


若要取消訂閱事件, 使用減法指派運算子 (-=) :
publisher.RaiseCustomEvent -= HandleCustomEvent;

event為物件提供一種方式, 可在某些事情發生時,通知其他類別或物件.

傳送或引發事件的類別稱為 Publisher(發行者) ,而接收或處理事件的類別則稱為Subscriber(訂閱者)

Method(方法)不一樣的地方:

Event是主動, 有事件發生就作通知,你可以訂閱或不訂閱
; 而Method是被動, 只能等待來呼叫,你不呼叫它就不會執行


下面為在C#中使用自訂Event的步驟:

在Publisher Class(傳送事件的類別)
  • 宣告事件的Delegate型別
public delegate void ChangedEventHandler(object sender, CustomEventArgs e);

  • 宣告事件
public event ChangedEventHandler Changed;

  • 當事件發生時,觸發此事件
if (Changed != null)
Changed(this, e);


在Subscriber Class(接收或處理事件的類別)
  • 訂閱Publisher Class的事件
DelegateEvent關聯, 並且註冊這個event的EventHandler函式

List.Changed += new ChangedEventHandler(ListChanged);

  • 自訂事件發生時要作的動作
public void ListChanged(object sender, CustomEventArgs e)
{
// Do something....
}


下面的sample code 為時間變更事件的範例, 出處為 Delegates and Events in C# / .NET

using System;
using System.Threading;

namespace EventSample
{
/* ======== Event Publisher ============ */
// Our subject -- it is this class that other classes
// will observe. This class publishes one event:
// SecondChange. The observers subscribe to that event.
public class Clock
{
// Private Fields holding the hour, minute and second
private int _hour;
private int _minute;
private int _second;

// The delegate named SecondChangeHandler,
// which will encapsulate
// any method that takes a clock object
// and a TimeInfoEventArgs
// object as the parameter and returns no value. It's the
// delegate the subscribers must implement.
public delegate void SecondChangeHandler(
object clock,
TimeInfoEventArgs timeInformation
);

// The event we publish
public event SecondChangeHandler SecondChange;

// The method which fires the Event
protected void OnSecondChange(
object clock,
TimeInfoEventArgs timeInformation
)
{
// Check if there are any Subscribers
if (SecondChange != null)
{
// Call the Event
SecondChange(clock, timeInformation);
}
}

// Set the clock running, it will raise an
// event for each new second
public void Run()
{
for (; ; )
{
// Sleep 1 Second
Thread.Sleep(1000);

// Get the current time
System.DateTime dt = System.DateTime.Now;

// If the second has changed
// notify the subscribers
if (dt.Second != _second)
{
// Create the TimeInfoEventArgs object
// to pass to the subscribers
TimeInfoEventArgs timeInformation =
new TimeInfoEventArgs(
dt.Hour, dt.Minute, dt.Second);

// If anyone has subscribed, notify them
OnSecondChange(this, timeInformation);
}

// update the state
_second = dt.Second;
_minute = dt.Minute;
_hour = dt.Hour;

}
}
}

// The class to hold the information about the event
// in this case it will hold only information
// available in the clock class, but could hold
// additional state information
public class TimeInfoEventArgs : EventArgs
{
public TimeInfoEventArgs(int hour, int minute, int second)
{
this.hour = hour;
this.minute = minute;
this.second = second;
}
public readonly int hour;
public readonly int minute;
public readonly int second;
}

/* ==== Event Subscribers ======= */

// An observer. DisplayClock subscribes to the
// clock's events. The job of DisplayClock is
// to display the current time
public class DisplayClock
{
// Given a clock, subscribe to
// its SecondChangeHandler event
public void Subscribe(Clock theClock)
{
theClock.SecondChange +=
new Clock.SecondChangeHandler(TimeHasChanged);
}

// The method that implements the
// delegated functionality
public void TimeHasChanged(
object theClock, TimeInfoEventArgs ti)
{
Console.WriteLine("Current Time: {0}:{1}:{2}",
ti.hour.ToString(),
ti.minute.ToString(),
ti.second.ToString());
}
}
/* ======= Test Application ========== */

// Test Application which implements the
// Clock Notifier - Subscriber Sample
public class Test
{
public static void Main()
{
// Create a new clock
Clock theClock = new Clock();

// Create the display and tell it to
// subscribe to the clock just created
DisplayClock dc = new DisplayClock();
dc.Subscribe(theClock);

// Get the clock started
theClock.Run();
}
}
}

下面圖是為執行結果:


參考文章:
Delegates and events
MSDN - Events Tutorial

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月27日 星期四

如何在C#中使用Unmanaged dll

C#中可以透過PInvoke(Platform Invocation Services), 呼叫 DLL 內實作的Unmanaged函式,
其流程可以參考 MSDN - PInvok的流程圖

Managed的程式中使用Unmanaged dll的方法有兩種:
  • 呼叫DLL匯出(dllexport)的函式
  • 呼叫COM元件上的介面
因為, 使用COM Interop的方式有點複雜, 所以本文章只介紹第一種方法, 若要使用匯出的 DLL 函式(C-Style function), 可分成下面幾個步驟:
  • 引用DllImport所在的名子空間: using System.Runtime.InteropServices;
  • 定義類別(例如class PlatformInvoke)包裝要被呼叫的DLL介面(也就是C/C++中的header檔)
  • 使用 DllImport來引用DLL的函式, 並用 staticextern 來標記函式. 此目的在告訴編譯器該方法與實體物件無關.
DllImport的格式語法如下:
//將user32.dll換成要使用的DLL檔 
[DllImport("user32.dll"), SetLastError = true]

//換成所需的方法及參數(需要將Unmanaged類型轉成Managed)
public static extern ReturnType FunctionName(type arg);
如下面範例所示:
using System;
using System.Runtime.InteropServices;

class WrapperTest
{
    [DllImport("msvcrt.dll")]
    public static extern int puts(string c);

    [DllImport("msvcrt.dll")] 
    publlic static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}


下面表格列出DllImport的屬性欄位

名稱說明
BestFitMapping將 Unicode 字元轉換成 ANSI 字元時, 設定true/false可啟用或停用自動對應行為
CallingConventionCallingConvention 列舉成員, 此欄位的預設值為 WinAPI
CharSet指示用在入口處中的字串參數, 如果未指定 CharSet, 則預設為 CharSet.Auto
EntryPoint指示要呼叫的 DLL 進入點(Entry Point) 的名稱或序數
ExactSpelling控制 DllImportAttribute CharSet 欄位是否會導致 Common Language Runtime 搜尋 Unmanaged DLL 以取得不是指定名稱的進入點名稱.
PreserveSig指定簽章是否為 Unmanaged 程式碼進入點的直接轉譯, 預設此欄位為 true
SetLasrError指示自屬性方法傳回之前, 被呼叫端是否呼叫 SetLastError Win32 API 函式。
ThrowOnUnmappableChar在無法對應的 Unicode 字元轉換為 ANSI "?" 字元時, 啟用或停用例外狀況的擲回

  • Managed 資料型別代替 Unmanaged 資料型別
Managed需要封裝Unmanaged的資料, 下面表格列出UnmanagedManaged的資料型態轉換:
Wtypes.h 中的 Unmanaged 型別Unmanaged C 語言型別Managed 類別名稱
HANALEvoid*System.IntPtr
BYTEunsigned charSystem.Byte
SHORTshortSystem.Int16
WORDunsigned shortSystem.UInt16
INTintSystem.Int32
UINTunsigned intSystem.UInt32
LONGlongSystem.Int32
BOOLlongSystem.Int32
DWORDunsigned longSystem.UInt32
ULONGunsigned longSystem.UInt32
CHARcharSystem.Char
LPSTRchar*System.String 或
System.Text.StringBuilder
LPCSTRConst char*System.String 或
System.Text.StringBuilder
LPWSTRwchar_t*System.String 或
System.Text.StringBuilder
LPCWSTRConst wchar_t*System.String 或
System.Text.StringBuilder
FLOATFloatSystem.Single
DOUBLEDoubleSystem.Double


如果要包裝一個資料結構,
在 C# 中可以使用 StructLayout屬性來表示的結構, 如下面範例說明:

CC#

struct tOTM_Point
{
int X;

int Y;

int R;

};


[Serializable, StructLayout(LayoutKind.Sequential)]
public struct tOTM_Point
{

public int X;

public int Y;

public int R;

}

如果在Struct中有Array的型態, 可以利用MarshalAs 屬性來表示, 如下所示

CC#

struct tOTM_Touch
{
int TouchType;

int TouchState;

tOTM_Point PointArray[2];

};


[Serializable, StructLayout(LayoutKind.Sequential)]
public struct tOTM_Touch
{

public tOTM_TouchType TouchType;

public tOTM_TouchState TouchState;


[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public tOTM_Point[] PointArray;

};



下面我以一個用C寫的OTMUT.dll為例子, 說明如何將Unmanaged dll轉成Managed程式碼:


C
#ifdef  OTMUT_EXPORTS
#define OTMUT_API __declspec(dllexport)
#else
#define OTMUT_API __declspec(dllimport)
#endif


struct tOTM_Point
{
int X;

int Y;

int R;
};

struct tOTM_Touch
{
int TouchType;

int TouchState;

tOTM_Point PointArray[2];
};

typedef void (_stdcall* TouchFunc) (tOTM_Touch Touch);

OTMUT_API void RegisterTouchFunc(TouchFunc Func);


C#
using System;
using System.Runtime.InteropServices;


namespace OTMUTInterop
{
[Serializable,
StructLayout(LayoutKind.Sequential)]
public struct tOTM_Point
{   
public int X;

public int Y;

public int R;

};

[Serializable,
StructLayout(LayoutKind.Sequential)]
public struct tOTM_Touch
{    
public tOTM_TouchType TouchType;

public tOTM_TouchState TouchState;

[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 2)]
public tOTM_Point[] PointArray;

};

delegate void
TouchFunc(tOTM_Touch touch);

class OTMUT
{       
[DllImport("OTMUT.dll",
CallingConvention =
CallingConvention.StdCall)]
public static extern void
RegisterTouchFunc(TouchFunc func);

}
}



參考文章 :
MSDN -
使用 Unmanaged DLL 函式
MSDN - 使用平台叫用封送處理資料

2009年8月23日 星期日

C#型別轉換(Casting) - as運算子

當你在C#中作型別轉換時, 可以利用強制轉換的方法, 例如:

double dx = 7.3;
int i = (int) dx;


下面的範例中, 我們在一個控制項(ListBox)的選擇項目事件變更時, 將object(sender)轉型為 ListView.


void ONListBoxSelectinChange(object sender, SelectionChangedEventArgs args)
{
// 將object轉型為ListView
ListView lstView = (ListView)sender;
int TotalCount = lstView.Items.Count;
}
不過當轉型失敗時, 會造成程式當掉, 因此你必須使用trycatch來處理例外錯誤(系統會丟出InvalidCastException例外狀況)

void ONListBoxSelectinChange(object sender, SelectionChangedEventArgs args)
{
try
{
ListView lstView = (ListView)sender;
int TotalCount = lstView.Items.Count;
}
catch (InvalidCastException e)
{
MessageBox.Show(e.Message);
}
}


[圖 1] 使用try catch丟出的例外錯誤


而在C#中提供一個比較簡單的方法, 就是使用as運算子, 這樣作會更有效率也很安全, 如果轉型失敗的話, 它會返回null, 因此, 你可以很容易的來作一些判斷, 如下面的範例所示:



void ONListBoxSelectinChange(object sender, SelectionChangedEventArgs args)
{
ListView lstView = sender as ListView;

if (lstView == null)
{
MessageBox.Show("Could not convert object to ListView");
}
else
{
int TotalCount = lstView.Items.Count;
}
}

參考資料:
HOW TO:使用 as 和 is 運算子進行安全轉型 (C# 程式設計手冊)
Effective c# Item3:運算子is或as優於强制轉型

2009年8月19日 星期三

WPF ListBox - Beginning


ListBox
可以讓使用者能夠從清單選取某個項目, 它是一個項目的控制項, 可以包含物件(object)的集合.

ListBox繼承自ItemControl, 它可以透過Items(型別為object collection)和 ItemsSource(有實踐IEnumerable的物件)這兩個屬性 , 來將任何物件放入ListBox, 如果放入的項目不是UIElement(例如:數值或string), 則會呼叫物件的ToString()函式, 然後將資料放入TextBlock中.

將物件放入
ListBox有兩種方法:
  • 利用Items屬性加入項目(Add)
下面的程式碼說明利用Items將任一物件放到ListBox中:

XAML檔如下:

<Window
x:Class="ListBoxTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ListBox" Height="300" Width="300">
<Grid>
<ListBox Margin="10" Name="listBox1">

<ListBoxItem Foreground="Aqua">Text</ListBoxItem>

<ListBoxItem>
<sys:DateTime>8/20/2009</sys:DateTime>
</ListBoxItem>

<ListBoxItem>
<Button Content="Button"
Background="LavenderBlush"
Foreground="Green"
Width="60"
Height="30"/>
</ListBoxItem>

<ListBoxItem>
<StackPanel>
<Rectangle Width="50"
Height="50"
Fill="Chartreuse"
Stroke="LightSlateGray"
StrokeThickness="3"/>
<TextBlock Text="Multiple objects in Panel"
FontSize="16"
Foreground="Red"/>
</StackPanel>
</ListBoxItem>
</ListBox>
</Grid>
</Window>

C#檔如下:

Grid grid = new Grid();
ListBox listBox1 = new ListBox();
listBox1.Margin = new Thickness(10);

grid.Children.Add(listBox1);

// Add a string object to a ListBox.
listBox1.Items.Add("Text");

// Add a DateTime object to a ListBox
DateTime dateTime = new DateTime(2009, 8, 20);
listBox1.Items.Add(dateTime);

// Add a button object to a ListBox
Button button1 = new Button();
button1.Content = "Button";
button1.Background = Brushes.LavenderBlush;
button1.Foreground = Brushes.Green;
button1.Width = 60;
button1.Height = 30;
listBox1.Items.Add(button1);

// Add a stackpanel that contains multpile objects to the ListBox.
StackPanel panel = new StackPanel();

Rectangle rect = new Rectangle();
rect.Width = 50;
rect.Height = 50;
rect.Fill = Brushes.Chartreuse;
rect.Stroke = Brushes.LightSlateGray;
rect.StrokeThickness = 3;

TextBlock txtBlock = new TextBlock();
txtBlock.Text = "Multiple objects in Panel";
txtBlock.FontSize = 16;
txtBlock.Foreground = Brushes.Red;

panel.Children.Add(rect);
panel.Children.Add(txtBlock);
listBox1.Items.Add(panel);

this.AddChild(grid);


[圖 1] 利用Items加物件

  • ItemsSource 繫結到一個物件的集合
你可以ItemsSource等於一個物件的集合, 或是將ItemsSource繫結(Binding)至集合物件,
注意: 當設定ItemsSource 屬性設定時, 會將 Items 集合設為唯讀和固定大小, 因此不能再對Items作新增或移除. 若將ItemsSource 屬性設為 null, 則會移除集合, 並將用法還原為 Items.

下面的範例說明, 如何利用ItemSourceBinding一個物件:
首先, 我們先建立一個
WeekData 的物件,如下:

 public class WeekData : ObservableCollection<string>
{
public WeekData()
{
Add("Sun");
Add("Mon");
Add("Tue");
Add("Wed");
Add("Thu");
Add("Fri");
Add("Sat");
}
}

ItemsSource 繫結至 WeekData:

Grid grid = new Grid();
ListBox listBox = new ListBox();
WeekData weekData = new WeekData();
Binding binding = new Binding();

binding.Source = weekData;
listBox.SetBinding(ListBox.ItemsSourceProperty, binding);
grid.Children.Add(listBox);
this.AddChild(grid);

如果您想要從 XAML中 繫結的CLR物件, 可以將物件定義為資源, 並指定x:Key,如下所示:

<Window.Resources>
<c:WeekData x:Key="weekData"/>
</Window.Resources>

<ListBox ItemsSource="{Binding Source={StaticResource weekData}}"/>


[圖 2] 利用ItemSource加入物件

參考文件:
MSDN-
Controls 內容模型概觀
MSDN- ListBox Class

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月14日 星期五

WPF 面板 - Grid

Grid是很常見的容器(container), 它可以用來作UI layout, 它的功能類似HTML中的Table,可定義表格的直行與橫列.

如果你想要製定一個3x3的表格(三直行+三橫列), 寫法如下面程式碼(XAML檔和C#)所示:


XAML:
<Grid ShowGridLines="True" Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>

<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
</Grid>

C#
public GridWindow()
{
InitializeComponent();

Grid grid1 = new Grid();
grid1.ShowGridLines = true;

for (int i = 0; i < 3; i++)
{
ColumnDefinition colDef = new ColumnDefinition();
RowDefinition rowDef = new RowDefinition();

grid1.ColumnDefinitions.Add(colDef);
grid1.RowDefinitions.Add(rowDef);
}

this.AddChild(grid1);
}

在此範例中, 我們可以讓ShowGridLines = true以顯示格子之間的線, 方便作Debug用.


[圖 1] 3X3 Grid


GridUnitType可用來設定尺寸單位的類型, 你可以搭配GridLength()來指定指定行和列的大小,其成員如下:


  • Pixel - 固定大小
example:
RowDefinition rowDef = new RowDefinition();
rowDef.Height = new GridLength(50, GridUnitType.Pixel)


  • Auto - 根據Content(內容)來設定大小
example:
RowDefinition rowDef = new RowDefinition();
rowDef.Height =
GridLength.Auto;


  • Star - 分攤剩下的空間
example:
RowDefinition rowDef = new RowDefinition();
rowDef.Height = new GridLength(33, GridUnitType.Star) ;



如果你想要把控制項塞到Grid裡面, 可以使用SetRowSetColumn方法, 指定row和column的index(從0開始), 來表式控制項擺放的位置.

example:
Grid.SetRow(control, 1);
Grid.SetColumn(control, 2);

你也可以使用SetRowSpanSetColumn的方法, 指定要跨列和行的個數.

example:
Grid.SetRowSpan(control, 3);
Grid.SetColumnSpan(control, 3);


下面的範例, 我們將示範如何將控制項放入
3X3的Grid 中, 然後適當調整Grid行和列的大小, 畫面如下所示:


[圖 2] Number Grid

Grid的第一列中, 我們會放一個內容為"Number Grid"TextBox,
TextBox會橫跨3行, 列的高度會設為Auto.

然後, 我們在Grid其他的cell中各填入一個數字Button, 其程式碼如下所示:


XAML
<Grid Name="grid1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>

<TextBox Name="txbBox"
FontSize="25"
Background="BurlyWood"
Foreground="DarkGreen"
Text="Number Grid"

HorizontalContentAlignment
="Center"

Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3">
</TextBox>

<Button
FontSize="30"
Name="button1"
Grid.Row="1"
Grid.Column="0">1
</Button>

<Button
FontSize="30"
Name="button2"
Grid.Row="1"
Grid.Column="1">2
</Button>

<Button
FontSize="30"
Name="button3"
Grid.Row="1"
Grid.Column="2">3
</Button>

<Button
FontSize="30"
Name="button4"
Grid.Row="2"
Grid.Column="0">4
</Button>

<Button
FontSize="30"
Name="button5"
Grid.Row="2"
Grid.Column="1">5
</Button>

<Button
FontSize="30"
Name="button6"
Grid.Row="2"
Grid.Column="2">6
</Button>
</Grid>

C#
public GridWindow()
{
InitializeComponent();

Grid grid1 = new Grid();

for (int i = 0; i < 3; i++)
{                 
RowDefinition rowDef =
new RowDefinition();

ColumnDefinition colDef =
new ColumnDefinition();

if( i == 0)
rowDef.Height = GridLength.Auto;
else
rowDef.Height =
new GridLength(1, GridUnitType.Star);

grid1.ColumnDefinitions.Add(colDef);
grid1.RowDefinitions.Add(rowDef);
}

this.AddChild(grid1);


// Add a TexBox
TextBox txbBox = new TextBox();
txbBox.FontSize = 25;
txbBox.Background = Brushes.BurlyWood;
txbBox.Foreground = Brushes.DarkGreen;
txbBox.Text = "Number Grid";

txbBox.HorizontalContentAlignment =
HorizontalAlignment.Center;

grid1.Children.Add(txbBox);
Grid.SetRow(txbBox, 0);
Grid.SetColumn(txbBox , 0);
Grid.SetColumnSpan(txbBox, 3);

// Add button1
Button button1 = new Button();
button1.FontSize = 30;
button1.Content = "1";
grid1.Children.Add(button1);
Grid.SetRow(button1, 1);
Grid.SetColumn(button1, 0);

// Add button2
Button button2 = new Button();
button2.FontSize = 30;
button2.Content = "2";
grid1.Children.Add(button2);
Grid.SetRow(button2, 1);
Grid.SetColumn(button2, 1);

// Add button3
Button button3 = new Button();
button3.FontSize = 30;
button3.Content = "3";
grid1.Children.Add(button3);
Grid.SetRow(button3, 1);
Grid.SetColumn(button3, 2);


// Add button4
Button button4 = new Button();
button4.FontSize = 30;
button4.Content = "4";
grid1.Children.Add(button4);
Grid.SetRow(button4, 2);
Grid.SetColumn(button4, 0);

// Add button5
Button button5 = new Button();
button5.FontSize = 30;
button5.Content = "5";
grid1.Children.Add(button5);
Grid.SetRow(button5, 2);
Grid.SetColumn(button5, 1);

// Add button6
Button button6 = new Button();
button6.FontSize = 30;
button6.Content = "6";
grid1.Children.Add(button6);
Grid.SetRow(button6, 2);
Grid.SetColumn(button6, 2);
}



參考資料:
MSDN- Grid 類別
WPF Tutorial - Grid

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] 翻轉



2009年8月9日 星期日

WPF 面板 - Layout Properties

UI設計時必須考慮控制項的位置,大小和排列(有不同的面板配置), 來作版面配置(Layout), 版面配置還必須根據視窗大小不同, 而作相對的改變, 因此, 我們先來了解版面配置的一些基本屬性:

這篇文章先說明子控制項常見的版面配置方式:

  • Width and Height - 控制項的寬和高, 如果設成Auto的話, 它們的值會變成Double.NaN, 這時後你就必須透過 ActualHeightActualWidth(唯讀屬性)來取得畫面配置後的高和寬. 注意: Width / Height的值是可以改變的, 但ActualHeight / ActualWidth是不行的.

  • MinWidth, MaxWidth, MinHeight, and MaxHeight -控制項的最小/最大寬和高; 注意: 如果你同時在元件中設定了Height, ActualHeight, MinHeightMaxHeight, 如果Height落在MinHeightMaxHeight 之間, Height的優先權為最高, 但是如果衝突發生時, 它們的優先順序如下(Width屬性一樣):MinHeight > MaxHeight > Height > ActualHeight
  • HorizontalAlignment and VerticalAlignment - 子項目相對於母容器的水平和垂直對齊(例如, Button在StackPanel中的水平或垂直對齊); HorizontalAlignment對齊的方式可分成: Left, Center, Right, Sretch(子控制項會延伸填滿母容器的配置版面空間). VerticalAlignment對齊方式為: Top, Center, Bottom ,Stretch; 注意:Stretch為預設值, 如果有設定WidthHeight, 則以WidthHeight值為優先, 下面為HorizontalAlignment 範例說明:

<Border Background="LightBlue"
BorderBrush="Black"
BorderThickness="2"
Padding="15">
<StackPanel Background="White"
HorizontalAlignment="Center"
VerticalAlignment="Top">
<TextBlock Margin="5,0,5,30"
FontSize="20"
Foreground="Red"
VerticalAlignment="Top"
HorizontalAlignment="Center">
HorizontalAlignment Sample
</TextBlock>
<Button Background="Gold"
HorizontalAlignment="Center"
Margin="10">Left</Button>
<Button Background="LightGreen"
HorizontalAlignment="Right">Right</Button>
<Button Background="Honeydew"
HorizontalAlignment="Center">Center</Button>
<Button Background="Ivory"
HorizontalAlignment="Stretch">Stretch</Button>
</StackPanel>
</Border>

結果如圖示:



[圖1] HorizontalAlignment Sample

  • Margin - 控制元素外的邊界的大小. 例如 Margin="0,10,5,25"(邊界順序為, 左, 上,右,下); 或是寫成Margin="10" 來設定成均等邊界,正確使用Margin可以精確控制項目的呈現位置.
  • Padding- 控制元素邊界內的大小
下面為MarignPadding的範例說明:

首先, 我們拉出一個StackPanel, 然後塞入四個button, XAML檔和畫面如下:

<StackPanel>
<Button Name="button1"
Background="AliceBlue">Button
</Button>

<Button Name="button2"
Background="Beige">Button 2
</Button>

<Button Name="button3"
Background="Pink">Button 3
</Button>

<Button Name="button4"
Background="LightGreen">Button 4
</Button>
</StackPanel>


[圖2] 沒有Margin和Padding

然後, 我們在每個button的屬性中加入 Margin = "10", 可以看到下面的畫面中, button和其他項目有對應的邊界.

<Button Name="button1"
Background="AliceBlue"
Height="40"
Margin="10">Button
</Button>


[圖3] Margin Sample

然後, 我們再把Margin屬性換為Padding = "10", 可以看到下面的畫面, 每個button content的內邊界都作增加.

<Button Name="button1"
Background="AliceBlue"
Height="40"
Padding="10">Button
</Button>



[圖4] Padding Sample
  • HorizontalContentAlignment and VerticalContentAlignment - 除了控制項可以作水平或垂直對齊外, 控制項的內容也有這樣的對齊方式, 請看下面範例和圖示.

<StackPanel>
<Button Name="button1"
Background="AliceBlue"
Height="40"
HorizontalContentAlignment="Left">Button
</Button>

<Button Name="button2"
Background="Beige"
Height="40"
HorizontalContentAlignment="Right">Button 2
</Button>

<Button Name="button3"
Background="Pink"
Height="40"
VerticalContentAlignment="Top">Button 3
</Button>

<Button Name="button4"
Background="LightGreen"
Height="40"
VerticalContentAlignment="Bottom">Button 4
</Button>
</StackPanel>


[圖5] HorizontalContentAlignment Sample