2010年5月28日 星期五

在C#中啟用及關閉多點觸控

在.net 4還沒有完全成熟之前,要開發win7的多點觸控程式
可以透過Windows 7 Multitouch .NET Interop Sample Library
請參考建立多點觸控應用程式

但是有時候並不希望另外使用其他的函式庫
這時候可以透過簡單的Win32 API達到同樣的效果

[DllImport("User32.dll", SetLastError = true)]
public static extern bool SetProp(
IntPtr hWnd, string lpString, IntPtr hData);

[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetProp(
IntPtr hWnd, string lpString);

const int TABLET_ENABLE_MULTITOUCHDATA = 0x01000000;
const string TPS = "MicrosoftTabletPenServiceProperty";

bool EnableMultiStylus()
{
/// get current window handle
Window w = Application.Current.MainWindow;
IntPtr h = new WindowInteropHelper(w).Handle;

/// get current tablet property
IntPtr tp = GetProp(h, TPS);

/// set with enabled multi-touch flag
return SetProp(h, TPS,
new IntPtr(tp.ToInt32() |
TABLET_ENABLE_MULTITOUCHDATA));
}

在這之後就可以直接使用stylus的事件來接收多個觸控點的事件
不過這時候如果註冊了滑鼠事件,就會發現滑鼠事件也被影響了
第二點下去及動作同時也會觸發滑鼠事件,進而造成困擾
(因為滑鼠事件沒辦法判斷哪個事件是由哪個點引發的)
所以記得在不需要使用的時候把多點觸控事件給關掉

bool DisableMultiStylus()
{
/// get current window handle
Window w = Application.Current.MainWindow;
IntPtr h = new WindowInteropHelper(w).Handle;

/// get current tablet property
IntPtr tp = GetProp(h, TPS);

/// set with disabled multi-touch flag
return SetProp(h, TPS,
new IntPtr(tp.ToInt32() &
~TABLET_ENABLE_MULTITOUCHDATA));
}

--
參考資料
建立多點觸控應用程式
SetProp Function
GetProp Function
IRealTimeStylus3::MultiTouchEnabled Property

2010年5月27日 星期四

在C#中做字串分析

C#中的字串本身就有一些簡單的分析,例如Contains或EndsWith
但是如果是比較複雜的判斷,例如是否全部由字母構成
就需要使用正規表式(Regular Expression)來判斷了

bool IsAlpha(string s)
{
return !Regex.IsMatch(s, "[^a-zA-Z]");
}

用正規表式來確認字串規則時,會在字串中進行搜索
所以如果要判斷一個字串是否滿足"全部都由字母構成"
實際上得去判斷是否沒有"其餘字符存在"

另外,正規表式也可以做成執行個體

bool IsAlpha(string s)
{
Regex r = new Regex("[^a-zA-Z]");

return !r.IsMatch(s);
}

注意這裏的執行個體中的規則是不可變的
所以如果有大量的運算都需要用到同樣的規則再這樣使用
否則用靜態的方式會比較有彈性

--
參考資料
Regular Expressions Usage in C#
正規表式
Regex 類別
Regex.IsMatch 方法

2010年5月25日 星期二

將WPF介面多國語言化(Localization)

要將WPF介面變成支援多國語言首先要注意一個問題
因為介面可能同時定義在XAML或C#檔案中
所以如果使用傳統的resx或是單純讀文字檔的方法是沒辦法直接做到的
(當然,如果把所有的介面文字都使用C#定義也是可行
不過這樣就不太符合WPF的精神了)

這時候需要加入新的資源字典(ResourceDictionary)

資源字典是一個XAML檔案,通常的命名會以CultureInfo的Name命名
(例如en-US.xaml或zh-TW.xaml)
這邊我們定義了一系列的字串,並賦予對應的索引(key)

<ResourceDictionary
...
xmlns:sys="clr-namespace:System;assembly=mscorlib">

<sys:String x:Key="OK">
OK
</sys:String>
<sys:String x:Key="Msg">
Msg
</sys:String>
</ResourceDictionary>

繁體中文的語系檔如下

<ResourceDictionary
...
xmlns:sys="clr-namespace:System;assembly=mscorlib">

<sys:String x:Key="OK">
確定
</sys:String>
<sys:String x:Key="Msg">
訊息
</sys:String>
</ResourceDictionary>

必須注意的是,此時此資源字典的BuildAction可以設定為Page或Content

設為Page的時候,此檔案會直接被包含到應用程式中
通常在預設語系會採用此種做法(確保至少有一種語言字典可以被找到)

而設成Content的時候,將會將此檔案獨立出來
此時,複製到輸出目錄(CopyToOutputDirectory)
需設成有更新時才複製(Copy if newer)或永遠複製(Copy always)

這種方式的好處是可以彈性的加入或移除某種語系的支援而不需要重新編譯
但是就可能會有很多檔案,在發佈的時候會造成一些麻煩
而且使用者可以任意的改動語系檔案,可能造成其他的錯誤

另外一種就是將非預設語系都包成另一個Dll(全部都設成page)
動態載入需要的語系,這樣一樣可以在不需要重新編譯程式的狀態下換語系檔
也可以保有語系檔的封閉性,只是在讀的時候可能會稍微麻煩一些
可以參考在WPF中引用被封裝的資源檔(影像)

在定義資源檔後,可以透過

private void LoadLanguage()
{
ResourceDictionary rd = null;

/// get current culture name
string lang = CultureInfo.CurrentCulture.Name;

try
{
rd = Application.LoadComponent(
new Uri(lang + " .xaml", UriKind.Relative))
as ResourceDictionary;
}
catch
{
}

if (rd != null)
{
/// find current lang xaml, replace default
if (Resources.MergedDictionaries.Count > 0)
{
Resources.MergedDictionaries.Clear();
}
Resources.MergedDictionaries.Add(rd);
}
}

將語系檔載入到應用程式的資源裡面

而在使用的地方則可以使用DynamicResource去讀取

<Button Content="{DynamicResource OK}"
Click="BtnClick" />

如果是在C#檔裡則是可以用FindResource去讀

private void BtnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(FindResources("Msg") as string);
}

此時會從物件本身開始向上查找,直到找到或Application層級為止

--
參考資料
關於讓WPF軟件界面支持全球化和本地化
WPF - Resource
FrameworkElement.FindResource 方法
資源概觀

2010年5月19日 星期三

在WPF中觸控事件的捕捉

在WPF中觸控事件(Stylus Event)傳遞
我們可以觀察到一般觸控事件的傳遞
但是如果我們在中間的矩形按下,卻移開到視窗之外再釋放
那Up的事件全部都不會發生了,這時候就需要用到捕捉了

捕捉有兩種,CaptureMouse以及CaptureStylus
經過實際測試,這兩個動作是完全一致的
(也許是為了讓寫觸控AP的RD不要更動太多)

先在PreviewMouseLeftButtonDown的地方做CaptureMouse

private void CMouseEH(object sender, RoutedEventArgs e)
{
(sender as FrameworkElement).CaptureMouse();
Console.WriteLine(
(sender as FrameworkElement).Name + " : " +
e.RoutedEvent.Name);
}

記得要在對應的地方(PreviewMouseLeftButtonUp)將滑鼠捕捉釋放掉

private void RMouseEH(object sender, RoutedEventArgs e)
{
(sender as FrameworkElement).ReleaseMouseCapture();
Console.WriteLine(
(sender as FrameworkElement).Name + " : " +
e.RoutedEvent.Name);
}

為了更清楚的看出捕捉的效果,我們將這兩個事件放在Grd上
得出的結果如下

Win : PreviewStylusDown
Grd : PreviewStylusDown
Rec : PreviewStylusDown
Rec : StylusDown
Grd : StylusDown
Win : StylusDown
Win : PreviewMouseLeftButtonDown
Grd : PreviewMouseLeftButtonDown
Rec : PreviewMouseLeftButtonDown
Grd : MouseLeftButtonDown
Win : MouseLeftButtonDown
Win : PreviewStylusUp
Grd : PreviewStylusUp
Grd : StylusUp
Win : StylusUp
Win : PreviewMouseLeftButtonUp
Grd : PreviewMouseLeftButtonUp
Rec : MouseLeftButtonUp
Grd : MouseLeftButtonUp
Win : MouseLeftButtonUp

而如果在PreviewStylus事件中捕捉Stylus的話
結果如下

Win : PreviewStylusDown
Grd : PreviewStylusDown
Rec : PreviewStylusDown
Grd : StylusDown
Win : StylusDown
Win : PreviewMouseLeftButtonDown
Grd : PreviewMouseLeftButtonDown
Grd : MouseLeftButtonDown
Win : MouseLeftButtonDown
Win : PreviewStylusUp
Grd : PreviewStylusUp
Rec : StylusUp
Grd : StylusUp
Win : StylusUp
Win : PreviewMouseLeftButtonUp
Grd : PreviewMouseLeftButtonUp
Rec : PreviewMouseLeftButtonUp
Rec : MouseLeftButtonUp
Grd : MouseLeftButtonUp
Win : MouseLeftButtonUp

從上面兩個結果中可以看出
不論是在滑鼠或是觸控事件中捕捉,都會將該事件繼續傳遞下去
但在該事件傳遞結束後到觸發釋放事件傳遞結束前
所有下層的物件都不再收到任何滑鼠或觸控的事件

在WPF中觸控事件(Stylus Event)傳遞

在單點觸控發生時,Windows會觸發Stylus及Mouse的事件
參考在WPF中滑鼠事件(Mouse Event)傳遞中使用的結構

<Window ... Name="Win">
<Grid Name="Grd">
<Rectangle Name="Rec" />
</Grid>
</Window>

因為我們這次觀察的重點是Mouse跟Stylus的交互作用
所以觀察的事件如下
事件
PreviewMouseLeftButtonDown
PreviewMouseLeftButtonUp
MouseLeftButtonDown
MouseLeftButtonUp
PreviewStylusDown
PreviewStylusUp
StylusDown
StylusUp

同樣,將觀察的事件都套用到相同的事件處理函式
依照在WPF中滑鼠事件(Mouse Event)傳遞所作的設定
出來的結果會是

Win : PreviewStylusDown
Grd : PreviewStylusDown
Rec : PreviewStylusDown
Rec : StylusDown
Grd : StylusDown
Win : StylusDown
Win : PreviewMouseLeftButtonDown
Grd : PreviewMouseLeftButtonDown
Rec : PreviewMouseLeftButtonDown
Rec : MouseLeftButtonDown
Grd : MouseLeftButtonDown
Win : MouseLeftButtonDown
Win : PreviewStylusUp
Grd : PreviewStylusUp
Rec : PreviewStylusUp
Rec : StylusUp
Grd : StylusUp
Win : StylusUp
Win : PreviewMouseLeftButtonUp
Grd : PreviewMouseLeftButtonUp
Rec : PreviewMouseLeftButtonUp
Rec : MouseLeftButtonUp
Grd : MouseLeftButtonUp
Win : MouseLeftButtonUp

從這例子中可以看出Stylus的事件會全部發生完才發生Mouse事件
而Stylus事件上下層間的傳遞也如同Mouse事件一般

2010年5月14日 星期五

在C#中將數值轉字串

在C#中要將一個數值表示成字串可以直接使用ToString的方法
不過在不同的地方可能需要不同的表示法
這時候就要使用ToString(String format)
透過format來定義字串的格式
比較常用的如下
格式說明
0固定位數數值字串使用,若該位數不存在則填0
#非固定位數數值字串使用,若該位數不存在則不會出現
E或e科學記號(指數)表示法
X或x16進位表示法,僅用於整數數值


void ToStringSample()
{
double n1 = 12.12;
Console.WriteLine(n1.ToString("000.000"));
/// 012.120
Console.WriteLine(n1.ToString("###.###"));
/// 12.12
Console.WriteLine(n1.ToString("E"));
/// 1.212000E+001

double n2 = 1234.1234;
Console.WriteLine(n2.ToString("000.000"));
/// 1234.123
Console.WriteLine(n2.ToString("###.###"));
/// 1234.123
Console.WriteLine(n2.ToString("E"));
/// 1.234123E+003

double n3 = 0.05;
Console.WriteLine(n3.ToString("000.000"));
/// 000.050
Console.WriteLine(n3.ToString("###.###"));
/// .05
Console.WriteLine(n3.ToString("E"));
/// 5.000000E-002

int n4 = 123456;
Console.WriteLine(n4.ToString("000.000"));
/// 123456.000
Console.WriteLine(n4.ToString("E"));
/// 123456
Console.WriteLine(n4.ToString("###.###"));
/// 1.234560E+005
Console.WriteLine(n4.ToString("X"));
/// 1E240
}

數值的表示法真的很多,真的需要的話可以直接從MSDN裡面找

--
參考資料
Double.ToString 方法 (String)
標準數值格式字串
自訂數值格式字串
Double.ToString如何指定小數點後的精度

在WPF中宣告非公開類別(internal class)

在製作動態連結函式庫(DLL)的時候
有時候會用到一些非公開的類別(在內部使用,卻無法被外部的專案參考)
在C#中僅需在宣告的時候將類別修飾詞改為internal即可
(或是不要用修飾詞,預設即為internal)

internal class Class1
{
}

但如果使用WPF的時候在DLL裡面使用了
UserControl或是像Window等使用XAML宣告的類別時
預設的partial class會是public的
(此時即使沒有使用修飾詞也會預設為public)

如果將cs檔的partial class宣告為internal partial class
編譯時會出現錯誤
原因是XAML會在編譯時自動使用修飾詞public
所以兩個部分類別的修飾不同而造成錯誤
此時就要用x:ClassModifier去設定編譯時使用的修飾詞

<UserControl
x:Class="test_wpf.UserControl1"
x:ClassModifier="internal"
...>
...
</UserControl>

此時cs檔對應的類別就要改成

namespace test_wpf
{
internal partial class UserControl1 : UserControl
{
}
}

或是將修飾詞去掉,就會使用XAML設定的修飾辭為預設

--
參考資料
WPF's Use of Partial Classes' Access Modifiers
x:ClassModifier 屬性