ASP.NET 自訂組合控件將多個伺服器控件封裝為單一單元,支援設計器拖曳、屬性視窗編輯、工具箱整合與 WYSIWYG 預覽。雖然開發門檻高於 User Control(需手寫程式碼、無視覺化設計器),但編譯後僅需部署單一組件(.dll),跨專案複用極為便利。本文以 SearchControl 為例,完整演示 Managed C++ 開發流程。
WebControl 並實作 INamingContainer#pragma once
using namespace System;
using namespace System::Web::UI;
using namespace System::Web::UI::WebControls;
[DefaultProperty("Value"), DefaultEvent("Click"),
ToolboxData("<{0}:SearchControl runat=server>{0}:SearchControl>")]
public __gc class SearchControl : public WebControl {
// 子控件宣告
Label* label;
TextBox* textbox;
Button* button;
TableCell* cellLabel; // 用於版面配置控制
public:
SearchControl() {
label = new Label();
textbox = new TextBox();
button = new Button();
cellLabel = new TableCell();
}
// 必須覆寫:建立子控件樹
void CreateChildControls() override {
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->Width = Unit::Percentage(1); // 按鈕欄位最小寬度
cell3->Controls->Add(button);
row->Cells->Add(cellLabel);
row->Cells->Add(cell2);
row->Cells->Add(cell3);
table->Rows->Add(row);
table->Width = Unit::Percentage(100);
Controls->Add(table);
}
// Render 直接委派基類,子控件自行繪製
void Render(HtmlTextWriter* output) override {
__super::Render(output);
}
EnsureChildControls 確保子控件就緒
// LabelText 屬性
[Bindable(true), Category("Appearance"), DefaultValue("")]
__property String* get_LabelText() {
EnsureChildControls();
return label->Text;
}
__property void set_LabelText(String* value) {
EnsureChildControls();
label->Text = value;
}
// Value 屬性(綁定至 TextBox)
[Bindable(true), Category("Appearance"), DefaultValue("")]
__property String* get_Value() {
EnsureChildControls();
return textbox->Text;
}
__property void set_Value(String* value) {
EnsureChildControls();
textbox->Text = value;
}
// ButtonText 屬性
[Bindable(true), Category("Appearance"), DefaultValue("")]
__property String* get_ButtonText() {
EnsureChildControls();
return button->Text;
}
__property void set_ButtonText(String* value) {
EnsureChildControls();
button->Text = value;
}
// 版面控制屬性
[Bindable(true), Category("Appearance")]
__property Unit get_LabelWidth() {
EnsureChildControls();
return cellLabel->Width;
}
__property void set_LabelWidth(Unit value) {
EnsureChildControls();
cellLabel->Width = value;
}
[Bindable(true), Category("Appearance")]
__property HorizontalAlign get_LabelAlign() {
EnsureChildControls();
return cellLabel->HorizontalAlign;
}
__property void set_LabelAlign(HorizontalAlign value) {
EnsureChildControls();
cellLabel->HorizontalAlign = value;
}
// 建構子中綁定按鈕事件
SearchControl() {
// ... 初始化子控件 ...
button->Click += new EventHandler(this, &SearchControl::buttonClicked);
}
void buttonClicked(Object* sender, EventArgs* e) {
OnClick(e); // 觸發對外事件
}
// 受保護虛擬方法,允許衍生類擴充
protected: void OnClick(EventArgs* e) {
if (Click != 0) Click(this, e);
}
// 對外公開的事件
public: __event EventHandler* Click;
};
屬性 [DefaultProperty("Value"), DefaultEvent("Click")] 讓設計器雙擊控件自動產生 Click 事件處理函式。修改中繼資料後須「移除參考→重新加入參考」使設計器重新載入。
使用端程式碼 (C#):
private void SearchControl1_Click(object sender, System.EventArgs e) {
WebCustomControl1.Text = SearchControl1.Value;
}
| 技術點 | Managed C++ 實作要點 |
|---|---|
| 子控件建立 | 覆寫 CreateChildControls,使用 Controls->Add 建立樹狀結構 |
| 版面配置 | 採用 Table/TableRow/TableCell 控制對齊與自適應寬度 |
| 屬性封裝 | __property + EnsureChildControls() 確保子控件已建立 |
| 事件包裝 | 內部處理子控件事件 → 呼叫 OnXxx → 觸發 __event |
| 設計器整合 | ToolboxData、DefaultProperty、DefaultEvent 屬性 |
| 部署優勢 | 單一 .dll、GAC 部署、多專案共用、版本控管容易 |
Managed C++ 雖已被 C++/CLI 取代,但其組合控件開發模式仍具參考價值:封裝複雜度、對外簡潔介面、設計器友善、部署獨立,是企業級控件庫開發的標準範式。