WinForms

【C#/WinForms実践入門編】(4)複数フォーム・モーダル/モードレス ~タイマーアプリへ設定画面を追加~

今回のテーマは「複数フォーム」です。一つのアプリで複数の画面を使い分けることで、より複雑で便利な機能を実現できます。

この記事では以下の内容を説明します。

  • WinForms複数フォームを扱う基本的な方法
  • 設定画面のような新しい画面を追加・表示する方法
  • フォーム間のデータの受け渡し

次のような方に役立つ内容となっています。

  • WinFormsでアプリ開発を始めたばかりの方
  • 複数フォームを扱いたい方(例:設定画面、確認ダイアログボックス)

実践入門編の(2),(3)で基本的なタイマーアプリを作成し、コントロールとイベントの基本やコンテナコントロールとUIレイアウト方法についても学びました。

今回は、このポモドーロタイマーアプリ設定画面を追加して、作業時間をカスタマイズできるようにする演習も行います。

WinFormsの基本から学びたい方は、まず実践入門編の(2),(3)から見てもらえると嬉しいです。

【C#/WinForms実践入門編(2)】デザイナとイベントの基本 ~はじめてのWinFormsアプリ作成~【フォーム、コントロールの使い方】 Windows Forms (WinForms) でのアプリ開発の基礎を、シンプルなタイマーの作成を通して学びます。(2025年現在最...
【C#/WinForms実践入門編】(3)コンテナコントロール・レイアウト関連プロパティを初心者向け解説 ~FlowLayoutPanel・Dockでレスポンシブデザイン~】 Windows Forms (WinForms) でのアプリ開発の基礎を、シンプルなタイマーの作成を通して学びます。 今回は、前...
プロ太

機能をフォームごとに分けることでアプリがわかりやすくなりますね。

前回学んだコンテナコントロールの知識とあわせれば、自由にアプリの画面設計ができるようになると思います!

演習コード一式はGitHubにあります。

YouTube動画は以下です。

講義:複数フォームの基本

なぜ複数フォームが必要か?

複数フォームを使う主なメリットは以下の通りです。

  1. 機能の分離
  2. UIの整理
  3. 再利用性の向上

①機能の分離

フォームごとに役割を分けることで、コードが整理され、管理しやすくなります。

例えば、タイマーのメイン画面と設定画面を別々のフォームに分けることで、それぞれのコードがシンプルになります。

②UIの整理

複数の情報を一度に表示するのではなく、必要な情報だけを別々の画面に表示することで、ユーザーインターフェース(UI)がすっきりします。

設定項目が多い場合、設定画面を別のフォームにすることで、メイン画面がごちゃごちゃするのを防ぎます。

③再利用性の向上

作成したフォームを別アプリでも再利用しやすくなります。例えば、設定画面や確認ダイアログ画面のフォームを複数のアプリで部品として使い回すことも可能になります。

モーダルとモードレス

フォームの表示方法には大きく分けて「モーダル」と「モードレス」の2種類があります。

「モーダル表示」は以下の特徴があります。

  • 表示された画面を閉じるまで、親画面(元の画面)を操作できない表示方法
  • 設定画面や確認ダイアログなど、ユーザに集中して操作してほしい画面に使用
  • FormのShowDialog()メソッドで実現

Windowsでは「名前を付けて保存」ダイアログ、「印刷」ダイアログ、エラーダイアログなど、多くの重要な操作がモーダル表示として実装されています。

プロ太

モーダルは「親画面」と「子画面」で作業モードが切り替わる(モードが存在する)、という意味ですね。

「モードレス表示」は以下の特徴があります。

  • 表示された画面を開いたまま、親画面も操作できる表示方法
  • ツールウィンドウやサブ画面など、メイン画面と並行して使用する画面に使用
  • FormのShow()メソッドで実現

例えば、プロパティウィンドウ、ツールボックス、検索ウィンドウ、音楽プレーヤーのプレイリストウィンドウなどがありますね。

プロ美

モードレスはモーダルの反対で「モードが存在しない」ってことだね!

複数フォームを使ってみる

基本的なフォーム表示

まずは新しいフォームを追加し、それをモーダルとモードレスで表示する最も基本的な例を見てみましょう。

Visual StudioでWinFormsアプリ用の新規プロジェクトを作成した後に、「SubForm」という新しいフォームを追加します。

ソリューションエクスプローラでプロジェクトを右クリックし、「追加>フォーム(Windowsフォーム)」を選びます。

今回「SubForm.cs」という名前をつけて追加します。

以下のように新しいフォーム(SubForm.cs)が追加されます。

Form1に2つのボタンを配置して、それぞれの表示方法を試してみましょう。

    private void button1_Click(object sender, EventArgs e)
    {
        // モーダル表示
        SubForm subForm = new SubForm();
        subForm.ShowDialog();  // モーダルで表示
    }

    private void button2_Click(object sender, EventArgs e)
    {
        // モードレス表示
        SubForm subForm = new SubForm();
        subForm.Show();  // モードレスで表示
    }

それぞれの動作を確認してみましょう。

プロ美

モーダル・モードレスのフォーム表示はけっこう簡単だね!これで複数のフォーム表示ができたよ。

フォーム間のデータのやり取り

次に、フォーム間でデータを受け渡しする基本的な方法を見てみましょう。

以下のようにSubFormにSetTextというデータ受け渡し用メソッドをもたせ、_receivedTextに親フォームからデータを渡せるようにします。

...  
    public partial class SubForm : Form
    {
        private string _receivedText;

        public SubForm()
        {
            InitializeComponent();
        }

        private void SubForm_Load(object sender, EventArgs e)
        {
            label1.Text = _receivedText;
        }

        public void SetText(string text)
        {
            _receivedText = text;
        }
    }
...

親フォーム側で、フォームを作成するときに以下のように値を渡します。「モーダルで表示」という文字列値を設定しています。

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

        private void button1_Click(object sender, EventArgs e)
        {
            // モーダル表示
            SubForm subForm = new SubForm();
            subForm.SetText("モーダルで表示");
            subForm.ShowDialog();  // モーダルで表示
        }
...

以下のようにモーダル画面が表示されます。

今回はメソッドを使ってデータを渡しましたが、SubFormクラスのコンストラクタ引数でデータを渡すなど他の方法もあります。

データを渡す際は、必要最小限のデータのみを渡すようにし、フォーム間の依存関係を最小限に抑えることがコードの保守性向上につながります。

プロ美

フォームも「クラス」だから、フィールド・プロパティ・メソッドとかを持たせることができるんだね。

プロ太

各フォーム(Form1、SubForm)はFormクラスを継承したクラスですね。

フォーム、コントロールは全てクラスとして定義されています。オブジェクト指向の基礎はWinForms開発でも重要ですね。

オブジェクト指向やクラスの基本についてはC#入門編における以下の記事も参考にしてください!

  • (7)クラス、メソッドによるコードの部品化 ~オブジェクト指向の土台を学ぶ~
    (記事動画1動画2)
  • (8)オブジェクト指向とは?「カプセル化」 ~部品をブラックボックスとして使えるようにする~
    (記事動画1動画2)
  • (9)オブジェクト指向とは?「継承」 ~クラスを機能拡張して再利用する~
    (記事動画1動画2)
  • (10)オブジェクト指向とは?「ポリモーフィズム(多態性)」 ~条件分岐を使わず型に応じた振る舞いをさせる~
    (記事動画1動画2)

演習:タイマーアプリに設定画面を加える

今回作るアプリと作成手順

前回作成したポモドーロタイマーに設定画面を追加してみましょう。

  • 設定画面(モーダル)で作業時間を選択できる(15分、25分、45分から選択)

以下の手順で作ります。

  • 手順1:設定画面フォームを追加して「見た目」を作成
  • 手順2:イベントとデータの受け渡しで「動作」を作成
プロ太

さきほど学んだことを活用して作ってみましょう!

手順1:設定画面フォームを追加して「見た目」を作成

設定画面

まず、新しい設定画面フォームを追加します。プロジェクトへ「SettingsForm.cs」というフォームを追加しましょう。以下のようになります。

設定画面(SettingsForm.cs)には以下のコントロールを配置し、フォーム・コントロールそれぞれプロパティを設定します。

  • フォーム
    • Name:SettingsForm
    • Text:”タイマー設定”
    • FormBorderStyle:FixedDialog
    • MaximizeBox:False
    • MinimizeBox:False
  • 作業時間ラベル(Label)
    • Text:”作業時間(分)”
  • 時間選択コンボボックス(ComboBox)
    • Name:comboBoxWorkTime
    • DropDownStyle:DropDownList
  • OKボタン (Button)
    • Name:buttonOK
    • Text:”OK”
  • キャンセルボタン (Button)
    • Name:buttonCancel
    • Text:”キャンセル”

これらの設定により、設定画面は常に一定のサイズで表示され、ユーザが誤って画面サイズを変更してしまうことを防ぎます。

以下のようになります。設定画面では画面サイズを固定し、最大・最小といったウィンドウ操作も禁止にしています。

メイン画面

メイン画面(Form1.cs)も少し修正します。設定ボタンをFlowLayoutPanelへ追加しましょう。

  • 設定ボタン(Button)

以下のようになります。フローレイアウトなので「設定ボタン」は自動的に一番下に配置されますね。

プロ美

これでメイン画面と設定画面の「見た目」部分は完成だね!

手順2:イベントとデータの受け渡しで「動作」を作成

設定画面

以下の2つのイベントを作成します。

  • OKボタン
    • Click:buttonOK_Click
  • キャンセルボタン
    • Click:buttonCancel_Click

そして、SettingsForm.csのコードを以下のように修正・追加しましょう。

namespace WinFormsApp1
{
    public partial class SettingsForm : Form
    {
        private int _workMinutes;

        public SettingsForm(int currentWorkMinutes)
        {
            InitializeComponent();

            // コンボボックスに整数値を追加
            int[] timeOptions = { 15, 25, 45 };
            foreach (int minutes in timeOptions)
            {
                comboBoxWorkTime.Items.Add(minutes);
            }

            // 現在の設定値を選択
            comboBoxWorkTime.SelectedItem = currentWorkMinutes;
            _workMinutes = currentWorkMinutes;
        }

        private void buttonOK_Click(object sender, EventArgs e)
        {
            if (comboBoxWorkTime.SelectedItem is int selectedMinutes)
            {
                _workMinutes = selectedMinutes;
                DialogResult = DialogResult.OK;
                Close();
            }
        }

        private void buttonCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
            Close();
        }

        public int GetWorkMinutes()
        {
            return _workMinutes;
        }
    }
}

設定フォームでは、ユーザがコンボボックスから15、25、45分の中から選択できます。

OKボタンで選択した時間が保存され、キャンセルボタンで変更が破棄される設計で、GetWorkMinutesメソッドを通じて他のクラスが設定された作業時間を取得できます。

なお、現在の設定値が初期表示時にコンボボックスで選択された状態になります。

プロ美

ここでは、設定画面フォームのコンストラクタの引数としてメイン画面からデータ(現在の設定値)を渡しているんだね。

メイン画面

以下のイベントを作成します。

  • 設定ボタン
    • Click:buttonSettings_Click

Form1.csのコードを以下の書き換えます。

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        private const int SecondsPerMinute = 60;
        private int _workMinutes = 25;  // 定数から変数に変更
        private bool _isRunning = false;
        private int _remainingSeconds;

        public Form1()
        {
            InitializeComponent();
            _remainingSeconds = _workMinutes * SecondsPerMinute;  // 初期化
        }

        private void buttonStartStop_Click(object sender, EventArgs e)
        {
            if (!_isRunning)
            {
                timer1.Start();
                buttonStartStop.Text = "ストップ";
            }
            else
            {
                timer1.Stop();
                buttonStartStop.Text = "スタート";
            }
            _isRunning = !_isRunning;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            _remainingSeconds--;
            if (_remainingSeconds < 0)
            {
                timer1.Stop();
                MessageBox.Show("時間になりました!");
                ResetTimer();  // リセット処理を共通メソッドに
                return;
            }

            UpdateDisplay();  // 表示更新を共通メソッドに
        }

        private void buttonReset_Click(object sender, EventArgs e)
        {
            ResetTimer();
        }

        private void buttonSettings_Click(object sender, EventArgs e)
        {
            if (_isRunning)
            {
                MessageBox.Show("タイマー動作中は設定を変更できません。");
                return;
            }

            var settingsForm = new SettingsForm(_workMinutes);
            if (settingsForm.ShowDialog() == DialogResult.OK)
            {
                _workMinutes = settingsForm.GetWorkMinutes();
                ResetTimer();
            }
        }

        // 共通処理をメソッドに抽出
        private void ResetTimer()
        {
            timer1.Stop();
            _remainingSeconds = _workMinutes * SecondsPerMinute;
            UpdateDisplay();
            buttonStartStop.Text = "スタート";
            _isRunning = false;
        }

        private void UpdateDisplay()
        {
            int minutes = _remainingSeconds / SecondsPerMinute;
            int seconds = _remainingSeconds % SecondsPerMinute;
            labelTime.Text = $"{minutes:00}:{seconds:00}";
        }
    }
}

主に以下の部分を修正・追加しています。

  • 作業時間を動的に変更できるようにするため、WorkMinutes定数を_workMinutes変数へ変更
  • 作業時間変更のため、設定フォーム呼び出しイベントを追加
  • タイマーのリセット処理と表示更新処理を共通メソッドに抽出し、コードの再利用性を向上
プロ太

設定画面の「GetWorkMinutes」メソッドを使い、ユーザが選んだ設定値を取得していますね。

アプリを実行

アプリを実行すると、以下のように設定画面で作業時間を変更できます。

プロ美

複数フォームを使ったアプリの第一歩だね!

プロ太

この演習をベースにして、他の設定項目を追加してみたり、設定画面のUIレイアウトも変えてみたりとすると理解が深まるかと思います!

まとめ

本記事では、WinFormsアプリ開発における複数フォームの活用方法について学びました。

複数フォームを使う主なメリットとして、機能の分離UIの整理再利用性の向上があります。

フォームの表示方法にはモーダル表示(ShowDialog())とモードレス表示(Show())の2種類があり、用途に応じて使い分けることができます。

フォーム間のデータのやり取りは、メソッドコンストラクタを通じて行うことができ、それぞれのフォームをクラスとして扱うことでオブジェクト指向の考え方を活かせます。

タイマーアプリに設定画面を追加する演習を通じて、実際の開発でどのように複数フォームを活用するかを学びました。

今回学んだ複数フォームの基礎を応用することで、より本格的なアプリ開発が可能になります。

プロ太

引き続き、一緒にC# WinFormsアプリ開発を学んでいきましょう!

ABOUT ME
プロ太
●仕事:現在は個人事業主(メンター・情報発信等)、大手IT企業で技術者・マネージャ(15年以上)、大学の外部講師、学生時代は学習塾で非常勤講師(約4年間) ●博士(工学)の学位取得 ●高校生の頃に独学で始め、プログラミング歴20年以上 ●言語:C# 、Java、C/C++、Python、JavaScript/TypeScript等 ●分野:Webアプリ、テスト自動化、生成AI、デバッガ、コード解析、ドメイン特化言語

ご依頼・ご相談について

プログラミング学習のご相談、お仕事のご依頼については、
こちらのお問い合わせページをご確認ください。