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)