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(事件)

首先, 先來釐清Delgate和Event的不同之處:

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的事件
將Delegate與Event關聯, 並且註冊這個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