2015年3月23日 星期一

sTouch Board is now available on Google Play!

We launched an interactive app in Google Play
https://play.google.com/store/apps/details?id=sTouch.Android.Client&hl=en-us


sTouch Board- Getting Started
                                                 

                                                               

sTouh Board Client allows users to turn their Android tablet/phone into an interactive whiteboard. With the App you can view shared content, control your presentation device, and annotate over any content.

How To Use:
1. Install sTouch Board Windows version: The sTouch Board Client App requires the Windows version of sTouch Board software installed on your Windows device. You can download from here: http://www.stouchtech.com/#!download/c12ug

2. Network Environment: Make sure sTouch Board and sTouch Board Client are on the same network.

3. Connect to sTouch Board : Select the room you from pairing list, and click JOIN.
Please check software user manual for more detail:
http://media.wix.com/ugd/4ee221_94f3d4c3b321492eabe396860db8c6cc.pdf

We also provide interactive solutions for School Education/Office Presentation/Home Entertainment, please see the following components:

1. sTouch TV: It is the most cost effective solution for large touch screen in the world. Easy to set up! Easy to use! You can draw, write and play on the TV.

2. sTouch Whiteboard : It is a low-cost solution that simply turns a traditional whiteboard or projector surface into an interactive whiteboard.

3. sTouch Board: It is image-based, simple, intuitive whiteboard App available for Windows 8/Windows 7 system.
Features include:
- Drawing Tools - Color pens, highlighter pen, eraser, etc
- Present - Import document files or images into the app to make great presentation
- Sharing and Interactive - Broadcast your screen content and interact with remote devices
- Record - Ability to record and capture your screen
- Annotate - Annotate directly on the desktop screen

For more details, please refer the link: http://www.stouchtech.com/

2011年2月9日 星期三

事件的深入分析(function pointer, delegate, event, EventHandler)

Function pointer(函數指標)
C/C++ 若要設計事件(Callback function), 可利用function pointer來完成, 其詳細作法可參考Callback Function


Delegate(委託)
delegateC#特殊的類別, 它可以封裝一個函數, 但與一般類別不同之處,delegate擁有一個簽名(可以代表函式的引數類型和回傳值類型). delegate實現的功能和C/C++中的函數指標十分相似.

C/C++ :
    void (*UpdateCallback)(double time);
 
C# :
    delegate void UpateDelegate(double time);

雖然delegate類似於函式指標(function pointer), 不過還是有很多不同之處, 例如delegate具有物件導向和型別安全等特性
.

delegate用法
delegate的使用步驟如下:

1. 宣告委派型別 
宣告一個delegate的型別, 每個delegate型別封裝了函數的回傳類型,引數的數目, 和引數的類型, 如下面陳述式所示:
delegate void MessageDelegate(string str);

2. 定義一個符合委派型別的簽名方法, 可為 instance method 或 static method
public static void HelloFun(string str)
{
   Console.WriteLine("  Hello, {0}!", str);
}
public static void GoodbyeFun (string str)
{
   Console.WriteLine("  Goodbye, {0}!", str);
}

3. 建立委派物件, 並指定委派方法
delegate宣告後, 必須再將它實體化才能使用(和一般類別的使用方式一樣, 需先將物件實體化), 並指定所代表的函式, 但不需傳入引數, 如下面範例所示
static void Main(string[] args)
{
   MessageDelegate msDelegate = new MessageDelegate(HelloFun);
}

delegate同時也支援多點傳送(+=, -=), 因為繼承自System.MulticastDelegate類別, 你可以使用+= 運算子將多個方法綁定到同一個delegate, 當呼叫這個delegate的時候, 將依次呼叫其所綁定的方法. 當然你也可以使用 - = 運算子 將某個委派參考從委派清單中移除.

MessageDelegate msDelegate;
 msDelegate = new MessageDelegate(HelloFun);
 msDelegate += new MessageDelegate(GoodbyeFun);
 msDelegate -= new MessageDelegate(HelloFun);

4. 透過委派物件執行委派方法
如下所示:
msDelegate("Peter"); //等同msDelegate.invoke("Peter");


event
event的出現,是要避免使用delegate可能會破壞物件封裝性的問題,主要有下面兩點:
1.    delegate 可以隨意進行賦值操作, 若不小心使用將會移除所有委派
2.    為了讓delegate可以綁定特定的方法, 宣告時必須定義成public屬性, 但也因此破壞掉物件的封裝性, 因為類別的內外都可以呼叫delegate

考慮下面例子:
public class ButtonControl
{
        public delegate void ClickDelgate(object sender, EventArgs e);        
        public ClickDelgate ClickEvent; 
        public void OnFire(EventArgs e)
        {
            ClickEvent(this, e);
        }
}

static public void TexboxUpdate(object sender, EventArgs e)
{
     Console.WriteLine("Texbox update by button click");
 }

 static public void LabelUpdate(object sender, EventArgs e)
 {
      Console.WriteLine("Label update by button click");
 }

static void Main(string[] args)
{
ButtonControl btnCtrl = new ButtonControl();
      btnCtrl.ClickEvent = new ButtonControl.ClickDelgate(TexboxUpdate);
      btnCtrl.ClickEvent += new ButtonControl.ClickDelgate(LabelUpdate);
btnCtrl.ClickEvent = null;  //這將會移除所有委派
      btnCtrl.ClickEvent(null, new EventArgs()); //不應該讓使用者可以呼叫
}


而就觀察者模式來說, 也應避免下面這些情況:
  • 對於客戶端(觀察者), 應該只適合透過註冊/取消來操作自己對應的事件, 不應該隨意進行的賦值操作, 如果不小心將發佈者的delegate類別直接賦值null, 將會取消掉發佈者所有的通知列表
  • 發佈事件的通知應該由發佈者來觸發, 不應該讓客戶端可以呼叫
因此C#利用event來封裝delegate類型的變數, 所以在類別的外部, 使用者只能透過註冊“+=”和取消“-=” 來作事件的操作.

Event使用範例
將上述範例中, public ClickDelgate ClickEvent 加上 event修飾字
public class ButtonControl
{
        public delegate void ClickDelgate(object sender, EventArgs e);        
        public event ClickDelgate ClickEvent; 
        public void OnFire(EventArgs e)  
        {
            if (ClickEvent != null)
                ClickEvent(this, e);
        }
}

static void Main(string[] args)
{
   ButtonControl btnCtrl = new ButtonControl();

   //只能使用+=,-= 來對事件操作
   btnCtrl.ClickEvent += new ButtonControl.ClickDelgate(TexboxUpdate);
   btnCtrl.ClickEvent += new ButtonControl.ClickDelgate(LabelUpdate);
}

EventHandler
雖然 C# 語言允許event使用任一種delegate型別,但是 .NET Framework 對於使用於eventdelegate型別有更嚴格的規範, 因此另外定義出一個適當的delegate型別EventHandler, 語法如下:
public delegate void EventHandler(object sender, EventArgs e);

EventHandler 為預先定義的delegate, EventHandler會定義一個沒有傳回值的方法, 方法第一個參數的型別為 object, 表示引發事件的執行個體(如果是button1的click事件, 則sender就是button1), 而第二個參數的型別為EventArgs, 表示封裝事件的額外資訊.

EventHandler使用範例
public class ButtonControl
 {
        //利用EventHandler取代delegate void EventHandler(object sender, EventArgs e)
        public event EventHandler ClickEventHandler;  
        public void OnClick()
        {
            if (ClickEventHandler != null)
            {
                ClickEventHandler(this, new EventArgs());
            }           
        }
 }

自訂EventArgs
.NET Framework 2.0時引進EventHandler泛型版本,即 EventHandler<T>也就是如果你要自訂的 EventArgs 類別, 可以參考下面範例:
public event EventHandler<customeventargs> RaiseCustomEvent;

public class CustomEventArgs : EventArgs
{
    public CustomEventArgs(string s)
    {
        msg = s;
    }
    private string msg;
    public string Message
    {
        get { return msg; }
    } 
}

參考資料:
發行符合 .NET Framework 方針的事件
委派教學課程
事件教學課程
函數指標的進化論(下)
我對.NET中delegate和event區别的理解
談談委派 (Delegate)
C# 筆記:重訪委派-從 C# 1.0 到 2.0 到 3.0

2010年12月31日 星期五

WPF - Style使用

Style(樣式)
    Style主要就是透過屬性(Property)去自訂控制項的外觀, 尤其當我們需要相同的屬性套用在多個控制項時, Style會很方便.

比方在UI設計時, 常會讓UI具有某些相同的Style,例如:背景顏色,字型,控制項的顏色…等等.

使用Style的好處如下:
  • 可以方便使用者將一組屬性值(顏色,尺寸,動畫, 觸發方式等)套用到多個控制項的方式,這樣一來, 程式碼將變得簡潔.
  • 可集中在同一個地方作管理(ex: 資源 Resource), 好處如果以後想改變這些屬性, 只要在同一個地方更新即可
例如下面範例中, 有一個影音播放程式,如圖1所示, 其中UI上有三個外觀相同的按鈕, 在這程式碼當中, 如圖2所示, 如果不套用Style的話, 我們必需要將每一個控制項都設定一次相同的屬性, 這樣, 程式碼將會有很多重覆的部分, 而且, 萬一以後要變更其中一個屬性, 將要修改到很多地方

                                                       [圖1]具有一些相同屬性的Button



                                                          [圖2] 沒有套用Style


下面的範例中, 我們利用Style將上述程式碼中相同屬性抽離出來(StyleSetter組成的集合來設定目標屬性, 而每個Setter是由依存屬性和設定值組成), 並集中在Resource中. 程式碼將變得簡潔, 管理上也變的更方便, 如圖3所示.
                                                 [圖3] 套用Style


Style的共享與限定
你也可以將Style套用在不同的控制項上,如下所示:
<Style x:Key="controlStyle">
  <Setter Property="Control.Height" Value="30"/>
  <Setter Property="Control.Width" Value="75"/>
  <Setter Property="Control.Margin" Value="10"/>
  <Setter Property="Control.Background" 
                    Value="{StaticResource btnBackground}"/>
</Style>

<!-- Style套用在不同的控制項上-->
<Button x:Name="btnPlay" Grid.Column="0"
           Style="{StaticResource controlStyle}"
           Content="Play" Click="btnPlay_Click">
</Button>

<Label Grid.Column="1"
          Style="{StaticResource controlStyle}"
          Content="Text Block"/>
            
<Border Grid.Column="2"
           Style="{StaticResource controlStyle}"/>


或限定此Style只能給特定的控制項使用, 可用TargetType屬性指定, 例如你希望只有Button可套用此Style, 可寫成:
<Style x:Key="btnStyle" TargetType="{x:Type Button}">
     <Setter Property="Height" Value="30"/>
     <Setter Property="Width" Value="75"/>
     <Setter Property="Margin" Value="10"/>
     <Setter Property="Background" 
                Value="{StaticResource btnBackground}"/>
</Style>

若此Syle套用在非Button的控制項上, 將會產生編譯錯誤

Style繼承
Style之間可以互相繼承, 如果要這樣做, 您可以使用一個樣式做為基礎來建立新樣式, 並利用BasedOn屬性來繼承
<Style x:Key="btnStyleWithBold" 
          BasedOn="{StaticResource btnStyle}"
   <Setter Property="Button.FontWeight" Value= "Bold"/>
</Style>

另外, Style除了可用來設定控制項的屬性(Property)之外, 還可以運用在下列幾個地方
  • 設定觸發方式(Trigger)
  • 設定動畫(Animation)
  • 設定樣板(Template)
相關範例, 可參考: MSDN-設定樣式與範本


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年12月9日 星期四

DLL的製作和使用

在程式開發上, 常會將自己寫好的共用函式或共用類別編譯成DLL, 這樣除了可以省記憶體空間外, 也可達到模組化方便讓其他程式共用, 這邊寫了一篇文章來介紹DLL建立的流程,主要分成兩個重點:

1.    DLL建立的步驟
  • 如何建立DLL檔(以C3DMath.dll為例)
  • 如何引用別人的lib檔和h檔(以MainDLLTest.exe為例)

2.    編譯環境的設定
VC中可以利用一些巨集指令來設定編譯環境,其優點有:
  • 方便管理檔案
  • 省去人工複製的時間(可以少掉複製一些h檔、 dll檔和lib檔的時間)
  • 將來可用在Auto build機制上


DLL建立的步驟-

1.    建立C3DMaths DLL專案:
開啟VC點選File -->New-->Project-->Win32 Console Application,然後在Name的欄位輸入專案的名稱


 2.    點選Next

3.    選擇DLL,然後按Finish


4.    產生一個標頭檔(C3DMath.h), 並加入下面這段巨集指令, 如下所示:
#ifdef  API_BUILD_DLL
 #define C3DMATHS_CLASS __declspec(dllexport)
#else
 #define C3DMATHS_CLASS __declspec(dllimport)
#endif

上面這段巨集的意思是, 如果定義了API_BUILD_DLL 編譯時就把字串 C3DMATHS_CLASS換成__declspec(dllexport), 否則, 就換成 __declspec(dllimport), 因為C3DMath.h 通常會用在兩個地方:
(1)產生 DLL的專案: 需要匯出 DLL 的資料、函式、類別或類別成員函式,因此要使用__declspec(dllexport)
(2)使用 DLL 的專案(例如exe檔): 此時需要匯入DLL 的資料、函式、類別或類別成員函式, 所以要用 __declspec(dllimport)

5.    在Preprocessor Definitions中定義巨集(API_BUILD_DLL),其作用是讓上面的巨集指令知道此DLL專案要使用__declspec(dllexport)

6.    在class的title上加上巨集的定義(C3DMATHS_CLASS), 表示將此class匯出

class  C3DMATHS_CLASS  C3DVector
{

public:
    C3DVector(void);
    ~C3DVector(void);

    C3DVector(float X, float Y, float Z);
    void Set(float X, float Y, float Z);
    float Length(void);
    void Normalize(void);
}

7.    按下Rebuild Sloution 鍵,即可產生C3DMaths.dllC3DMaths.lib

APP使用DLL的步驟
程式中使用DLL需要有.h檔, .lib檔和.dl檔, 其中.h檔和.lib檔必須加入到使用DLL的專案中, 而.dll檔則需要放在應用程式所在的路徑(通常和.exe檔同一路徑), 步驟如下:

1.    建立一隻Console的程式(MainDLLTest.exe)
2.    將C3DMath專案所產生的h檔、lib檔,複製到MainDLLTest的專案底下
3.    引入標頭檔(C3DMath.h)
       #include ".\C3DMaths\C3DMath.h"
4.    連結lib檔
       點選Property Page-->Linker-->Input-->Additional Dependencies,然後輸入lib檔的路徑


5. 複製.dll檔(C3DMaths.dll)到應用程式執行路徑下(MainDLLTest.exe), 即可執行程式來測試DLL的功能



編譯環境的設定
VC中可以利用一些巨集指令來設定你的編譯環境,不但可方便管理專案,也節省很多人工的時間,尤其在開發大型專案時,自動build的流程就變得很重要。下面介紹的方法,可用來自動產生使用DLL時,所要引用的檔案和資料夾,如此一來可省去人工複製的麻煩和時間。

首先看一下VC項目設置中可以使用的巨集,常用的有:

 1.    設定dll檔的輸出路徑
General -->Output Directory -->  $(PlatformName)\$(ConfigurationName)

2.    設定lib檔的輸出路徑
Linker->Advance->ImportLibrary -->\$(PlatformName)\$(ConfigurationName)/$(TargetName).lib

3.    利用Post Build Event複製檔案
這個功能大多是當你在開發 DLL 專案,編譯完成後,直接將編譯結果複製到會使用的專案目錄中。這樣就不用, 每次還要開檔案總管進行複製了。輸入的指令如下所示

產生專案目錄
mkdir  ".\$(ProjectName)" 

產生Debug或Release目錄
mkdir  ".\$(ProjectName)\$(PlatformName)\$(ConfigurationName)"

複製lib檔
COPY".\$(PlatformName)\$(ConfigurationName)\$(TargetName).lib"   
       ".\$(ProjectName)\$(PlatformName)\$(ConfigurationName)"

複製dll檔
COPY "$(TargetPath)" ".\$(ProjectName)\$(PlatformName)\$(ConfigurationName)"

複製h檔
COPY "C3DMatrix.h" ".\$(ProjectName)" 
COPY "C3DVector.h" ".\$(ProjectName)"                                                                                             
COPY "C3DMathLoader.h" ".\$(ProjectName)"        

2010年12月1日 星期三

在C++ Class中使用callback function

C++ 的類別中如何使用APIcallback function, 相信這是剛學會callback function程式員會遇到的問題, 所以在這整理一下解決的方法.
1.      callback function要成為類別的member function必須宣告成static

解析 - 首先, 我們先來釐清類別中的成員函式, 是如何存取成員變數(注意: 記憶體中只會有一份類別成員函式, 而類別成員變數可能有好幾份).

關鍵就是透過this指標. 因為, C++在編譯時, 會隱含this指標在類別的成員函式中
例如有一個類別和其成員函式為:
class CShape
{
...
public:

void SetColor(int color) { m_color = color;};
}
經過編譯後, 會變成
class CShape
{
...
public:
    void SetColor(int color, (CShape*)this) { this->m_color = color; }
};
因此, 透過this指標可用來指向呼叫它的物件(instance), 進而存取各自的成員變數.
callback function通常是由系統呼叫, 也就是系統不需經由任何物件呼叫此函式,  因此member callback function的 this 指標會沒有指向任何實體物件, 這將會造成錯誤. 

為了不要讓callback function自動含有this指標, 可以將callback function宣告成static member function. 
如此一來, callback function就變成 global function, 不需透過物件的實體就可呼叫.

2.   若要在callback function中存取類別成員變數或成員函式, 需要修改callback function prototype, 讓函式的參數列中, 可傳入this指標.

解析 - 由於static member function是屬於類別而不是物件, 所以呼叫static member function時, 不會傳入物件的位址, 所以static member function中不會有this指標, 因此, static member function中不允許使用non-static成員.
 

所以, 如果要在static callback function中存取non-static成員, 需要在callback functuon的參數列表中傳入物件的this指標.

例如API中有一個callback function宣告如下:
      void (*TouchCallback)(TouchPoint p);

若要成為類別的成員函式, 可改為:
     void (*TouchCallback)(TouchPoint p, void* pInstance);
 


下面為範例:
有一個Touch API, 它利用callback的方法來取得觸控點資料.其介面如下:

#ifdef  TOUCHAPI_EXPORTS
  #define TOUCH_API __declspec(dllexport)
#else
  #define TOUCH_API __declspec(dllimport)
#endif

// 定義觸控點的相關資訊
struct TouchPoint
{
 int X; 

 int Y; 
};

// 定義可傳入this指標的callback function prototype
typedef void (_stdcall *CB_TOUCH_CLASS_FUNC) (TouchPoint, void* pObject);

// 定義了一個RegisterCBTouchMemberFunc的函式,
// 可傳入一個具有CB_TOUCH_FUNC型別的callback function
TOUCH_API void RegisterCBTouchMemberFunc(CB_TOUCH_CLASS_FUNC callback,void* pObject);

定義一個CTouchClass的類別, 必且利用Touch APIcallback function 作為member function, 程式碼如下:
TouchClass.h

#include "TouchAPI.h"
class CTouchClass
{
private:
 int m_x;
 int m_y;

public:
 CTouchClass(void);
 ~CTouchClass(void);

public:
 static void _stdcall TouchCallback(TouchPoint point, void* pObject);

public: 
 int GetTouchX();
 int GetTouchY();

};
TouchClass.cpp

#include "StdAfx.h"
#include "TouchClass.h"

CTouchClass::CTouchClass(void)
{
    this->m_x = 0;
    this->m_y = 0;
}

CTouchClass::~CTouchClass(void)
{
   this->m_x = 0;
   this->m_y = 0;
}

// 利用this指標存取member function
void CTouchClass::TouchCallback(TouchPoint point, void* pObject)
{
   CTouchClass* pClass =  (CTouchClass*)pObject;
   pClass->m_x = point.X;
   pClass->m_y = point.Y;
}
 
int CTouchClass::GetTouchX()
{
   return m_x;
}
    
int CTouchClass::GetTouchY()
{
   return m_y;
}

2010年11月24日 星期三

如何在C#中使用C++ Class DLL

如何在C#中使用Unmanaged dll 文章中曾介紹C#中使用C++ 函式庫(DLL)的方法, 不過此方法僅適用於C-Style export 出來的函式, 不行用在類別(Class)中的成員函式.


但其實很多API都是設計成C++的類別, 因此本文將介紹如何在C# 使用C++ Class的函式.

其步驟如下:

  • 設計一個C-Style function 介面, 透過指標存取物件的實體 – 由於C#中並沒有提供包裝C++ Class的方法, 所以我們必須將類別中public function重新包裝成C-Style function, 而一個物件的實體, 其實可以透過指標(*)作存取, C#中指標的使用也可透過IntPtr.
  • 利用C# PInvoke機制呼叫C-Style function– 作法可參考如何在C#中使用Unmanaged dll
  • 釋放unmanaged object記憶體 –由於C#的回收機制,不會自動回收unmanaged object記憶體, 所以我們必須透過Dispose/Finalize手動回收, 其作法可參考C#記憶體管理

範例如下:

原始C++類別程式碼
#ifdef CSHAPEAPI_EXPORTS
 #define  CSHAPE_API  __declspec(dllexport)
#else
 #define  CSHAPE_API  __declspec(dllimport)
#endif


class CSHAPE_API CShape
{

public:
 CShape(){};
 virtual ~CShape(){};

public:
 float GetRectangleArea(float w ,float h) { return w*h; };  
 float GetTriangleArea(float b ,float h) { return 0.5*b*h;};
};

另外設計一個C-Style function 介面, 包裝要使用的C++ 類別

CShapeBridge.h
#include "CShapeAPI.h"
#ifdef __cplusplus
extern "C" {
#endif

extern CSHAPE_API CShape* CreateInstance();
extern CSHAPE_API void DisposeInstance(CShape* pShapeInstance);

extern CSHAPE_API float GetRectangleArea(CShape* pShapeInstance,
                                        float w ,float h);  
extern CSHAPE_API float GetTriangleArea(CShape* pShapeInstance,
                                        float b ,float h);


#ifdef __cplusplus
}
#endif

CShapeBridge.cpp
#include "stdafx.h"
#include "CShapeBridge.h"

extern "C"  CSHAPE_API CShape* CreateInstance()
{
 return new CShape();
}

extern "C"  CSHAPE_API void DisposeInstance(CShape* pShapeInstance)
{
 if(pShapeInstance != NULL)
 {
  delete pShapeInstance;
  pShapeInstance = NULL;
 }
}

extern "C"  CSHAPE_API float GetRectangleArea(CShape* pShapeInstance, 
                                              float w ,float h)
{

 return pShapeInstance->GetRectangleArea(w, h);
}

extern "C"  CSHAPE_API float GetTriangleArea(CShape* pShapeInstance, 
                                             float b ,float h)
{
 return pShapeInstance->GetTriangleArea(b, h);
}

利用PInvoke機制呼叫C-Style function , 並釋放unmanaged object記憶體

ShapeWrapper.cs
using System;
using System.Runtime.InteropServices;

namespace ShapeTest
{
    public class ShapeWrapper : IDisposable
    {
        #region PInvokes
        [DllImport("CShapeAPI.dll")]
        private static extern IntPtr CreateInstance();

        [DllImport("CShapeAPI.dll")]
        private static extern void DisposeInstance(IntPtr pShapeInstance);

        [DllImport("CShapeAPI.dll")]
        private static extern float GetRectangleArea(IntPtr pShapeInstance,
                                                     float w, float h);  
       
        [DllImport("CShapeAPI.dll")]
        private static extern float GetTriangleArea(IntPtr pShapeInstance, 
                                                    float b, float h);
        #endregion

        #region Members
        // Variable to hold the C++ class's this pointer
        private IntPtr m_pNativeObject;  
        #endregion Members


        public ShapeWrapper()
        {
            // We have to Create an instance of 
            // this class through an exported function
            this.m_pNativeObject = CreateInstance();
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool bDisposing)
        {
            if (this.m_pNativeObject != IntPtr.Zero)
            {
                // Call the DLL Export to dispose this class
                DisposeInstance(this.m_pNativeObject);
                this.m_pNativeObject = IntPtr.Zero;
            }

            if (bDisposing)
            {
                // No need to call the finalizer since we've now cleaned
                // up the unmanaged memory
                GC.SuppressFinalize(this);
            }
        }

        // This finalizer is called when Garbage collection occurs, but only if
        // the IDisposable.Dispose method wasn't already called.
        ~ShapeWrapper()
        {
            Dispose(false);
        }


        #region Wrapper methods
        public float GetRectangleArea(float w, float h)
        {

            return GetRectangleArea(m_pNativeObject, w, h);
        }

        public float GetTriangleArea(float b, float h)
        {
            return GetTriangleArea(m_pNativeObject, b, h);
        }
        #endregion
    }
}


測試C#程式使用C++ Class DLL
using System;
namespace ShapeTest
{
    class Program
    {
        static void Main(string[] args)
        {
            ShapeWrapper shape = new ShapeWrapper();

            float w= 25.0f;
            float h= 10.0f;
            Console.WriteLine("Rectangle area = "+shape.GetRectangleArea(w, h));


            float b = 30.0f;
            float h1 = 20.0f;
            Console.WriteLine("Triangle area = "+ shape.GetTriangleArea(b, h1));

        }
    }
} 


測試結果:
Rectangle area = 250
Triangle area = 300

另外, MSDN也有提供使用CLR的方法, 請參考包裝原生類別以便讓 C# 使用

參考文章:

2010年11月11日 星期四

Background Process in WPF

在程式執行中, 有時後需要處理一些比較耗時的程序, 例如讀取或計算大量的資料, 因為這些動作會花比較多的時間, 而且會讓畫面停留不動. 所以, 通常需要一個Loading/Processing的畫面來提醒使用者.

Loading過程中, 如果要更新UI內容的話, 需要在背景工作, 否則會等Loading處理完畢後, 才會更新UI.

下面的範例中, 程式會顯示Loading過程中的次數和完成的百分比.
private void UpdateProcess()
 {
     for (int i = 1; i <= _numberOfTasks; i++)
     {
         labelNumber.Content = i +"/" + _numberOfTasks;
         float percentageDone = (i / (float)_numberOfTasks) * 100f;
         labelPercent.Content = (int)percentageDone+"%";
         Thread.Sleep(1);
     }
}


從執行結果可看出, 因為不是在背景中(background)更新UI內容, 所以畫面會停住不動, Loading完才會更新UI內容

 
[影片1] 不在背景工作中更新UI 


WPF中提供多個背景工作的方法, 下面逐一介紹

1. 使用Dispatcher

此方法最為簡單, 我們可將Dispatcher Priority 設成 DispatcherPriority.Background,即可在背景執行,而Dispatcher的相關用法可參考這篇文章WPF - Thread and Dispatcher
因此我們可將上面的程式碼, 改成為下面的程式:

private delegate void UpdateProcessDelegate(int index, int percent);
private void UpdateProcessAction(int index, int percent)
{
    labelNumber.Content = index + "/" + _numberOfTasks;
    labelPercent.Content = percent + "%";
 }

 private void UpdateProcess()
 {
     for (int i = 1; i <= _numberOfTasks; i++)
     {
         //labelNumber.Content = i +"/" + _numberOfTasks;
         float percentageDone = (i / (float)_numberOfTasks) * 100f;
         //labelPercent.Content = (int)percentageDone+"%";

         Dispatcher.Invoke(DispatcherPriority.Background,
                           new UpdateProcessDelegate(UpdateProcessAction),
                           i, 
                           (int)percentageDone);
         
         Thread.Sleep(1);
      }
 }

 
[影片2] 使用Dispatcher更新UI內容

 
2. 使用DispatcherFrame       
此作法類似Windows Form的DoEvents.  
DispatcherFrame 表示處理暫停之工作項目的迴圈.發送器會處理迴圈中的工作項目佇列. 此迴圈即稱為框架。通常是由應用程式以呼叫 Run 的方式來初始化初始迴圈. PushFrame 進入以 frame 參數表示的迴圈。對該迴圈的每次查看, Dispatcher 都會檢查 DispatcherFrame 類別上的 Continue 屬性,以判斷迴圈是否應繼續或停止.

程式碼如下:
public void DoEvents()
{
   DispatcherFrame frame = new DispatcherFrame();
   Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                                            new DispatcherOperationCallback(ExitFrame),
                                            frame);
   Dispatcher.PushFrame(frame);
}

private object ExitFrame(object f)
{
   ((DispatcherFrame)f).Continue = false;
   return null;
}

private void UpdateProcess()
{
   for (int i = 1; i <= _numberOfTasks; i++)
   {
       labelNumber.Content = i +"/" + _numberOfTasks;
       float percentageDone = (i / (float)_numberOfTasks) * 100f;
       labelPercent.Content = (int)percentageDone+"%";
       DoEvents();
       Thread.Sleep(1);
    }  
 }

BackgroundWorker可以在個別的執行緒上執行. BackgroundWoker常用的function如下:
程式碼如下:
private void btnShowText_Click(object sender, RoutedEventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;
    worker.DoWork += new DoWorkEventHandler(DoWork);
    worker.ProgressChanged += new ProgressChangedEventHandler(DuringWork);
    
    // start the background work
    worker.RunWorkerAsync();
 }

 void DoWork(object sender, DoWorkEventArgs e)
 {
     BackgroundWorker worker = sender as BackgroundWorker;
     for (int i = 1; i <= _numberOfTasks; i++)
     {
         if (worker.CancellationPending)
         {
            e.Cancel = true;
            return;
          }
          Thread.Sleep(1);
          float percentageDone = (i / (float)_numberOfTasks) * 100f;
          worker.ReportProgress((int)percentageDone, i);
      }
  }
       
 void DuringWork(object sender, ProgressChangedEventArgs e)
 {
    labelNumber.Content = e.UserState + "/" + _numberOfTasks;
    labelPercent.Content = e.ProgressPercentage.ToString() + "%";
 }


參考文章:

2010年11月10日 星期三

WPF - Thread and Dispatcher

WPF 執行緒:
WPF應用程式中主要有兩個執行緒(Thread):Rendering ThreadUI Thread
  • Rendering Thread: 在背景中執行並協助UI Thread完成工作
  • UI Thread: 主要負責處理InputEvent與程式碼的部分
WPF中, 所有UI工作和事件都是在UI Thread上完成. 每個WPF程式至少有一個Dispatcher物件和一個UI Thread. 而大部分WPF物件的操作都是binding在UI Thread上,其他的Thread如果想要直接操作這些UI物件, 是不被允許的, 因為只有建立 DispatcherObject 的Thread才可以存取UI物件。例如, 要讓其他的Thread存取 LabelContent 屬性, 將會發生錯誤, 此範例如下所示:
設計一個WPF視窗, 當按下Button後, 讓Label顯示按Button的次數.

C#程式碼如下所示:
private static int _clickCount = 0;
private void btnShowText_Click(object sender, RoutedEventArgs e)
{
      _clickCount++;
       // Update Label Conten in another thread
       Thread updateThread = new Thread(UpdateText);
       updateThread.Start();
 }
 private void UpdateText()
 {
      labelClickCount.Content = "Click Count= " + _clickCount.ToString();
 }

[圖1]無法再另一個Thread中更新UI


Dispatcher用法:

Dispatcher可讓其他的ThreadWPF中的 UI Thread上執行程式碼. Dispatcher又被稱為WPF中的Message Pump,提供一個機制去發送工作項目給UI Thread進行處理.  
Dispatcher將工作項目轉給UI Thread時, 會讓UI Thread進入封鎖狀態(畫面鎖定, 因為要處理工作項目). 其他的Thread必須將工作委派給與 UI Thread相關聯的 Dispatcher. 這可以使用 Invoke BeginInvoke 完成.
  • Invoke 是同步呼叫,也就是說,除非 UI 執行緒實際完成委派的執行,否則不會返回. 
Dispatcher 會依優先權排列其佇列中項目的順序. 在將項目加入至 Dispatcher 佇列.

下面的程式碼, 將說明其解決方法:
C#程式碼如下所示:

private static int _clickCount = 0;

 // Declare a delegate type for updating UI
 private delegate void UpdateDelegate(int number, string str);
 private void btnShowText_Click(object sender, RoutedEventArgs e)
 {
     _clickCount++;
     Thread updateThread = new Thread(UpdateText);
      updateThread.Start();
 }

 private void UpdateText()
 {
      //labelClickCount.Content = "Click Count= " + _clickCount.ToString();
      // Places the delegate onto the UI Thread's Dispatcher
      Dispatcher.Invoke(new UpdateDelegate(UpdateClickCountAction),
                                          _clickCount, 
                                          "Click Count = ");
 }

  private void UpdateClickCountAction(int number, string str)
  {
       labelClickCount.Content = str + number.ToString();
  }

[圖2] 利用Dispatcher執行另一個Thread的function

參考文章:

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