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