close

Web Application 三部曲:動態生成控制項技術

作者:朱明中

動態生成控制項(Dynamic Control Creation)

在 Web Client 的前端介面中,經常出現有連續且類似(或相同)的使用者介面,例如訂單商品輸入,客戶資料輸入等等表單,這些表單都有幾個特性:

  • 介面控制項組合相同(或相似)。
  • 數量不固定,或者是數量很大(通常數量大於 10 個就很多了)。
  • 通常以 Master-Detail(主從式使用者介面)居多。

例如像是這樣的問題:

各位程式開發達人:
小弟最近遇到一個問題,就是要讓使用者自行新增控制項到網頁中,舉例來說我在網頁上方維護 VS 2005 server control list 讓使用者選擇,選定後在網頁下方 create 該控制項,並讓使用者設定屬性,我再將使用者設定存入資料庫,有沒有比較簡便的方式或 function 可以達成此目標呢??
Source:http://forums.microsoft.com/msdn-cht/ShowPost.aspx?PostID=729081&SiteID=14

這種需求通常會出現在類似問卷建立系統,購物車系統等等地方。

實作動態控制項必要的背景知識

若要在 Windows Application 中實作這種控制項相對來說比較簡單,因為 Windows Application 是一種具有狀態記錄的應用程式,在表單中經過 Form_Load 程式產生的控制項,都會被記錄住(有類別層級的區域變數),在後續的程式處理都可以使用。

然而,Web Application 是一個 State-less(無狀態)的應用程式,所有在Web應用程式中產生的控制項,在伺服器產生HTML回傳到 Web Client 後,伺服器就會丟棄這次的處理結果,包含變數與所耗用的資源,讓 Web Server 可以處理更多的用戶端要求的動作,這也表示,控制項的維護,要由 Web Application 來負責,而不是由 Web Server 負責。

所以 Web Application 的開發人員需要處理許多動態控制項的問題,而這些問題在處理上會有些複雜,諸如:

  • 控制項建立(Control Creation),在頁面處理時,產生控制項。
  • 控制項命名(Control Naming)與套用事件處理常式。
  • 控制項資料存取(Control Access)。
  • 控制項資料與狀態保存(Control State Persistent)。

A. 控制項建立

在每次提交 Request 到 Web Server 時,都需要在頁面載入時期,將控制項重新生成,讓 ASP.NET 知道有這個控制項存在,否則無法在資料載入時期將資料套用到控制項中(後面會提到)。

B. 控制項命名

控制項在生成之時,都需要設定控制項的 ID,通常在大量的控制項生成時,ID 數量會變得許多,在設計上通常以具規則性的方式來做,才可以在後續處理上方便許多,否則只會讓處理工作更複雜。

C. 控制項資料存取

ASP.NET 在執行完成頁面載入之後,就會開始資料設定,將資料設定給控制項,但若在控制項生成階段未產生控制項時,這個動作就會被忽略掉。

D. 控制項資料與狀態保存

在 ASP.NET 輸出資料之後,所有控制項的資料都會釋放掉,開發人員需要利用一些方法或機制來保存狀態與資料,狀態的控制在複雜的應用程式是個較不好處理的工作。

另外,在使用者介面之中,有一種特殊的控制項,可以收納其他的控制項在其中,就如同收納箱可以放許多小東西一樣,這種控制項稱為收納器或者容器(Container)控制項,在容器控制項中還可以放容器控制項,成為巢狀的控制項層級。容器控制項由容器類別組成,容器類別實作了 IContainer 介面,提供了可收納控制項或元件的基礎支援。

ASP.NET 的 Page 本身就是一個容器控制項,可以收納許多控制項,例如 Web Control、HTML Control、User Control 與 Custom Control。

使用 ASP.NET Page API 來處理控制項工作

在動態控制項處理的過程中,ASP.NET 提供了幾個重要的屬性與方法,讓開發人員在動態控制項處理上能夠更方便,雖然老實說沒有方便到哪裡去,基於容器控制項的特性,開發人員可以透過 Page 所提供的 API 來進行控制項的處理工作。

  • 找尋控制項(Searching Control)
    Page.FindControl() 允許開發人員以控制項 ID,搜尋在頁面中的控制項,但這個函式只會搜尋在 Page 中的控制項,若要找的控制項是放在 Page 裡面的容器控制項時,將不會被 Page.FindControl() 找到,此時將需要使用遞迴(Recursive)方法來搜尋在每個容器中的控制項。
    要判斷是不是容器控制項也很簡單,只要判斷 Controls.Count 是否大於零就可以了。
  • 列舉控制項(Enumerating Control)
    列舉控制項是由根物件(或者指定的物件)向下,列出所有控制項的方法,這在要依順序來存取控制項時是很好用的方法,同時也可以知道目前控制項的分布狀況。
  • 型別處理與控制項轉型(Type processing and casting for Control)
    當使用 Page.FindControl() 與列舉控制項的時候,通常不會固定在某個控制項類別,因為每個網頁都會有許多控制項,而不是只有一種控制項,最常使用的就是 Control 類別,它是所有控制項的父類別,所有的控制項都要繼承它來實作。

    在Page.FindControl() 找到控制項時,它會回傳一個 Control 物件,代表搜尋到的控制項,開發人員需要將它轉型成需要的類別才可以使用,否則就只能使用 Control 物件可用的方法與屬性,物件轉型的方法,C# 可以使用 as 運算子,Visual Basic 則可以使用 CType() 陳述式。
    // C#
    Control ctl = Page.FindControl(“TextBox1”);
    TextBox TextBox1 = ctl as TextBox;
    ‘ Visual Basic
    Dim ctl As Control = Page.FindControl(“TextBox1”)
    Dim TextBox1 As TextBox = CType(ctl, TextBox)
    
    

綜合前面所說明的,一個可以進行深度搜尋的程式碼列示如下:

// C#
public Control DepthFindControl(string ControlID, Control BaseControl)
{
  Control result = BaseControl.FindControl(ControlID);
  if (result == null)
  {
    foreach (Control ctl in Page.Controls)
    {
      if (ctl.Controls.Count > 0)
        result = DepthFindControl(ControlID, ctl);
    }
  }
  return result;
}

'Visual Basic
Public Function DepthFindControl(ControlID As String, BaseControl As Control) As Control
  Dim result As Control = BaseControl.FindControl(ControlID);
  If result Is Nothing Then
      For Each ctl As Control In Page.Controls
          If ctl.Controls.Count > 0 Then
              result = DepthFindControl(ControlID, ctl)
          End If
      Next ctl
  End If
  Return result
End Function

一個可以進行深度列舉(列舉出所有的Web Control)的程式碼列示如下:

//C#
public List EnumControls(List ctlContainer, Control BaseControl)
{
    if (ctlContainer == null)
        ctlContainer = new List();

    ctlContainer.Add(BaseControl);

    foreach (Control ctl in BaseControl.Controls)
        ctlContainer = EnumControls(ctlContainer, ctl);

    return ctlContainer;
}

'Visual Basic
Public Function EnumControls(ctlContainer As List(Of Control), BaseControl As Control) As List(Of Control)

    If ctlContainer Is Nothing Then
        ctlContainer = new List(Of Control)()
    End If

    ctlContainer.Add(BaseControl)

    For Each ctl As Control In BaseControl.Controls
        ctlContainer = EnumControls(ctlContainer, ctl)
    Next ctl

    Return ctlContainer;

End Function

建立動態控制項

動態控制項建立其實很簡單,只要使用一個新增物件的指令就可以了:

// C#
Button cmdNewButton = new Button(); // for Web Control
Control ctl = Page.LoadControl(“UserCtl.ascx”); // for user control.
‘ Visual Basic
Dim cmdNewButton As Button = New Button(); // for Web Control
Dim ctl As Control = Page.LoadControl(“UserCtl.ascx”); // for user control.

不過困難總是隱藏在看起來很簡單的程式碼中,因為 Web-Based 應用程式的特性,如果這段程式碼是放在 Page.IsPostBack 判斷式中:

// C#
if (!Page.IsPostBack)
{
    Button cmdNewButton = new Button();
    cmdNewButton.Text = “Dynamic Button”;
    cmdNewButton.Click += new EventHandler(this.Dynamic_Click);
    // PageContent是一個PlaceHolder控制項
    this.PageContent.Controls.Add(cmdNewButton);
}

當你按下這個按鈕後,可能就會大吃一驚,因為按鈕不見了,連按鈕的事件常式都沒有被呼叫到:

但我們如果改成:

// C#
Button cmdNewButton = new Button();
cmdNewButton.Text = “Dynamic Button”;
cmdNewButton.Click += new EventHandler(this.Dynamic_Click);
// PageContent是一個PlaceHolder控制項
this.PageContent.Controls.Add(cmdNewButton);

就可以順利執行了:

你也許會覺得很奇怪,明明已經建立了控制項,為什麼會不見?其實,這就是 Web-Based 應用程式的特性,這在首篇(首部曲)文章中就已經解釋過,Web-Based 不會主動幫你記住任何狀態,不管是資料,控制項或是使用者狀態等等,這是開發人員的工作。也就是說,在每一次 PostBack 時,都要重新將控制項建立起來,ASP.NET 才會處理由用戶端回傳的指令,也才會執行到控制項所繫結的事件處理常式,否則指令會被捨棄掉。

那麼,要把控制項生成放在哪一個事件常式呢?網路上有一些說法,說要放在 Page_Init,但也有說要放在 Page_Load 事件常式,筆者說:都可以,理由待我說來。

ASP.NET 的 Page 事件順序,由初始化到完成,其順序可由下圖組成:

為什麼我會說都可以呢?因為一般控制項通常都只會使用 Load 事件,來讓 ASP.NET Page 載入控制項,而很少使用 Init 事件來初始化,對像 Button 這樣的 Web 控制項來說,不太需要使用 Init 事件,一般的使用者控制項,也不見得會用到 Init 事件,對於這種控制項來說,不需要一定要在 Page_Init 事件來建立控制項,在 Page_Load 也可以,所以前面的範例碼可以用。

然而,這有個例外,若控制項有使用到 Init 事件時,就需要把它放到 Page_Init 來呼叫,所以在MSDN上的一篇技術文章: "Creating Dynamic Data Entry User Interfaces" 中,建議將動態控制項生成的程式放在 Page_Init 中,但筆者認為不一定,看應用程式使用的控制項為何來決定。

動態控制項設計的考量

隨著使用者介面的複雜度提升,若頁面中都充斥著動態生成的控制項,會讓程式設計的複雜度提高,在狀態維護的難度上也會提高,所以適當的使用動態控制項,才是最重要的觀念。任何使用者介面的創新或發展,都會有其瓶頸之處,如何在不造成瓶頸前善用控制項,這考驗著系統設計與開發人員的智慧,尤其是 Web 應用程式和 Windows 應用程式之間本質上的不同。

本文提筆至此,下回將進一步提出一些簡單的動態控制項生成的技巧,分成用戶端與伺服器端的簡單應用。

arrow
arrow
    全站熱搜

    羅 朝淇 發表在 痞客邦 留言(0) 人氣()