WinForms 開發中常見一個痛點:當輸入焦點停留在 TextBox、DataGridView 等控件上時,點擊普通 Button 會正常觸發 Leave、Validating、DataError 等驗證事件;但點擊 ToolStrip 上的項目(ToolStripButton 等)卻不會觸發焦點控件的驗證,直接執行按鈕邏輯,導致資料未驗證就提交、錯誤未攔截等嚴重問題。
ToolStrip 及其內部項目本質上是無法獲得焦點的控件(類似 Panel),點擊時不會導致當前焦點控件失去焦點,因此驗證事件鏈不會啟動。嘗試透過 Win32 API 讓 ToolStrip 模擬 Button 行為(發送同樣訊息),雖能讓焦點控件收到相同訊息,但驗證邏輯仍不觸發——說明問題不在訊息層面,而在於 WinForms 內部焦點管理機制。
採用「焦點轉移」策略:點擊 ToolStrip 時,先將焦點強制移至一個隱形、零尺寸的 Button,藉此觸發原焦點控件的驗證流程。若驗證失敗(Validating 設 Cancel=true),焦點不會轉移,後續 ToolStripButton.Click 也不會執行,完美符合預期。
public class ToolStripEx : ToolStrip
{
private readonly Button _focusDummy = new Button { Width = 0, Height = 0 };
public ToolStripEx()
{
// 視覺風格:按下狀態更明顯
ToolStripManager.VisualStylesEnabled = false;
// 隱藏拖曳抓柄
GripStyle = ToolStripGripStyle.Hidden;
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
var form = this.FindForm();
if (form != null) form.Controls.Add(_focusDummy); // 加入表單,位置自動為 (0,0)
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
_focusDummy.Focus(); // 關鍵:轉移焦點觸發驗證
}
// 移除最右邊 1px 豎線(視覺潔癖)
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SetClip(new Rectangle(0, 0, Width - 1, Height));
base.OnPaint(e);
}
}
ToolStrip 改為 ToolStripExInitializeComponent 需手動修改型別,或在建構函式中動態替換ToolStripButton.Click 事件邏輯完全保留,無需修改ErrorProvider、Validating、IDataErrorInfo 等標準驗證體系無縫協作若未來 .NET / WinForms 修復此行為,只需移除 ToolStripEx 即可平滑遷移。這是一個「小代價換大穩定」的經典工程取捨。