什麼是自定義的組合控件
自定義的Web組合控件正如它名字說的那樣:在單個控件中集成了一個或多個服務端程序及HTML控件。自定義的組合控件在功能上與用戶控件非常類似,最大的不同之處是,它只存在於它自己的程序集中(或與其他控件共享),能被放在工具條中,並可提供它所包含控件的所見即所得視圖方式。
另一方面,自定義Web組合控件比用戶控件(user control)更加難創建,因為Visual Studio.NET的設計者們並沒有提供可視化創建它們的任何工具,因此,問題是:為什麼要用組合控件取代用戶控件呢?當分發控件到多個Web程序或系統中時,如果使用自定義Web組合控件,情況要好得多,而用戶控件最好用在不重視復用的地方,例如,如果只准備在你自己的網站中使用控件,那麼用戶控件可能會是更好的選擇。基本上來說,你不得不在創建它所花的額外努力與從中所得到的可復用次數之間,作一權衡;同時,因為自定義組合控件只存在於它自己的程序集中,所以在每台電腦上,只需要一份拷貝,而用戶控件則放置於Web程序集內,因此,必須存儲在每一個使用它的Web網站上。
創建一個自定義Web組合控件 創建一個自定義Web組合控件的步驟,實質上與創建一個自定義的超類Web控件一樣,本例中為SearchControl,第一件要做的事,是設計控件的外觀,完成之後,看起來大致如圖1所示。
圖1:設計器中的控件外觀
SearchControl,正如上面所看到的,由三個服務端控件組成(實際上有四個,後面將會說到):一個標簽控件、一個文本框控件、一個按鈕。另外,自定義Web組合控件中比較棘手的部分是它們並沒有一個很好的拖放設計工具以支持創建控件,而需要以老方式--手工編寫代碼來完成。但是,也不完全正確,在此不必手工編寫服務端或HTML控件代碼,那怎樣創建SearchControl的外觀呢?
首先,在SearchControl類中寫出三個服務端控件的定義:
Label *label;
TextBox *textbox;
Button *button;
接下來,在類的構造函數中創建它們的實例:
SearchControl::SearchControl()
{
label = new Label();
textbox = new TextBox();
button = new Button();
}
最後,在類的CreateChildControls()方法中,把它們添加到一個自定義Web組合控件的子控件集合裡:
void SearchControl::CreateChildControls()
{
Controls->Add(label);
Controls->Add(textbox);
Controls->Add(button);
}
CreateChildControls()方法是從Control類繼承來的一個虛方法,而WebControl也正是從Control類繼承而來。
注意,在此並不需要Render()方法,因為組成組合控件的服務端與HTML控件能繪制自身,所以,你完全不用考慮此方法,或者在Render()方法中調用基類:
void SearchControl::Render(HtmlTextWriter *output)
{
__super::Render(output);
}
現在,有了基本的外觀了,還可以添加一些功能。首先,需要一些屬性,用於更新標簽及文本框內的值,以下是屬性的定義:
[Bindable(true), Category("Appearance"), DefaultValue("")]
__property void set_Value(String *value);
__property String *get_Value();
[Bindable(true), Category("Appearance"), DefaultValue("")]
__property void set_LabelText(String *value);
__property String *get_LabelText();
[Bindable(true), Category("Appearance"), DefaultValue("")]
__property void set_ButtonText(String *value);
__property String *get_ButtonText();
代碼如下:
String *SearchControl::get_LabelText()
{
this->EnsureChildControls();
return label->Text;
}
void SearchControl::set_LabelText(String *value)
{
this->EnsureChildControls();
label->Text = value;
}
String *SearchControl::get_Value()
{
this->EnsureChildControls();
return textbox->Text;
}
void SearchControl::set_Value(String *value)
{
this->EnsureChildControls();
textbox->Text = value;
}
String *SearchControl::get_ButtonText()
{
this->EnsureChildControls();
return button->Text;
}
void SearchControl::set_ButtonText(String *value)
{
this->EnsureChildControls();
button->Text = value;
}
上述代碼最棘手的部分就是EnsureChildControls()方法,其保證了子控件在之前已經被創建,如果你不添加這個,設計器將會顯示一個空白的自定義控件。 當你運行上述代碼時,將會發現一些設計上的缺陷。首先,文本框總是同樣大小,並且不能排列多於一個SearchControl實例的標簽。為進行修正,要添加第四個服務端控件到自定義Web組合控件中,在此,表格(Table)可能是處理所有控件布局問題最好的方法:
void SearchControl::CreateChildControls()
{
System::Web::UI::WebControls::Table *table = new Table();
TableRow *row = new TableRow();
TableCell *cell1 = new TableCell();
TableCell *cell2 = new TableCell();
TableCell *cell3 = new TableCell();
cell1->Controls->Add(label);
cell2->Controls->Add(textbox);
cell3->Controls->Add(button);
row->Cells->Add(cell1);
row->Cells->Add(cell2);
row->Cells->Add(cell3);
table->Rows->Add(row);
Controls->Add(table);
}
為處理排列問題,需再再添加兩個屬性:LabelWidth和LabelAlign。LabelWidth保證了標簽控件為一特定的單位長度,而LabelAlign允許標簽使用HorizontalAlign枚舉進行排列:
[Bindable(true), Category("Appearance")]
__property void set_LabelWidth(Unit value);
__property Unit get_LabelWidth();
[Bindable(true), Category("Appearance")]
__property void set_LabelAlign(HorizontalAlign value);
__property HorizontalAlign get_LabelAlign();
現在問題又有些棘手了,為指定標簽的寬度與排列,可對表格單元格屬性進行修改,但在單元格中並不包含標簽自身。為簡化起見,創建一個名為cellLabel的私有類變量,由其取代CreateChildControls()方法中的cell1,以下是LabelWidth與LabelAlign屬性的實現代碼:
Unit SearchControl::get_LabelWidth()
{
this->EnsureChildControls();
return cellLabel->Width;
}
void SearchControl::set_LabelWidth(Unit value)
{
this->EnsureChildControls();
cellLabel->Width = value;
}
HorizontalAlign SearchControl::get_LabelAlign()
{
this->EnsureChildControls();
return cellLabel->HorizontalAlign;
}
void SearchControl::set_LabelAlign(HorizontalAlign value)
{
this->EnsureChildControls();
cellLabel->HorizontalAlign = value;
}
處理文本框的縮放時,可使它完全充滿包含其的單元格,這樣,當表格縮放時,文本框也會跟著縮放。於是,當控件縮放時,文本框會隨控件變化,或者你可以指定標簽為控件的一個百分比,如30%,這樣一來,當控件縮放時,標簽與文本框都會基於百分比進行調整,以下是修改後的CreateChildControls()方法:
void SearchControl::CreateChildControls()
{
System::Web::UI::WebControls::Table *table = new Table();
TableRow *row = new TableRow();
TableCell *cell2 = new TableCell();
TableCell *cell3 = new TableCell();
cellLabel->Controls->Add(label);
textbox->Width = Unit::Percentage(100);
cell2->Controls->Add(textbox);
cell3->Controls->Add(button);
row->Cells->Add(cellLabel);
row->Cells->Add(cell2);
cell3->Width = Unit::Percentage(1);
row->Cells->Add(cell3);
table->Rows->Add(row);
table->Width = Unit::Percentage(100);
Controls->Add(table);
}
另外,別忘了在構造函數中創建labelCell的實例,要不然可要花點時間找出為什麼設計器會給你一個大大的錯誤提示框了。
處理事件 現在,有關控件的外觀已經全部完成,是時候對控件加入一點功能了。在此,你想做的第一件事,可能就是讓按鈕控件能處理單擊事件了,我們在此使用一個事件處理函數,它就和普通的Windows與Web程序中的一樣。以下是在單擊事件中添加的事件處理函數:
button->Click += new EventHandler(this, buttonClicked);
接著,創建一個處理此事件的方法:
void SearchControl::buttonClicked(Object *sender, EventArgs *e)
{
OnClick(e);
}
看到了吧,沒有多深的科學道理,很簡單。
引發事件 如果想讓控件的用戶訪問到單擊事件,那該怎麼做呢?我們需要創建一個公共的__event,在此之上,用戶可提供他們自己的事件處理函數:
__event EventHandler* Click;
或許,你還想提供一個受保護的OnClick()方法,這樣,如果有人從SearchControl繼承,在傳回控件給SearchControl自定義Web組合控件之前或之後,他可為單擊事件提供一些額外的功能。另外,如果你很專心,那一定注意到了buttonClick()事件處理函數實際上是調用了OnClick()方法,由其把控件從按鈕傳遞到任意事件處理函數,而事件處理函數則可被附加於控件的單擊事件之上。
void SearchControl::OnClick(EventArgs *e)
{
if (Click != 0)
Click(this, e);
}
因為單擊事件是默認也是唯一的事件,也許,再添加一個DefaultEvent("Click")屬性,指定單擊為控件的默認事件,會是一個不錯的主意:
[DefaultProperty("Value"), DefaultEvent("Click"),
ToolboxData("<{0}:SearchControl
runat=server></{0}:SearchControl>")]
public __gc class SearchControl :
public System::Web::UI::WebControls::WebControl
{}
請記住,如果你修改了元數據(屬性),那必須刪除並重新引用程序集,以便元數據可被重新識別。一般而言,如果要使用此控件,只需在設計器中雙擊SearchControl控件,以創建一個事件處理函數。(下面假定使用者的代碼為C#代碼):
private void SearchControl1_Click(object sender, System.EventArgs e)
{
WebCustomControl1.Text = SearchControl1.Value;
}
在本文結束時,作一下簡單的小結,在托管C++中開發自定義Web控件,涉及了一些比較有意思的事情:首先,它創建了一個由四個服務端控件所組成的自定義Web組合控件,並演示了怎樣使用屬性來操作控件;其次,還說明了怎樣處理由控件產生的事件;最後,演示了怎樣傳遞事件給使用自定義組合控件的Web應用程序。