◀ 2.

C#「トップレベルのコントロールをコントロールに追加できません」の対処

▶
編集
この記事の要点
  • Windows フォームで Form を別のコントロールの中に入れようとしたときに出る
  • 原因は Form.TopLevel が true のこと。トップレベルのウィンドウは入れ子にできない
  • 対処は form.TopLevel = false; にしてから Controls.Add() する
  • 枠が二重に見えるので FormBorderStyle = None と Dock = Fill も併せて指定する
  • そもそも画面の一部として使い回すなら UserControl にするのが本来の設計

エラーの内容

System.ArgumentException:
  'トップレベルのコントロールをコントロールに追加できません。'

(英語環境)
System.ArgumentException:
  'Top-level control cannot be added to a control.'
// 例外が出るコード
var child = new ChildForm();
panel1.Controls.Add(child);      // ← ここで ArgumentException

なぜ起きるのか

Windows フォームの Form は、既定で TopLevel = true(=自分がウィンドウそのもの)です。トップレベルのウィンドウは OS が直接管理するため、別のコントロールの子要素にはできません。

クラスTopLevel他のコントロールに入れられるか
Formtrue(既定)できない
UserControlfalseできる
Panel Button などの通常のコントロールfalseできる

対処 1: TopLevel を false にする

private void ShowChild()
{
    var child = new ChildForm();

    child.TopLevel = false;                       // 入れ子にできるようにする
    child.FormBorderStyle = FormBorderStyle.None; // タイトルバーと枠を消す
    child.Dock = DockStyle.Fill;                  // 親いっぱいに広げる

    panel1.Controls.Add(child);
    child.Show();                                 // ← これを忘れると表示されない
    child.BringToFront();
}

Show() の呼び忘れが次のつまずきです。Controls.Add() しただけでは Visible が false のままで、何も表示されません。

入れ替えるとき

private Form? _current;

private void SwitchTo(Form next)
{
    // 前の画面を確実に片付ける(残すとメモリを食い続ける)
    if (_current != null)
    {
        panel1.Controls.Remove(_current);
        _current.Dispose();
    }

    next.TopLevel = false;
    next.FormBorderStyle = FormBorderStyle.None;
    next.Dock = DockStyle.Fill;

    panel1.Controls.Add(next);
    next.Show();
    _current = next;
}

Remove() だけでは破棄されません。Dispose() まで呼ばないと、画面を切り替えるたびにフォームが増え続けます。

対処 2: UserControl にする(推奨)

// 画面の一部として使い回すなら UserControl が正しい
public partial class UserListControl : UserControl
{
    public UserListControl()
    {
        InitializeComponent();
    }
}

// 使う側
var view = new UserListControl { Dock = DockStyle.Fill };
panel1.Controls.Add(view);      // TopLevel の設定は不要

Visual Studio では プロジェクトを右クリック → 追加 → ユーザー コントロール で作れます。デザイナーで部品を並べられる点は Form と同じです。

やりたいこと使うもの
独立したウィンドウとして開くForm + Show() / ShowDialog()
画面の一部として埋め込むUserControl
既存の Form を作り直さず埋め込むTopLevel = false(応急処置)
複数の子ウィンドウを親の中で管理するMDI(IsMdiContainer)
タブで切り替えるTabControl + UserControl

対処 3: MDI を使う

// 親フォーム
public MainForm()
{
    InitializeComponent();
    IsMdiContainer = true;
}

private void OpenChild()
{
    var child = new ChildForm();
    child.MdiParent = this;      // TopLevel は変更しない
    child.Show();
}

MDI では MdiParent を設定するだけで、Controls.Add() は使いません。MdiParent と TopLevel = false を同時に使うと例外になります。

関連するエラー

エラー原因
トップレベルのコントロールをコントロールに追加できません。Form を Controls.Add() した(本記事)
フォームが MDI 親フォームの場合、TopLevel を false に設定できませんIsMdiContainer = true の親に TopLevel = false を設定した
コントロールをそれ自体に追加することはできません自分自身を Controls.Add() している
ObjectDisposedExceptionDispose() 済みのフォームを再度 Show() した
追加したのに何も表示されないShow() の呼び忘れ/Dock と Size の指定漏れ

埋め込んだフォームの後始末

// 埋め込んだフォームで Close() を呼ぶと Dispose されて再表示できない
child.Close();
child.Show();          // ObjectDisposedException

// 一時的に隠すだけなら Hide()
child.Hide();
child.Show();          // 再表示できる

// 完全に破棄するとき
panel1.Controls.Remove(child);
child.Dispose();
child = null;

Form.Close() はそのフォームを破棄します(モーダル表示の ShowDialog() を除く)。埋め込んだフォームを使い回すなら Hide() にしてください。

親子で値をやり取りする

// 子側: イベントで親に知らせる
public partial class ChildForm : Form
{
    public event EventHandler<string>? Selected;

    private void listBox1_DoubleClick(object sender, EventArgs e)
    {
        Selected?.Invoke(this, listBox1.SelectedItem?.ToString() ?? "");
    }
}

// 親側: 受け取る
var child = new ChildForm();
child.Selected += (s, value) => textBox1.Text = value;

child.TopLevel = false;
child.FormBorderStyle = FormBorderStyle.None;
child.Dock = DockStyle.Fill;
panel1.Controls.Add(child);
child.Show();

子から親のコントロールを直接触らないでください。イベントで知らせる形にすると、子を別の画面でも使い回せます。

画面の切り替え方の選択肢

やり方向いている場面注意点
Panel + UserControl最も素直。メニューで画面を切り替える破棄を忘れない
TabControl並列に見比べる画面タブが増えると重くなる
MDI同じ種類の画面を複数開く(帳票など)見た目が古く感じられる
ShowDialog()入力を確定させたい(設定・確認)閉じるまで親を操作できない
Show()(別ウィンドウ)並べて作業するウィンドウの管理が要る
// モーダルで開いて結果を受け取る
using var dialog = new SettingsForm();
if (dialog.ShowDialog(this) == DialogResult.OK)
{
    ApplySettings(dialog.Settings);
}
// ShowDialog は自動で Dispose されないので using を付ける

デザイナーで起きる場合

フォームのデザイナー上で他のフォームをドラッグしたときにも同じ例外が出ます。この場合はコードではなくデザイナーの操作が原因なので、ツールボックスから UserControl を配置する形に直してください。

1. UserControl を追加してビルドする
2. ツールボックスの先頭に、そのプロジェクトのコントロールが表示される
3. デザイナーにドラッグして配置する

ビルドしないとツールボックスに出てこないため、作成したら 1 度ビルドしてください。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 【Visual Studio】Form自動生成時に「値が有効な範囲にありません」エラー
  2. トップレベルのコントロールをコントロールに追加できません。
  3. 指定された名前のソリューションファイルが既に存在するため、ソリューション名を変更できません