たきるブログ

C#やOracleなどの情報を書いています。

【C#】フォーカスの遷移順にコントロールを取得する

個人的に、画面に貼り付けられている全コントロールを、フォーカス遷移順に取得したかった。
調べまくった。

参考サイト

コントロールのTabIndex順で処理したいことってあるよね。階層のTabIndexを表現してみよう。 - Bug Catharsis
ここに記されている方法は、SplitContainerが絡むと、希望する結果にはならない。
試していないけど、結局、階層関係が絡んでくるとダメな気がする。

解決策

結局、APIなんて使わずに自前で取得していった方が確実な気がしたので、それで解決した。
いちいちめんどくさいロジックにもならない。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TabFocusTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            var controls = this.GetAllControls();
            foreach (var control in controls)
            {
                Console.WriteLine(control.Name);
            }
        }

        /// <summary>
        /// フォームに存在するすべてのコントロールをフォーカス遷移順に取得します。
        /// </summary>
        /// <param name="excludeInvisible">true:非表示項目を取得しない, false:非表示項目を取得する</param>
        /// <param name="excludeDisable">true:無効項目を取得しない, false:無効項目を取得する</param>
        /// <returns>コントロールリスト</returns>
        public List<Control> GetAllControls(bool excludeInvisible = true, bool excludeDisable = true)
        {
            List<Control> allControls;
            getAllControls(this, this, excludeInvisible, excludeDisable, out allControls);

            return allControls;
        }

        /// <summary>
        /// フォーム上のすべてのコントロールを取得する。
        /// </summary>
        /// <param name="targetControl">次のコントロールを取得する対象オブジェクト</param>
        /// <param name="currentControl">手前のコントロールオブジェクト</param>
        /// <param name="excludeInvisible">true:非表示項目を取得しない, false:非表示項目を取得する</param>
        /// <param name="excludeDisable">true:無効項目を取得しない, false:無効項目を取得する</param>
        /// <param name="allControls">取得したすべてのコントロールオブジェクト</param>
        /// <returns></returns>
        private Control getAllControls(Control targetControl, Control currentControl,
                bool excludeInvisible, bool excludeDisable, out List<Control> allControls)
        {
            allControls = new List<Control>();

            while (true)
            {
                var nextControl = targetControl.GetNextControl(currentControl, true);
                if (nextControl == null)
                {
                    return currentControl;
                }
                if (excludeInvisible && !nextControl.Visible)
                {
                    currentControl = nextControl;
                    continue;
                }
                if (excludeDisable && !nextControl.Enabled)
                {
                    currentControl = nextControl;
                    continue;
                }
                allControls.Add(nextControl);

                if (nextControl.Controls.Count > 0)
                {
                    var innerControls = new List<Control>();
                    nextControl = getAllControls(nextControl, nextControl,
                            excludeInvisible, excludeDisable, out innerControls);
                    if (innerControls.Count > 0)
                    {
                        allControls.AddRange(innerControls);
                    }
                }
                currentControl = nextControl;
            }
        }
    }
}

Form.GetAllControls()を呼べば、それで済む。
GetNextControl()メソッドを使っているので、恐らく問題ない。
Visibled, Enabledプロパティの影響もあるので、選択できるように。(デフォルトは、操作が可能なコントロールのみ)
MenuStripだのDataGridViewだののItemsプロパティとかで管理されてる中身は取ってこない。
SplitContainer.Panel1オブジェクトとかも取得するので、取得した結果、どうこうするのは呼び元の制御のお話。

あと、再帰の関係でエレガントさに欠けるけど、まぁ別にいいや。
時間があったらもうちょっと考え直すレベル。