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

Callback Function

什麼是Callback function?  
如果在程式中使用某個function, 我們可稱作”call” function,這種行為是主動的,  callback function是被動的, 也就是當特定事件發生時(例如觸控發生, 或某手勢被偵測),callback function才會被呼叫.

CallbackC/C++實現的方式, 就是利用function pointer(函式指標), 例如A程式想要B程式發生某些事件時, 能通知A程式. 利用Callback機制的話, A程式必須傳一個function pointer給B程式,讓B程式可以使用這個function pointer來通知A程式.


function pointer(函式指標)宣告的方式如下:
返回類型 (* 函式指標名稱) (參數列表);

example:
int (* GetPointCallback)(int x, int y);

Callback function的使用案例
例如: 我們在設計一個觸控繪圖的應用程式時, 通常不會讓程式不斷地向系統要觸控點的資料, 而是希望當系統偵測到觸控發生時, 才會丟回觸控點的資料, 而這個丟回觸控點的行為, 就可以設計成callback function. 其示意圖如下所示:

[圖 1] Touch Paint Callback function

Callback function 範例程式
TouchAPI.h
#ifdef  TOUCHAPI_EXPORTS
 #define TOUCH_API __declspec(dllexport)
#else
 #define TOUCH_API __declspec(dllimport)
#endif

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

// 定義callback function的prototype
// CB_TOUCH_FUNC是一個pointer,指向具有一個TouchPoint類型,且回傳void型的函数
// 前面有個typedef,所以現在CB_TOUCH_FUNC是這種類型的别名
typedef void (_stdcall *CB_TOUCH_FUNC) (TouchPoint);

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

TouchAPI.cpp
#include "stdafx.h"
#include "TouchAPI.h"

void DetectTouchFromSystem(CB_TOUCH_FUNC callback)
{
    // 當系統取得touch資料時,
    // 就會呼叫一次pCB_TOUCH_FUNC參數所指到function一次
    TouchPoint tp;
    tp.X = 5;
    tp.Y = 10;
    callback(tp);
}

void RegisterCallbackTouchFunc(CB_TOUCH_FUNC callback)
{
 DetectTouchFromSystem(callback);
}
TouchPaint.cpp
#include "stdafx.h"
#include "conio.h"
#include "TouchAPI.h"
 
// 定義了一個名為GetTouch的函式, 也就是callback函式
// 它的prototype必須與TouchAPI的CB_TOUCH_FUNC宣告一致
void _stdcall GetTouch(TouchPoint point)
{
 printf("x=%d, y=%d", point.X, point.Y);
}
 
int main(void)
{
    //註冊一個callback function
    RegisterCBTouchFunc(GetTouch);
    getch();
    return 0 ;
};

參考資料:
何謂callback function
Callback (computer science)