【WindowsForms】作成したオブジェクトに一斉に処理をさせる

2023年2月24日

最終的な目標は、複数のオブジェクトをアプリケーション実行中に作成し、そのオブジェクトに命令を送って振る舞いを行わせるものです

ここでは、順を追って達成できるように組み立てていきます

自作ボタンを作成、表示

最初からデザイン画面にコンポーネントをD&Dすることなく、自作のコンポーネントを配置するようにしましょう

デザイン

From1には、何のコンポーネントも配置しません

まず、自作のボタンを作成するところから始めます
これは、作成するオブジェクトのダミーオブジェクトになります
なので、ボタン以外のクラスでも同様に適用することができます
また、1つのクラスで複数のコンポーネントを扱いたいときは、UserControlを継承して自作のクラウを作成しましょう

コード

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

            MyButton myButton = new MyButton();
            Controls.Add(myButton);
        }
    }

    class MyButton : Button
    {
    }
}

実行結果

解説

まず、namespaceキーワードを使用して、SelectObjectSampleという名前の新しい名前空間を定義します。名前空間は、関連するクラス、インターフェイス、メソッド、および変数を含むコードのグループ化の仕組みです。

次に、public partial class Form1 : Formというクラスが定義されています。このクラスは、Windowsフォームアプリケーションのメインフォームを表します。partialキーワードは、このクラスが複数のファイルに分割されていることを示します。この場合、他のファイルにも同じクラスの一部がある可能性があります。

コンストラクター(public Form1())の中で、MyButtonという名前の新しいボタンを作成し、Controlsプロパティを使用してフォームに追加しています。これにより、新しいボタンがフォーム上に表示されます。

MyButtonという別のクラスも定義されていますが、このクラスはButtonクラスを継承しています。これにより、MyButtonクラスはButtonクラスの機能を利用できますが、必要に応じてカスタマイズすることもできます。

このコードは、簡単なWindowsフォームアプリケーションの例であり、新しいボタンをフォームに追加する方法を示しています。MyButtonクラスは、将来的にはより複雑な動作を実装することもできます。

クラス図

自作ボタンを表示(作成時に表示位置を指定)

コード

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

            MyButton myButton = new MyButton(100, 100);
            Controls.Add(myButton);
        }
    }

    class MyButton : Button
    {
        public MyButton(int x, int y)
        {
            Location= new Point(x, y);
        }
    }
}

実行結果

解説

MyButtonクラスは、コンストラクタによって、ボタンの位置を指定するために使用される整数型のxおよびy座標を受け取ります。そして、その座標をLocationプロパティに設定します。これによって、フォーム上の指定した位置に新しいボタンが表示されます。

クラス図

自作ボタンにイベントを追加

自作のボタンをクリックするたびに”選択中”と空白が交互に表示するようにしましょう
選択中かどうかは、bool型のisSelected変数に保存しておきます

デザイン

Form1には同じく何のコントロールも配置しません

コード

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

            MyButton myButton = new MyButton(100, 100);
            Controls.Add(myButton);
        }
    }

    class MyButton : Button
    {
        bool isSelected;

        public MyButton(int x, int y)
        {
            Location= new Point(x, y);
            Click += ChangeSelect;
        }

        private void ChangeSelect(object? sender, EventArgs e)
        {
            isSelected = !isSelected;
            Text = isSelected ? "選択中" : "";
        }
    }
}

実行結果

解説

MyButtonクラスには、bool型のisSelectedフィールドがあります。

また、ClickイベントにChangeSelectメソッドを登録しています。

ChangeSelectメソッドは、Clickイベントが発生するたびに呼び出されます。このメソッドでは、isSelectedフィールドの値を反転させ、Textプロパティに選択中かどうかに応じたテキストを設定しています。

つまり、このコードは、選択状態を切り替えることができるボタンを作成するためのものです。ボタンがクリックされると、選択状態が切り替わり、テキストが表示されるか消えるかが変わります。

選択切り替えの三項演算子は次のようなif文を置き換えたものです

ext = isSelected ? "選択中" : "";
if (isSelected)
{
    Text = "選択中";
}
else
{
    Text = "";
}

クラス図

フォームのボタンイベントで、登録したイベントハンドラを実行できるようにする

このサンプルでは、洗濯中のオブジェクトに削除のダミー処理を実行するケースについて考えていきます

デザイン

ここでは、あらかじめフォームに1つのボタンを置いておきます
デフォルトの名前(変数名)は、button1になります
Textは”削除”としておきましょう

コード

namespace SelectObjectSample
{
    public partial class Form1 : Form
    {
        public event EventHandler deleteHandler;
        public Form1()
        {
            InitializeComponent();

            MyButton myButton = new MyButton(100, 100);
            deleteHandler += myButton.EventSample;
            Controls.Add(myButton);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            deleteHandler.Invoke(this, EventArgs.Empty);
        }
    }

    class MyButton : Button
    {
        bool isSelected;

        public MyButton(int x, int y)
        {
            Location = new Point(x, y);
            Click += ChangeSelect;
        }

        internal void EventSample(object? sender, EventArgs e)
        {
            if (isSelected)
            {
                Text = "削除処理";
            }
        }

        private void ChangeSelect(object? sender, EventArgs e)
        {
            isSelected = !isSelected;

            Text = isSelected ? "選択中" : "";
        }
    }
}

実行結果

解説

このコードは、C#のWindowsフォームアプリケーションで、ボタンをクリックすると選択状態が切り替わるMyButtonクラスを定義しています。そして、そのMyButtonクラスのインスタンスをフォームに追加しています。

Form1クラスには、deleteHandlerというEventHandler型の変数があります。この変数には、MyButtonクラスのEventSampleメソッドを登録しています。また、ボタンをクリックすると、deleteHandlerに登録されたメソッドを呼び出すようになっています。

MyButtonクラスには、選択状態を切り替えるChangeSelectメソッドと、deleteHandlerに登録されるEventSampleメソッドが定義されています。EventSampleメソッドは、MyButtonクラスのインスタンスが選択された状態で、deleteHandlerが呼び出された場合に実行されます。このメソッドでは、選択状態であればボタンのテキストを「削除処理」と変更しています。

このコードは、MyButtonクラスのインスタンスをフォームに追加し、そのインスタンスが選択された状態でdeleteHandlerが呼び出されると、「削除処理」というテキストを表示するというサンプルアプリケーションです。

複数の処理ができるようにする

選択中のオブジェクトだけ、2種類の処理をさせるサンプルになります

デザイン

さらに1つのボタンを追加します
Textは、"色変更”としておきましょう

また、名前(変数名)は、button1をdeleteButtonにbutton2(今回作成ボタン)をcolorChangeButtonにしておきましょう
イベントハンドラの名前のそれに従って変更しておきましょう

一連の名前の変更は、VisualStudioの名前の変更で実行します

コード

namespace SelectObjectSample
{
    public enum Command
    {
        delete,
        colorChange,
    }
    public partial class Form1 : Form
    {
        public event EventHandler<Command> commandHandler;
        public Form1()
        {
            InitializeComponent();

            MyButton myButton = new MyButton(100, 100);
            commandHandler += myButton.EventSample;
            Controls.Add(myButton);
        }

        private void deleteButton_Click(object sender, EventArgs e)
        {
            commandHandler.Invoke(this, Command.delete);
        }

        private void colorChange_Click(object sender, EventArgs e)
        {
            commandHandler.Invoke(this, Command.colorChange);
        }
    }

    class MyButton : Button
    {
        bool isSelected;

        public MyButton(int x, int y)
        {
            Location = new Point(x, y);
            Click += ChangeSelect;
        }

        internal void EventSample(object? sender, Command command)
        {
            if (!isSelected)
            {
                return;
            }

            switch (command)
            {
                case SelectObjectSample.Command.delete:
                    Text = "削除処理";
                    break;
                case SelectObjectSample.Command.colorChange:
                    Text = "色変更";
                    break;
                default:
                    break;
            }
        }

        private void ChangeSelect(object? sender, EventArgs e)
        {
            isSelected = !isSelected;

            Text = isSelected ? "選択中" : "";
        }
    }
}

実行結果

解説

このコードは、C#のWindowsフォームアプリケーションで、ボタンをクリックして選択した状態で、削除処理を実行したり、色を変更したりするサンプルアプリケーションです。

Form1クラスでは、Commandという列挙型と、commandHandlerというイベントハンドラを定義しています。Form1のコンストラクタでは、MyButtonクラスのインスタンスを生成して、commandHandlerにイベントハンドラを追加しています。また、MyButtonクラスのインスタンスをフォームに追加しています。

MyButtonクラスでは、Buttonクラスを継承しています。MyButtonクラスのコンストラクタでは、ボタンの位置を設定し、ClickイベントにChangeSelectメソッドを登録しています。

EventSampleメソッドは、MyButtonクラスのインスタンスが選択状態の場合に実行され、Command列挙型の値に応じた処理を行います。Command列挙型には、deleteとcolorChangeの2つの値が定義されています。

ChangeSelectメソッドは、ボタンがクリックされたときに選択状態を切り替えます。

Form1クラスのdeleteButton_ClickメソッドとcolorChange_Clickメソッドでは、commandHandlerをInvokeメソッドで呼び出して、MyButtonクラスのインスタンスが選択状態である場合に、Command列挙型の値に応じた処理を実行します。

このコードは、イベントハンドラを使用して、複数のオブジェクト間でコマンドを送信する方法を示しています。ボタンを選択状態にし、削除処理や色変更などのコマンドを送信することができます。

デザインパターン

このコードは、イベント駆動型デザインパターンを使用しています。特に、Observerパターンに類する構造を持っています。MyButtonクラスが「Subject」として、選択状態が変化したときにイベントを発行し、Form1クラスが「Observer」として、それに応じた処理を行う構造になっています。Form1クラスは、MyButtonクラスが発行したイベントを受け取り、Command列挙型の値に応じて処理を実行することができます。このように、SubjectとObserverを分離しており、Subjectの変更に応じて複数のObserverが異なる動作をすることができます。

クラス図

自作ボタンを複数作成できるようにする

コード

namespace SelectObjectSample
{
    public enum Command
    {
        delete,
        colorChange,
    }
    public partial class Form1 : Form
    {
        public event EventHandler<Command> commandHandler;
        public Form1()
        {
            InitializeComponent();
        }

        private void deleteButton_Click(object sender, EventArgs e)
        {
            commandHandler.Invoke(this, Command.delete);
        }

        private void colorChange_Click(object sender, EventArgs e)
        {
            commandHandler.Invoke(this, Command.colorChange);
        }

        int y = 50;

        private void createButton_Click(object sender, EventArgs e)
        {
            MyButton myButton = new MyButton(50, y);
            y += 30;
            commandHandler += myButton.CommandExecHandler;
            Controls.Add(myButton);
        }
    }

    class MyButton : Button
    {
        bool isSelected;

        public MyButton(int x, int y)
        {
            Location = new Point(x, y);
            Click += ChangeSelect;
        }

        internal void CommandExecHandler(object? sender, Command command)
        {
            if (!isSelected)
            {
                return;
            }

            switch (command)
            {
                case SelectObjectSample.Command.delete:
                    Text = "削除処理";
                    break;
                case SelectObjectSample.Command.colorChange:
                    Text = "色変更";
                    break;
                default:
                    break;
            }
        }

        private void ChangeSelect(object? sender, EventArgs e)
        {
            isSelected = !isSelected;

            Text = isSelected ? "選択中" : "";
        }
    }
}

実行結果

解説

このコードは、ボタンを生成し、選択したボタンに対して削除処理や色変更を行うアプリケーションのサンプルです。

まず、Commandという列挙型を定義しています。この列挙型には、deleteとcolorChangeという2つの要素が定義されています。

次に、Form1クラスを定義しています。このクラスは、Windowsフォームアプリケーションのメインフォームとして機能します。createButton_Clickメソッドが呼び出されると、MyButtonクラスのインスタンスを生成し、フォーム上に追加します。そして、commandHandlerにmyButton.CommandExecHandlerを追加します。commandHandlerは、選択したボタンに対して実行するコマンドを受け取ります。

MyButtonクラスは、Buttonクラスを継承しています。このクラスは、ボタンの選択状態を管理し、CommandExecHandlerメソッドで選択されたボタンに対してコマンドを実行します。ChangeSelectメソッドは、ボタンがクリックされたときに、選択状態を切り替えます。

CommandExecHandlerメソッドでは、選択されたボタンに対して実行するコマンドに応じて、ボタンのテキストを変更します。deleteコマンドが渡された場合は、ボタンのテキストを「削除処理」とします。colorChangeコマンドが渡された場合は、ボタンのテキストを「色変更」とします。

以上のように、このコードは、選択されたボタンに対して実行するコマンドを管理するシンプルなアプリケーションです。

デザインパターン

このコードは、イベント駆動型デザインパターンを使用しています。特に、Observerパターンに類する構造を持っています。MyButtonクラスが「Subject」として、選択状態が変化したときにイベントを発行し、Form1クラスが「Observer」として、それに応じた処理を行う構造になっています。Form1クラスは、MyButtonクラスが発行したイベントを受け取り、Command列挙型の値に応じて処理を実行することができます。このように、SubjectとObserverを分離しており、Subjectの変更に応じて複数のObserverが異なる動作をすることができます。

また、コマンドパターンも使用しています。コマンドパターンは、操作をオブジェクトとしてカプセル化するデザインパターンの一つであり、このコードではCommand列挙型でコマンドを定義し、commandHandlerでカプセル化しています。そして、MyButtonクラスでCommandExecHandlerメソッドがコマンドを実行します。このように、コマンドパターンを使用することで、コードの拡張性や柔軟性を高めることができます。

このコードには、オブザーバーパターンとコマンドパターン以外にもいくつかのデザインパターンが使われています。

ファクトリーメソッドパターン:Form1クラスのcreateButton_Clickメソッドで、MyButtonクラスのインスタンスを生成しています。これは、ファクトリーメソッドパターンによるインスタンス生成の例です。
イテレータパターン:Form1クラスのy変数を使用して、ボタンの生成位置を設定しています。createButton_Clickメソッドで、y変数の値を更新することで、ボタンの生成位置を順次変更しています。

Mementoパターン:MyButtonクラスは、選択された状態にあるかどうかを示すisSelectedフィールドを持ちます。ChangeSelectメソッドは、選択された場合にテキストを設定することで、選択状態を視覚的に表現します。CommandExecHandlerメソッドは、deleteコマンドが呼び出されたときに、MyButtonのインスタンスを Stackに保存します。undoButton_Clickメソッドは、deleteHistoryスタックからMyButtonのインスタンスを取得し、それをフォームに再度追加することで、状態を元に戻すことができます。

クラス図