Utils/TabIndexHelper.cs

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)
                .Where(c => c.Visible)
                .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)
                .Where(c => c.Visible)
                .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;
                
                // 読み取り専用のテキストボックスも Tab で移動可能にする
                if (control is TextBox textBox && textBox.ReadOnly)
                {
                    textBox.TabStop = true;
                }
            }
        }
        
        /// <summary>
        /// 送信者情報マスタ画面のTabIndexを設定
        /// </summary>
        public static void SetSenderMasterTabOrder(Form form, Control senderInfoControl)
        {
            // nullチェック
            if (form == null || senderInfoControl == null) return;

            // 送信者情報コントロール内のコントロールを位置でソート
            var controls = GetAllControls(senderInfoControl)
                .Where(c => c is TextBox || c is Button)
                .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;
            }
        }
    }
}