【C#の小技】Formの上の全コントロールを再帰的に取得する【Visual Studio】

この記事は約24分で読めます。
スポンサーリンク

以前、別の記事で、Windows Formアプリにおいて、groupBox内にあるradioButtonのどれがチェックされているのかを調べる手法を紹介した。

その時に紹介した手法では、チェック状態を取得したいラジオボタンを保持しているグループボックス名を、groupBox1やgroupBox2のように自分で指定してやる必要があった。

でも、それだと不便だ。

 

例えばForm1の上に物凄く沢山のコントロールを配置した場合に、グループボックスが一個や二個じゃなくて何十個も有るかもしれない。

グループボックスの名前もgroupBox1、groupBox2 と言った分かり易い名前では無いかも知れない。

また、グループボックスがネスト(入れ子)になっているかもしれない。

そんなややこしい状況では、一々どのグループボックスかなどと指定するのも面倒。

そう言う時には、取り敢えずForm1の上にある全グループボックスを取得したい。

その結果を使って、そのグループボックス内のラジオボタンでチェックされているものを取得すると手っ取り早い。

あるいは、グループボックスは関係なく、兎に角チェック済のラジオボタンを全部取得したい。

今回は、その二種類の手法を紹介したい。

スポンサーリンク
ワテ推薦のプログラミングスクールで学ぶ
スポンサーリンク
スポンサーリンク

Formの上にある全コントロールを再帰的に取得する方法

C#のWindows FormアプリにおいてForm上のコントロールを全部取得する為には、再帰的に探索するのが正統的な手法のようだ。

自分で書いてみるのも勉強にはなると思うが、ネットを検索するとその手の手法は沢山のサンプルコードが有る。

例えば、StackOverflowで見付けたやつ。

// StackOverflow版
public IEnumerable<Control> GetSelfAndChildrenRecursive(Control parent)
{
    List<Control> controls = new List<Control>();
 
    foreach (Control child in parent.Controls)
    {
        controls.AddRange(GetSelfAndChildrenRecursive(child));
    }
 
    controls.Add(parent);
 
    return controls;
}

引用元 http://stackoverflow.com/questions/13933120/how-to-get-all-control-list-of-the-form-in-c

このコードの動作は、再帰処理に慣れている人ならそんなに難しくな無いと思うが、再帰処理を書いた事が無い人向けに説明してみる。

使い方はこんな感じ。

var allControlLst = GetSelfAndChildrenRecursive(this);

今の場合、thisはForm1になる。

まず、関数の引き数でthisつまりForm1を与えてコールする。

そうすると、parent変数がForm1になり、foreachループの所で、parent.Controlsを求めているので、それはFom1の上にある子コントロールの集合だ。孫コントロールは含まない。

具体的にはparent.Controlsは、二つのグループボックスと三つのボタンの集合になる(注:この後で出て来る実行例のようなForm1を作成した場合)。

その五つのコントロールをchild変数に一個ずつ取り出して、再帰的に GetSelfAndChildrenRecursive(child)を実行して、その戻り値をAddRangeでcontrols変数に保管する。

ここが再帰処理の部分。

例えばchildがbutton1の場合には、再帰によってコールされたGetSelfAndChildrenRecursive()の引数parentがbutton1になる。

そうすると、parent.Controlsはnullになるので、foreachは実行されずに、次の行

    controls.Add(parent);
 
    return controls;

が実行されるので、結局、controls変数にはbutton1が保管される。

それをリターンするので、呼び出し元のcontrols変数にはそのbutton1が追加される。

注意点としては、今出て来た二つのcontrols変数は同じ物ではない。関数内で定義しているローカル変数なので、再帰実行する度に新しい変数として登場する。

一方、childがgroupBox1の場合には、同じく再帰でコールされたforeachのparent.Controlsの部分がつまり groupBox1.Controlsとなり、それはradioButton1~radioButton4の集合になる。そうすると、その四つに対して再び再帰実行が行われる。

以降、これの繰り返し。

のように、言葉で再帰処理を説明しても、分り辛い。ワテの説明が下手なのも理由ではあるが。

と言う事で、初めて再帰関数を実行する人は、デバッガーでステップイン(F11)を使いながら地道に追っかけて行くのが良い。

最初は、こんがらがって訳分からなくなると思うが、慣れて来ると大して難しくはない。

特定のTypeを持つコントロールのみを再帰取得する方式に改造する

さて、上記のStackOverflow版だと指定した親コントロールの子供、孫、ひ孫など全コントロールを再帰取得する。

これを改造して、特定の種類のコントロールを再帰的に取得する方式にしてみよう。

例えばラジオボタンのみを取得するなど。

グループボックスとかラジオボタンなど、コントロールの種類はType型で区別できる。

その改造版がこれだ。

public IEnumerable<Control> GetSelfAndChildrenRecursive2(Control parent, Type type_find)
{
    /*
     
    【機能】  指定したparent例えばform1の上の全コントロールを再帰的に探索して
                指定したType属性のコントロールのみを取り出す
    【使い方】 var result2a = GetSelfAndChildrenRecursive2(this, typeof(GroupBox));
    【説明】  StackOverflow版のGetSelfAndChildrenRecursive()を改造した。
 
    */
 
    List<Control> controls = new List<Control>();
 
    foreach (Control child in parent.Controls)
    {
        if (child.GetType() == type_find)
        {
            controls.Add(child);
        }
 
        controls.AddRange(GetSelfAndChildrenRecursive2(child, type_find));
    }
 
    return controls;
}

使い方としては、以下の通り。

第二番目の引数を追加して、そこで特定のコントロール型を指定する。

// GroupBoxのみを取得する
var groupBoxLst = GetSelfAndChildrenRecursive2(this, typeof(GroupBox));

これでthisつまりForm1上の全GroupBoxが取得出来る。仮にGroupBoxがネスト(入れ子)になっていても正しく取得出来る。

これで得られたGroupBoxのリストを使って、冒頭で紹介した別記事の手法で各GroupBox上にあるRadioButtonを取り出して、その中でチェックされているものを取り出せば良い。

 

あるいは、RadioButtonがGroupBoxのどこに属しているかなどは気にせずに直接RadioButtonを再帰的に全取得する事も可能だ(下図)。

var radioButtonLst = GetSelfAndChildrenRecursive2(this, typeof(RadioButton));
Console.WriteLine("{0}の上には{1}個のRadioButtonがあります。", 
       this.Name, radioButtonLst.Count());
 
var radioButtonCheckedLst = radioButtonLst.Select(rb => rb)
        .Where(rb => ((RadioButton)rb).Checked).ToList();
Console.WriteLine("そのうちチェックされているものは{0}個の有りました。",
        radioButtonCheckedLst.Count());

それで得られた全ラジオボタンradioButtonLst の中からChecked==trueなものを取り出す処理を行えばチェック済のラジオボタンが得られる。

この例ではLINQを使って取り出している。このLINQでやっている処理は、SelectとWhereの使い方の典型的な例なので、分かり易いと思う。

さて、これで当初の予定通り、Form1上にGroupBoxが何個あっても、あるいはRadioButtonが何個あっても、チェック済のラジオボタンのみを取り出す事が出来る。

チェック済のラジオボタンを再帰的に取り出す全C#コード

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 _2017_04_07_GetAllControlsOnForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
 
            MyConsoleClass.set_pos_size(xpos: 100, ypos: 0, wid: 120, hei: 60);
 
        }
 
        static class MyConsoleClass
        {
            public const int SWP_NOSIZE = 0x0001;
            [DllImport("kernel32.dll", ExactSpelling = true)]
            public static extern IntPtr GetConsoleWindow();
            private static IntPtr MyConsole = GetConsoleWindow();
 
            // dllの中のSetWindowsPos()関数を使う
            [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
            public static extern IntPtr SetWindowPos(
                IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
 
            public static void set_pos_size(int xpos, int ypos, int wid, int hei)
            {
                // Consoleウインドウの位置とサイズ指定する。バッファーサイズはmaxにした。
 
                SetWindowPos(MyConsole, 0, xpos, ypos, 0, 0, SWP_NOSIZE);
                Console.SetBufferSize(Console.BufferWidth, 32766);// バッファーサイズを最大化しておく
                Console.SetWindowSize(wid, hei);//(120, 60);
            }
        }
 
 
        // StackOverflow版
        // http://stackoverflow.com/questions/13933120/how-to-get-all-control-list-of-the-form-in-c?noredirect=1&lq=1
        public IEnumerable<Control> GetSelfAndChildrenRecursive(Control parent)
        {
            /*
            
            【機能】  指定したparent例えばform1の上の全コントロールを再帰的に全取得する。自分自身も。
            【使い方】 var result = GetSelfAndChildrenRecursive(this);
             
            */
 
            List<Control> controls = new List<Control>();
 
            foreach (Control child in parent.Controls)
            {
                controls.AddRange(GetSelfAndChildrenRecursive(child));
            }
 
            controls.Add(parent);
 
            return controls;
        }
 
        public IEnumerable<Control> GetSelfAndChildrenRecursive2(Control parent, Type type_find)
        {
            /*
             
            【機能】  指定したparent例えばform1の上の全コントロールを再帰的に探索して
                        指定したType属性のコントロールのみを取り出す
            【使い方】 var result2a = GetSelfAndChildrenRecursive2(this, typeof(GroupBox));
            【説明】  StackOverflow版のGetSelfAndChildrenRecursive()を改造した。
 
            */
 
            List<Control> controls = new List<Control>();
 
            foreach (Control child in parent.Controls)
            {
                if (child.GetType() == type_find)
                {
                    controls.Add(child);
                }
 
                controls.AddRange(GetSelfAndChildrenRecursive2(child, type_find));
            }
 
            return controls;
        }
 
        // LINQ版だ
        // http://stackoverflow.com/questions/15186828/loop-through-all-controls-on-a-form-even-those-in-groupboxes
        private static List<Control> AllSubControls(Control control)
        {
            var list = control.Controls.OfType<Control>().ToList();
            var deep = list.Where(o => o.Controls.Count > 0).SelectMany(AllSubControls).ToList();
            list.AddRange(deep);
            return list;
        }
 
 
 
        private void button3_Click(object sender, EventArgs e)
        {
 
            Console.WriteLine("【方法1】 グループボックスを探して次にラジオボタンを探す方式");
            {
                // 再帰的に全コントロールを取得する。
                var allControlLst = GetSelfAndChildrenRecursive(this);
                Console.WriteLine("{0}の上には{1}個のコントロールがあります({0}自身も含む)", this.Name, allControlLst.Count());
 
                // GroupBoxのみを取得する
                var groupBoxLst = GetSelfAndChildrenRecursive2(this, typeof(GroupBox));
                Console.WriteLine("GroupBoxは{0}個の有りました。", groupBoxLst.Count());
 
                foreach (var groupBox in groupBoxLst)
                {
                    var radioButtonChecked_InGroup = groupBox.Controls.OfType<RadioButton>()
                                .SingleOrDefault(rb => rb.Checked == true);
 
                    // 結果
                    Console.Write("{0}では、", groupBox.Name);
                    if (radioButtonChecked_InGroup == null)
                    {
                        Console.WriteLine("どのボタンもチェックされとらん。");
                    }
                    else
                    {
                        Console.WriteLine("チェックされているボタンの文字列 = {0}", radioButtonChecked_InGroup.Text);
                    }
 
                }
            }
 
            Console.WriteLine("【方法2】 一気に全部のチェックRadioButtonを取得する方式");
            {
                var radioButtonLst = GetSelfAndChildrenRecursive2(this, typeof(RadioButton));
                Console.WriteLine("{0}の上には{1}個のRadioButtonがあります。", this.Name, radioButtonLst.Count());
 
                var radioButtonCheckedLst = radioButtonLst.Select(rb => rb).Where(rb => ((RadioButton)rb).Checked).ToList();
                Console.WriteLine("そのうちチェックされているものは{0}個の有りました。", radioButtonCheckedLst.Count());
 
 
                foreach (var rb in radioButtonCheckedLst)
                {
                    Console.WriteLine("チェックされているボタンの文字列 = {0}", rb.Text);
                }
            }
 
 
            Console.WriteLine("【参考】 LINQ方式で全部のコントロールを再帰的に取得する方法");
            {
                var allControlLst2 = AllSubControls(this);//.OfType<TextBox>()
                Console.WriteLine("{0}の上には{1}個のコントロールがあります({0}自身は含まない)", this.Name, allControlLst2.Count());
                for (int i = 0; i < allControlLst2.Count; i++)
                {
                    Console.WriteLine("i={0} 名前:{1}", i, allControlLst2[i].Name);
                }
            }
 
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Console.Clear();
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }// class Form1
 
}// ns

実行に必要なものは、このコードと、あとはForm1の上に下図のようにgroupBoxを適当に何個か配置して、その中に適当にラジオボタンなどを入れておけば良い。

もちろんそれ以外のどんなコントロールを配置しても良い。

下段の三つのボタンはbutton1、button2、button3と言う名前だが、もちろんどんな名前でも良い。

ただし名前を変える場合には、例えばbutton3なら、そのクリックのイベントハンドラ関数の名前、

       private void button3_Click(object sender, EventArgs e) 

が対応するようにして下さい。

 

実行例

プログラムを実行するとこんな画面が出る。

同時にコンソールウインドウも出るようにしている。

その理由は、Formアプリでもデバッグ時はコンソールウインドウも出しておきたい。

その辺りの手法はこの記事やこの記事で解説しているので参考にして頂きたい。

[実行ボタン]をクリックする

適当にラジオボタンの選択状態を変更して、[実行ボタン]をクリックするとコンソールウインドウには以下のような出力が行われる。

【方法1】 グループボックスを探して次にラジオボタンを探す方式
 Form1の上には19個のコントロールがあります(Form1自身も含む)
 GroupBoxは3個の有りました。
 groupBox2では、チェックされているボタンの文字列 = radioButton6
 groupBox3では、チェックされているボタンの文字列 = radioButton12
 groupBox1では、チェックされているボタンの文字列 = radioButton1
 【方法2】 一気に全部のチェックRadioButtonを取得する方式
 Form1の上には12個のRadioButtonがあります。
 そのうちチェックされているものは3個の有りました。
 チェックされているボタンの文字列 = radioButton12
 チェックされているボタンの文字列 = radioButton6
 チェックされているボタンの文字列 = radioButton1
 【参考】 LINQ方式で全部のコントロールを再帰的に取得する方法
 Form1の上には18個のコントロールがあります(Form1自身は含まない)
 i=0 名前:button2
 i=1 名前:button1
 i=2 名前:button3
 i=3 名前:groupBox2
 i=4 名前:groupBox1
 i=5 名前:groupBox3
 i=6 名前:radioButton8
 i=7 名前:radioButton7
 i=8 名前:radioButton6
 i=9 名前:radioButton5
 i=10 名前:radioButton9
 i=11 名前:radioButton10
 i=12 名前:radioButton11
 i=13 名前:radioButton12
 i=14 名前:radioButton4
 i=15 名前:radioButton3
 i=16 名前:radioButton2
 i=17 名前:radioButton1

二種類の方式でチェック済ラジオボタンを全部取得する事が出来た。

 

MyConsoleClassの説明

static class MyConsoleClass

の部分は今回の処理とは本質的には無関係。

何をしているかと言うと、実行時にコンソールウインドウが開くようにしたが、デフォルトだと毎回その位置が勝手に変わるのでで煩わしい。

それを固定位置にして大きさも指定しておくと便利なので、それを行う小細工処理。

      MyConsoleClass.set_pos_size(xpos: 100, ypos: 0, wid: 120, hei: 60);

この行を実行すると指定位置に指定大きさでコンソールウインドウを表示出来る。
ワテの場合、この小細工はデバッグに便利なのでよく使う。

 

LINQ方式で再帰的に全コントロールを取得する事も可能

上記のコードの末尾に追加した以下のサンプルコードはStackOverflowで見付けたやつだ。

LINQ方式で再帰的に全コントロールを取得するやり方だ。

参考までに掲載したので試して頂きたい。

        // LINQ版だ
        // http://stackoverflow.com/questions/15186828/loop-through-all-controls-on-a-form-even-those-in-groupboxes
        private static List<Control> AllSubControls(Control control)
        {
            var list = control.Controls.OfType<Control>().ToList();
            var deep = list.Where(o => o.Controls.Count > 0).SelectMany(AllSubControls).ToList();
            list.AddRange(deep);
            return list;
        } 

ワテの場合、LINQはまだ初心者レベルではあるが、最近LINQに凝っている。

LINQに凝りだすと何でもかんでもLINQで書きたくなる。

まるでLINQ症候群と呼んでも良いかも知れない。

forループなどのややこしい記述を必死でLINQに置き換える事が出来ると、ある種の達成感がある。まさにLINQ中毒状態なのだが、その結果、後で見直してもサッパリ分からないと言う難点がある。

でも、いつかそれを克服してLINQが自然に書けて自然に理解出来るように成りたい。

まとめ

Visual Studio のC#でWindows Formアプリを作成する場合に役立つ手法を紹介した。

具体的には以下の通り。

  • 指定したコントロール上の全コントロールを再帰的に取得する方法。
  • 指定したコントロール上の全コントロールのうち特定のTypeを持つもののみを再帰的に取得する方法。
  • Windows Formアプリにおいてコンソールウインドウを表示する方法。
  • コンソールウインドウの位置や大きさを指定する方法。
  • コンソールウインドウをクリアする方法。

などである。

この辺りの手法を覚えておくと、C#でコンソールアプリを作る場合でもFormアプリを作る場合でも、何かと役立つと思うので皆さんにも利用して頂きたい。

C#関連本を読む

本格的に言語を勉強するなら取り敢えずその言語の関する本を一冊読むのが良いだろう。

ネット情報だけで勉強していると断片的な知識しか得られないので良く忘れる(ワテの場合)。

一方、教科書を一冊全部を読み通せば体系的に理解出来るので、全体像が把握し易い。

どちらもアマゾンレビューの評価が高いのでお勧めの本かも知れない。

でもワテは読んでいない。高いので。

 

フリーランスエンジニアを目指す人にお勧め

 

インターノウス株式会社のプロエンジニア

 

 

 

 

取り敢えず無料みたいなので登録してみると良いかも。

スポンサーリンク
ワテ推薦のプログラミングスクールで学ぶ
コメント募集

この記事に関して何か質問とか補足など有りましたら、このページ下部にあるコメント欄からお知らせ下さい。

C#
スポンサーリンク
warekoをフォローする
スポンサーリンク
われこ われこ

コメント