【C#・.NET Framework・WinForms】テキストボックスの内容が不適切であった場合に再度フォーカスする方法

以下のようなテキストボックス (Name: SampleTextBox) があるとして、このテキストボックスには数字しか入力できないようにするとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483756/67a2dec9-22d8-e0c1-bdc3-4b9950de19b6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483756/0507a5f2-ee0f-18cf-ced9-8d25c5a43921.png)

仮に数字以外が入力された場合、このテキストボックスに再度フォーカスが当たって入力可能になるようにするにはどうすれば良いか？ というのが今回の内容です。

以下の動画のような動きを目指します。
![完成イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483756/4802c4a7-52bd-3382-d7c6-eaeb50192782.gif)


# その1：Focus メソッドを使う
フォーカスを当てることのできる (選択可能な) コントロールには `Focus` メソッドが組み込まれています。

SampleTextBox に入力された内容が適切かどうかを条件式で判定して、もし不適切であれば `Focus` メソッドで再度フォーカスを当てる、という方法です。

```C#:C#
using System.Text.RegularExpressions;

if (!Regex.IsMatch(SampleTextBox.Text, @"^[0-9]*$"))  // 入力された内容がすべて数字かどうか判定
{
    MessageBox.Show("数字以外が入力されています");
    SampleTextBox.Focus()
}
```
簡単な方法なので初心者のうちはついつい多用してしまいがちですが、**フォームのロード時には** `Focus` **メソッドは使えない**ので注意が必要です。

また、[公式ドキュメント](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.focus?view=windowsdesktop-7.0)には `Focus` メソッドが使えないコントロールが挙げられています。
>次の一覧のWindows フォーム コントロールは選択できません。 これらのコントロールから派生したコントロールも選択できません。
>
>Panel
>
>GroupBox
>
>PictureBox
>
>ProgressBar
>
>Splitter
>
>Label
>
>LinkLabel (コントロールにリンクがない場合)

# その2：テキストボックスをアクティブにする
「テキストボックスにフォーカスを当てる」ということは、厳密にはテキストボックスをアクティブにすることに他なりません。なので、入力内容が不適切であると判定した際に、テキストボックスをアクティブにしてやると良さそうです。

コントロールに組み込まれている `Select` メソッドをつかって、テキストボックスをアクティブにすることができます。
```C#:C#
using System.Text.RegularExpressions;

if (!Regex.IsMatch(SampleTextBox.Text, @"^[0-9]*$"))  // 入力された内容がすべて数字かどうか判定
{
    MessageBox.Show("数字以外が入力されています");
    SampleTextBox.Select()
}
```
`Focus` メソッドとほぼ変わりません。
`Select` メソッドで使えないコントロールも[公式ドキュメント](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.select?view=windowsdesktop-7.0)に記載されていますが、こちらも `Focus` メソッドと同じでした。

## Select メソッドと Focus メソッドの違い
### 戻り値
公式ドキュメントによると、`Select` メソッドの定義は以下の通りです。
>コントロールをアクティブにします。
> ```C#:C#
> public void Select ();
> ```

`Focus` メソッドの定義は以下の通りです。
>コントロールをアクティブにします。
> ```C#:C#
> public bool Focus ();
> ```

`Focus` メソッドには `bool` 型の戻り値が設定されています。`Select` メソッドはコントロールに正常にフォーカスを当てることができても特に何か返すわけではないが、`Focus` の場合はフォーカスを当てることができたら `true`、できなければ `false` を返してくれるみたいです。

### フォームのロード時に使えるかどうか
一方、`Select` メソッドはフォームのロード時に使うことができます。
```C#:C#
public class SampleForm: Form 
{
  public void SampleForm_Load(object sender, EventArgs e)
  {
    SampleTextBox.Focus();  // 機能しない
    SampleTextBox.Select();  // フォーカスが当たる
    this.ActiveControl = SampleTextBox;  // フォーカスが当たる
  }
}
```
本題とは少しずれますが、フォームが開いた際に最初からフォーカスが当たっている状態にしたい、といったときに便利です。

また、親であるフォームの `ActiveControl` プロパティにテキストボックスをセットすることでも、テキストボックスをアクティブにすることができます (`ActiveControl` プロパティと `Select` メソッドはほぼ同じ動きをします)。

# その3：テキストボックスの Validating イベントを使う
テキストボックスには `Validating` イベントを追加できます。これはテキストボックスなどのコントロールから別のコントロールに移る際に呼び出されるイベントで、呼び出された際にイベントとしてセットされたメソッドの処理が実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483756/a3f206e3-a497-41e9-bc3a-ba183ee99382.png)
上記の `Validating` という項目をダブルクリックすると、対応するメソッドが自動で生成されます。とても便利。

再度テキストボックスにフォーカスを与える処理は以下のようになります。
```C#:C#
SampleTextBox_Validating(object sender, CancelEventArgs e)
{
  Textox tb = (TextBox)sender;
  if (!Regex.IsMatch(tb.Text, @"^[0-9]*$"))  // 入力された内容がすべて数字かどうか判定
  {
    e.Cancel = true;
  }
}
```
`Validating` イベントではメソッドに `CancelEventArgs` 型の値が渡されます。この値の `Cancel` プロパティを `true` にしてあげると、別コントロールへのフォーカス遷移をキャンセルしてくれます。

つまり、テキストボックスに数字以外の文字が入力されていた場合、送信ボタンを押したとしても内容が送信されず、送信ボタンを押す前の状態 (テキストボックスの入力待ち状態) に戻る、というわけです。ジョジョのGERみたい。

ただし、こいつにも弱点があります。それは、「**遷移先の** `CausesValidation` **プロパティが** `false` **なら、遷移元の** `Validating` **イベントは無視される**」という点です。
テキストボックスが入力待ちであっても他のボタンを押したい、というときに `CausesValidation` プロパティは役に立つのですが、送信ボタンなど「テキストボックスの中身が正しくないと選択しちゃダメ」みたいなコントロールの `CausesValidation` プロパティは極力いじらないのが良さそうです。

# 参考
`CausesValidation` プロパティを使った一例を取り扱っています。

https://qiita.com/ritto1101/items/4877422877317d732d01