下拉式 vs 模態對話框編輯器
| 類型 | UI 表現 | 適用場景 | 基類返回值 |
| 模態對話框 | 彈出獨立視窗,阻塞設計器 | 複雜配置、多步驟、需大量輸入 | UITypeEditorEditStyle.Modal |
| 下拉式 | 屬性網格內下拉面板,輕量、非阻塞 | 簡單選擇、預覽、少量輸入、即時反饋 | UITypeEditorEditStyle.DropDown |
核心架構:三層協作
- 編輯器控件 (繼承
UserControl):實際 UI,處理輸入驗證、鍵盤導航 (Esc/Enter 關閉)、焦點離開提交。
- UITypeEditor 衍生類:橋樑,
GetEditStyle 返回 DropDown,EditValue 調用 IWindowsFormsEditorService.DropDownControl 顯示控件,獲取結果返回。
- 屬性聲明:
[Editor(typeof(MyDropDownEditor), typeof(UITypeEditor))] 掛載編輯器。
完整實戰:Scope 範圍屬性下拉編輯器
1. 編輯器控件 (ScopeEditorControl)
public partial class ScopeEditorControl : UserControl {
private Scope _oldScope;
private Scope _newScope;
private bool _canceling;
public ScopeEditorControl(Scope scope) {
_oldScope = scope;
_newScope = scope;
InitializeComponent(); // 包含兩個 TextBox: txtMax, txtMin
}
public Scope Scope => _newScope; // 編輯器服務讀取結果
// 輸入驗證
private void txtMax_Validating(object sender, CancelEventArgs e) {
if (!int.TryParse(txtMax.Text, out _)) { e.Cancel = true; ShowError("無效的最大值"); }
}
private void txtMin_Validating(object sender, CancelEventArgs e) {
if (!int.TryParse(txtMin.Text, out _)) { e.Cancel = true; ShowError("無效的最小值"); }
}
// 鍵盤處理:Esc 取消,Enter 確認 (由父容器處理,這裡只處理 Esc 邏輯)
protected override bool ProcessDialogKey(Keys keyData) {
if (keyData == Keys.Escape) {
_newScope = _oldScope; // 恢復原值
_canceling = true;
}
return base.ProcessDialogKey(keyData);
}
// 焦點離開 = 用戶點擊外部 = 提交 (除非剛按 Esc)
private void ScopeEditorControl_Leave(object sender, EventArgs e) {
if (!_canceling) {
_newScope.Max = int.Parse(txtMax.Text);
_newScope.Min = int.Parse(txtMin.Text);
}
}
private void ScopeEditorControl_Load(object sender, EventArgs e) {
txtMax.Text = _oldScope.Max.ToString();
txtMin.Text = _oldScope.Min.ToString();
}
}
2. 下拉式編輯器 (ScopeDropDownEditor)
public class ScopeDropDownEditor : UITypeEditor {
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
return (context?.Instance != null) ? UITypeEditorEditStyle.DropDown : base.GetEditStyle(context);
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
if (context?.Instance != null && provider != null) {
var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (editorService != null) {
var control = (MyListControl)context.Instance; // 獲取被編輯控件實例
var editorControl = new ScopeEditorControl(control.Scope); // 創建編輯器控件
editorService.DropDownControl(editorControl); // 顯示下拉面板,阻塞直到關閉
return editorControl.Scope; // 返回編輯結果
}
}
return value;
}
}
3. 掛載到屬性
[Browsable(true)]
[Editor(typeof(ScopeDropDownEditor), typeof(UITypeEditor))]
public Scope Scope {
get => _scope;
set { _scope = value; }
}
關鍵交互細節
| 事件/方法 | 觸發時機 | 處理邏輯 |
DropDownControl | 用戶點擊屬性值右側下拉按鈕 | 模態顯示控件,**阻塞 EditValue 方法**直到控件關閉 |
| 用戶按 Esc | 編輯器控件有焦點時 | ProcessDialogKey 設置 _canceling=true、恢復原值,面板關閉 |
| 用戶按 Enter | 文本框內 | 默認觸發驗證 -> 焦點移動 -> Leave 事件提交值 |
| 用戶點擊 面板外部 | 失去焦點 | 觸發 Leave 事件提交值 (若非 _canceling) |
Validating 取消 | 輸入非法 | e.Cancel=true 阻止焦點離開,用戶必須修正或按 Esc 取消 |
UI 效果展示


與模態編輯器的代碼差異 (僅 2 行)
// 模態編輯器
return UITypeEditorEditStyle.Modal;
// ...
editorService.ShowDialog(dialog); // 返回 DialogResult
// 下拉編輯器
return UITypeEditorEditStyle.DropDown;
// ...
editorService.DropDownControl(control); // 直接返回控件屬性
最佳實踐提示
- 編輯器控件保持輕量:避免複雜佈局、數據庫訪問、網絡請求,保證下拉展開瞬間響應。
- 正確處理鍵盤:Esc 取消、Enter/焦點離開提交,符合 Windows 交互慣例。
- 輸入驗證:
Validating 事件 + ErrorProvider 或 MessageBox,e.Cancel=true 防止提交無效數據。
- 狀態隔離:編輯器控件內部維護
_oldScope/_newScope,不直接修改原對象,直到提交時返回新值。
- 泛化復用:編輯器控件、UITypeEditor 可封裝為基類,新屬性只需實現具體控件與值轉換邏輯。