マスタ画面Tabフォーカス順制御実装手順書.md

# マスタ画面Tabフォーカス順制御実装手順書

## 概要
マスタ画面全般において、Tabキーを押したときのフォーカス順を、基本的に左上から右下へ順番に遷移するように制御します。

## 現状の問題点
- Tabキーを押したときのフォーカス順が論理的でない可能性がある
- ユーザーが期待する順序でフォーカスが移動しない
- 操作性が低下している

## 修正方針

### 目標
1. 左上から右下への自然な流れでフォーカスが移動
2. すべてのマスタ画面で統一されたフォーカス順
3. ユーザーにとって直感的な操作性

### 実装方法
各コントロールのTabIndexプロパティを適切に設定します。

## TabIndex設定の基本ルール

### フォーカス順の優先順位
1. **検索条件エリア**(最上部)
   - 検索条件の入力フィールド(左から右へ)
   - 検索ボタン
2. **検索結果エリア**(中央部)
   - DataGridView
3. **操作ボタンエリア**(最下部)
   - 戻るボタン
   - 新規作成ボタン

### TabIndex値の割り当て方針
- 0~99: 検索条件エリア
- 100~199: 検索結果エリア
- 200~299: 操作ボタンエリア

## 実装手順

### ステップ1: TabIndex設定ヘルパークラスの作成

#### 1.1 TabIndexHelper.csの作成

```csharp
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;

namespace ChatworkBulkSender.Utils
{
    /// <summary>
    /// マスタ画面のTabIndex(フォーカス順)を設定するヘルパークラス
    /// </summary>
    public static class TabIndexHelper
    {
        /// <summary>
        /// マスタ検索画面のTabIndexを設定
        /// </summary>
        public static void SetMasterScreenTabOrder(Form form,
            Control searchBoxControl,
            Control dgvControl,
            Control btnControl)
        {
            // nullチェック
            if (form == null || searchBoxControl == null ||
                dgvControl == null || btnControl == null) return;

            // 検索ボックス内のコントロールにTabIndexを設定
            SetSearchBoxTabOrder(searchBoxControl, 0);

            // DataGridViewのTabIndexを設定
            SetDataGridViewTabOrder(dgvControl, 100);

            // ボタンコントロールのTabIndexを設定
            SetButtonTabOrder(btnControl, 200);
        }

        /// <summary>
        /// 検索ボックス内のコントロールのTabIndexを設定
        /// </summary>
        private static void SetSearchBoxTabOrder(Control searchBoxControl, int startIndex)
        {
            var controls = GetAllControls(searchBoxControl)
                .Where(c => c is TextBox || c is ComboBox || c is CheckBox || c is Button)
                .OrderBy(c => c.Location.Y)
                .ThenBy(c => c.Location.X)
                .ToList();

            int tabIndex = startIndex;
            foreach (var control in controls)
            {
                control.TabIndex = tabIndex++;
                control.TabStop = true;
            }
        }

        /// <summary>
        /// DataGridViewのTabIndexを設定
        /// </summary>
        private static void SetDataGridViewTabOrder(Control dgvControl, int tabIndex)
        {
            var dgv = FindControl<DataGridView>(dgvControl);
            if (dgv != null)
            {
                dgv.TabIndex = tabIndex;
                dgv.TabStop = true;
            }
        }

        /// <summary>
        /// ボタンコントロールのTabIndexを設定
        /// </summary>
        private static void SetButtonTabOrder(Control btnControl, int startIndex)
        {
            var buttons = GetAllControls(btnControl)
                .Where(c => c is Button)
                .OrderBy(c => c.Location.X)
                .ToList();

            int tabIndex = startIndex;
            foreach (var button in buttons)
            {
                button.TabIndex = tabIndex++;
                button.TabStop = true;
            }
        }

        /// <summary>
        /// コントロール内のすべての子コントロールを取得
        /// </summary>
        private static IEnumerable<Control> GetAllControls(Control parent)
        {
            var controls = new List<Control>();

            foreach (Control control in parent.Controls)
            {
                controls.Add(control);
                controls.AddRange(GetAllControls(control));
            }

            return controls;
        }

        /// <summary>
        /// 特定の型のコントロールを検索
        /// </summary>
        private static T FindControl<T>(Control parent) where T : Control
        {
            foreach (Control control in parent.Controls)
            {
                if (control is T typedControl)
                {
                    return typedControl;
                }

                T found = FindControl<T>(control);
                if (found != null) return found;
            }
            return null;
        }

        /// <summary>
        /// 個別編集画面のTabIndexを設定
        /// </summary>
        public static void SetIndividualEditScreenTabOrder(Form form)
        {
            // nullチェック
            if (form == null) return;

            // すべてのコントロールを位置でソート
            var controls = GetAllControls(form)
                .Where(c => c is TextBox || c is ComboBox || c is CheckBox || 
                           c is RadioButton || c is Button || c is DataGridView)
                .Where(c => c.Visible) // 表示されているコントロールのみ
                .OrderBy(c => c.Location.Y)
                .ThenBy(c => c.Location.X)
                .ToList();

            // TabIndexを設定
            int tabIndex = 0;
            foreach (var control in controls)
            {
                control.TabIndex = tabIndex++;
                control.TabStop = true;
            }
        }
    }
}
```

### ステップ2: 各マスタ画面への適用

#### 2.1 M_CustomerMaster.csの修正

```csharp
private void M_CustomerMaster_Load(object sender, EventArgs e)
{
    // 既存のコード...
    
    // TabIndexの設定(フォーカス順制御)
    TabIndexHelper.SetMasterScreenTabOrder(
        this,
        _searchBoxControl,
        _customerDgvControl,
        _btnControl);
}
```

#### 2.2 M_SendPatternMaster.csの修正

```csharp
private void M_SenderPatternMaster_Load(object sender, EventArgs e)
{
    // 既存のコード...
    
    // TabIndexの設定(フォーカス順制御)
    TabIndexHelper.SetMasterScreenTabOrder(
        this,
        _patternSearchBoxControl,
        _pattrernDgvControl,
        _btnControl);
}
```

#### 2.3 M_CustomerMasterIndividualEdit.csの修正

```csharp
private void M_CustomerMasterIndividualEdit_Load(object sender, EventArgs e)
{
    // 既存のコード...
    
    // TabIndexの設定(フォーカス順制御)
    TabIndexHelper.SetIndividualEditScreenTabOrder(this);
}
```

### ステップ3: 特殊なケースの対応

#### 3.1 非表示コントロールの除外

非表示のコントロールはTabStopをfalseに設定:

```csharp
if (!control.Visible)
{
    control.TabStop = false;
}
```

#### 3.2 読み取り専用コントロールの扱い

読み取り専用のテキストボックスなどは、TabStopを維持するかどうかを検討:

```csharp
if (control is TextBox textBox && textBox.ReadOnly)
{
    // 読み取り専用でもTabで移動できるようにする場合
    textBox.TabStop = true;
}
```

## テストシナリオ

### シナリオ1: 基本的なTab順の確認
1. マスタ画面を開く
2. Tabキーを押して、フォーカスが以下の順序で移動することを確認:
   - 検索条件の各入力フィールド(左から右、上から下)
   - 検索ボタン
   - DataGridView
   - 戻るボタン
   - 新規作成ボタン

### シナリオ2: Shift+Tabでの逆順移動
1. 最後のコントロールにフォーカスがある状態でShift+Tabを押す
2. 逆順でフォーカスが移動することを確認

### シナリオ3: 個別編集画面でのTab順
1. 個別編集画面を開く
2. Tabキーで全てのフィールドを順番に移動できることを確認

## 実装の注意点

### 1. 動的に生成されるコントロール
実行時に動的に生成されるコントロールがある場合は、生成後にTabIndexを設定する必要があります。

### 2. ユーザーコントロール内のTabIndex
ユーザーコントロール内部のコントロールも適切にTabIndexを設定する必要があります。

### 3. DataGridView内のTab移動
DataGridViewでは、セル間の移動もTabキーで行われるため、特別な考慮が必要な場合があります。

## 実装チェックリスト

### TabIndexHelper.cs
- [ ] クラスの作成
- [ ] SetMasterScreenTabOrderメソッドの実装
- [ ] SetSearchBoxTabOrderメソッドの実装
- [ ] SetDataGridViewTabOrderメソッドの実装
- [ ] SetButtonTabOrderメソッドの実装
- [ ] SetIndividualEditScreenTabOrderメソッドの実装

### 各マスタ画面
- [ ] M_CustomerMaster.csへの適用
- [ ] M_SendPatternMaster.csへの適用
- [ ] M_SenderMaster.csへの適用
- [ ] M_CustomerMasterIndividualEdit.csへの適用
- [ ] M_SendPatternIndividualEdit.csへの適用

### 動作確認
- [ ] Tab順が左上から右下へ自然に流れること
- [ ] すべての入力可能なコントロールにTabで到達できること
- [ ] Shift+Tabで逆順に移動できること
- [ ] 非表示のコントロールがスキップされること

## 追加の推奨事項

### 1. アクセシビリティの向上
TabIndexの設定と併せて、各コントロールにAccessibleNameやAccessibleDescriptionを設定することで、スクリーンリーダー対応も向上します。

### 2. Enterキーでの次フィールド移動
必要に応じて、EnterキーでもTabキーと同様に次のフィールドに移動する機能を実装できます。

### 3. フォーカス時の視覚的フィードバック
フォーカスが当たったコントロールの背景色を変更するなど、視覚的なフィードバックを提供することも検討できます。

## まとめ

この実装により、マスタ画面でのTab操作が直感的になり、キーボードのみでの操作性が大幅に向上します。ユーザーは左上から右下への自然な流れでフォーカスを移動できるようになり、効率的な操作が可能になります。