2010/1/19

[UserControl] 在使用者控制項中建立事件處理函式

當你在設計 ASP.NET 網站時, 使用者控制項 (User Controls) 是非常好用的,假設我們把一個 DropDownList 製作成使用者控制項,那麼我們就等於把整個相關機制 (外觀、資料存取及邏輯判斷等) 都封裝在一個可以重複使用的物件裡面,可以在網站的任何地方使用。所以我十分推薦大家適當地使用使用者控制項

不過以這個例子來講,你可能還會發現一個「小」問題,那就是,我要如何才能在網頁中使用使用者控制項中觸發的事件?例如,當你在網頁中按下那個使用者控制項,而你要在按下該控制項的時候做一些事情。如果是普通的 DropDownList,那麼你可以在這個 DropDownList 的 SelectedIndexChanged 事件中去做你想做的事情。但是當你把它變成使用者控制項的時候,你會發現,這個使用者控制項並沒有這個事件存在! 

那是當然的,因為和自訂控制項不同,使用者控制項並不是使用繼承某個既有的網頁控制項而來,它充其量是一個 Container 物件,它怎麼可能天生就具備子控制項的事件呢?何況使用者控制項本身可以包含很多子控制項,它預設沒有 SelectedIndexChanged 事件,這是理所當然的。

要解決這個問題,我們就必須為你的使用者控制項加上自訂的事件和事件處理函式。

Public Event selectedIndexChanged(...)
Protected Sub ddl_SelectedIndexChanged(...) Handles ddl.SelectedIndexChanged
    RaiseEvent selectedIndexChanged(Me, e)
End Sub

經此修改之後,你在網頁上就可以撰寫該使用者控制項的 SelectedIndexChanged 事件處理函式了,方法和一般的 DropDownList 沒有什麼不同。

C# 語法與 VB 略有不同,範例如下:

public event EventHandler selectedIndexChanged;

protected void ddl_SelectedIndexChanged(object sender, EventArgs e) 
{
   if (selectedIndexChanged != null)    // 這一行一定要加
      selectedIndexChanged(sender, e);
}

不過, 如果寫成上述型式, 事件在網頁中觸發之後, 參數 "sender" 會指向那個控制項。例如, 假設你的使用者控制項裡有兩個元件, 分別是 CheckBox 和 RadioButton, 那麼, 按照上述寫法, 即使事件是在網頁中按下 CheckBox 後觸發的, 到時候抓取到的 sender 會是那個 CheckBox, 而不是整個使用者控制項本身。如果你要讓它指向使用者控制項本身, 你應該寫成如下:

public event EventHandler selectedIndexChanged;

protected void ddl_SelectedIndexChanged(object sender, EventArgs e) 
{
   if (selectedIndexChanged != null)    // 這一行一定要加
      selectedIndexChanged(this, e);
}

請注意最後一行中的 this 關鍵字。在 VB 程式中也比照處理即可 (VB 的對應關鍵字是 Me)。

此外另外有一點特別值得注意的,就是 User Control 和 .aspx 網頁各事件在整個網頁生命週期的先後關係。

舉例來講,假設你在如上述的 SelectedIndexChanged 事件中將 SelectedValue 儲存在 Cookie 裡,然後你企圖在 User Control 的 Page.Load() 事件中讓 User Control 去取得 Cookie 值並套用在 User Control 的 DropDownList 上,同時還設法去觸發 SelectedIndexChanged 事件以驅動 .aspx 網頁中的某些動作,那麼你將會發現問題。什麼問題呢?就是 User Control 的 Page.Load() 事件和 .aspx 的 Page.Load() 事件發生的順序,可能並不是你所想像中的順序。

換句話說,由於你使用了 Cookies 去記錄使用者上一次的設定,所以你希望當網頁一被載入時,User Control 會載入 Cookies 而去調整自己的 Selected Item,而且同時自動觸發 SelectedIndexChanged 事件而讓 .aspx 網頁做應該做的事情。

可惜事與願違。你將會發現即使你自己去手動觸發 SelectedIndexChanged  事件,該做的事情也不會做,而且恐怕會做錯 - 這完全是因為在 .aspx 的 Page.Load() 事件中,User Control 永遠只會選到項目 0。為何如此?這正是因為 User Control 的 Page.Load() 事件是發生在 .aspx 的 Page.Load() 之後,所以它事實上根本不會做你希望它達成的動作。如果你不相信的話,自己去 Trace 一遍就會清楚了。

唯一的解決辦法,就是不要依賴 User Control 的 SelectedIndexChanged (或其它 User Control 事件) 在 .aspx 剛載入時去驅動任何動作,而是去採用其它的做法。我自己的做法,在這種情況下,是在 .aspx 的 Page.Load() 事件中直接去讀取 Cookies 的內容作為判斷的依據,而非 User Control 中的 Selected Item。

在 Windows Form 中, 也是使用 (幾乎) 完全相同的做法, 唯一的差異就是你不能直接從方案總管把 User Control 拉到畫面上, 而是從工具箱找到圖示 (需建置後才會有), 再拉到畫面上。

沒有留言:

張貼留言