Button.PerformClick() の問題を回避する

色々と考えてはみたが、今のボタンの PerformClick() メソッドの動きは SDK/BCL の不具合としてよさそうだ。
というわけで、回避するためのボタンクラスは以下のような実装でいけそうだと思われる。

  /// <summary>
  /// System.Windows.Forms.Button の PerformClick() メソッドが CausesValidation
  /// プロパティが false の時に、マウス操作以外によってボタンを実行(クリック)
  /// した際に ContainerControl.Validate() を呼び出してしまい、コンテナ全体の検
  /// 証機能が期待したように動作しない問題を修正します。
  /// </summary>
  /// <remarks>
  /// == 問題の再現手順 ==
  ///  - 新規に Windows Forms アプリケーションを作成。
  ///  - TextBox を1つ、Button を2つ配置。
  ///  - Form1.AcceptButton に button1、Form1.CancelButton に button2 を設定。
  ///  - button2.CausesValidation に false を設定。
  ///  - textBox1.Validating イベントハンドラを作成、e.Cancel = true とする。
  ///    * 実行するとマウス操作ではフォームを閉じることができる。
  ///    * 実行するとキー操作 (ESC キー) ではフォームを閉じることができない。
  /// </remarks>
  public class Button : System.Windows.Forms.Button, System.Windows.Forms.IButtonControl
  {
    /// <summary> ボタンの Click イベントを生成します。 </summary>
    public new void PerformClick()
    {
#if false
      if (this.CausesValidation)
      {
        base.PerformClick();
      }
      else 
#endif
      if (this.CanSelect)
      {
        this.OnClick(EventArgs.Empty);
      }
    }

    // 同名の internal メソッドと同じ機能(のはず)
    protected bool CanProcessMnemonic()
    {
      Control c = this;
      while (c.Enabled && c.Visible)
      {
        c = c.Parent;
        if (c == null) return true;
      }

      return false;
    }

    /// <summary> ニーモニック文字を処理します。 </summary>
    protected override bool ProcessMnemonic(char charCode)
    {
      if (this.CanProcessMnemonic() && Control.IsMnemonic(charCode, this.Text))
      {
        this.PerformClick();
        return true;
      }

      return base.ProcessMnemonic(charCode);
    }

    void System.Windows.Forms.IButtonControl.PerformClick()
    {
      this.PerformClick();
    }
  }

途中の #if false の部分は微妙にあやしいが、PerformClick() メソッドで自発的に Validate() を呼ばなければならないシナリオがすぐに発見できなかったので、2重チェックを防ぐためにカットしてある。
この部分を有効化する場合、1回のボタン操作で2回の Validate() が発生するので、データベースのユニークキーのような要素の自動修正を Validating イベントで発行しているような場合などに注意する必要がある。


どうでもいいが、SDK のヘルプでは PerformClick() メソッドは virtual だと書いてあるが、nonvirtual だぞ(苦笑