Разработка приложений на C# в среде Visual Studio 2005

Динамическая компоновка формы

Разбить на страницы
Показывать лекцию целиком

Проблема с размещением элементов управления

Визуальные средства проектирования формы (режим Designer ) хороши тем, что программисту не нужно вычислять местоположение и размер элементов управления в пределах клиентской области формы. Он это делает визуально. Но в то же время мы не знаем, на каких компьютерах и при каких условиях будет работать наше приложение. Это зависит от следующих факторов:

  • Приложение может работать на разных языках и тексты могут выступать за границы элементов управления и формы
  • На мониторах может быть установлено разное разрешение
  • Разные версии операционной системы могут по разному отображать элементы управления
  • Наиболее эффективный метод решения этих проблем - так спроектировать приложение, чтобы элементы управления и форма сами подстраивались под нестандартные условия именно во время выполнения программы, автоматически вычисляя свой размер и местоположение. Ниже рассматриваются средства, простые и более сложные, для динамической компоновки интерфейса пользователя.

    Свойство AutoSize

    Все элементы управления, включая форму, являются наследниками класса Control, в котором имеется булево свойство AutoSize. По умолчанию значение этого свойства равно false, но если изменить его на true, то элемент управления сам будет менять свои размеры в зависимости от содержимого. Следующие элементы имеют дополнительное уточняющее свойство AutoSizeMode:

  • System.Windows.Forms.Button.AutoSizeMode
  • System.Windows.Forms.DataGridViewColumn.AutoSizeMode
  • System.Windows.Forms.Form.AutoSizeMode
  • System.Windows.Forms.GroupBox.AutoSizeMode
  • System.Windows.Forms.Panel.AutoSizeMode
  • System.Windows.Forms.UserControl.AutoSizeMode
  • Это свойство ожидает одно из значений одноименного перечисления

  • System.Windows.Forms.AutoSizeMode.GrowAndShrink - растягиваться и сжиматься
  • System.Windows.Forms.AutoSizeMode.GrowOnly (по умолчанию) - только растягиваться
  • Пример размещения одной кнопки

    В следующем примере на форме размещается одна кнопка, внутри которой имеется многострочный текст. Настройка свойств кнопки и самой формы дает им возможность подстраиваться под размеры своего содержимого: кнопка подстраивается под размер текста, форма подстраивается под размер кнопки.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()    // Конструктор
            {
                // Настройка формы
                // Заголовок окна
                this.Text = "Автоподстройка размещения";
                // Включили автоподстройку формы
                this.AutoSize = true;
                // Пусть форма растягивается и сжимается
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создание и настройка объекта кнопки
                Button btn = new Button();
                // Привязали к форме
                btn.Parent = this;
                // Включили автоподстройку кнопки
                btn.AutoSize = true;
                // Задаем содержимое кнопки
                btn.Text =
                    "У Лукоморья дуб зеленый" + Environment.NewLine +
                    "Златая цепь на дубе том" + Environment.NewLine +
                    "И днем и ночью кот ученый" + Environment.NewLine +
                    "Все ходит по цепи кругом";
                // Задаем шрифт
                btn.Font = new Font("Arial", 16, FontStyle.Underline);
                // Задаем расположение текста
                btn.TextAlign = ContentAlignment.TopLeft;
                // Задаем цвет текста
                btn.ForeColor = Color.Red;
                // Задаем стиль кнопки
                btn.FlatStyle = FlatStyle.Flat;
                // Цвет рамки кнопки
                btn.FlatAppearance.BorderColor = Color.Blue;
                // Ширина рамки кнопки
                btn.FlatAppearance.BorderSize = 15;
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());// Создание объекта формы
            }
        }
    }

    Вот результат выполнения примера

    Обратите внимание, что при установке

    this.AutoSizeMode = AutoSizeMode.GrowAndShrink;

    возможность интерактивного изменения размеров формы отключается.

    Пример размещения двух кнопок

    Теперь попробуем разместить на форме две кнопки. Для тренировки упакуем уже разработанную кнопку в отдельный класс с именем MyButton, а вторую разместим как обычно и без лишних дополнительных настроек. Код программы будет таким

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс расширения кнопки
        class MyButton : Button
        {
            public MyButton()    // Конструктор
            {
            // Настройка класса расширения кнопки
            // Включили автоподстройку кнопки
            this.AutoSize = true;
            // Задаем содержимое кнопки
            this.Text =
              "У Лукоморья дуб зеленый" + Environment.NewLine +
              "Златая цепь на дубе том" + Environment.NewLine +
              "И днем и ночью кот ученый" + Environment.NewLine +
              "Все ходит по цепи кругом";
            // Задаем шрифт
            this.Font = new Font("Arial", 16, FontStyle.Underline);
            // Задаем расположение текста
            this.TextAlign = ContentAlignment.TopLeft;
            // Задаем цвет текста
            this.ForeColor = Color.Red;
            // Задаем стиль кнопки
            this.FlatStyle = FlatStyle.Flat;
            // Цвет рамки кнопки
            this.FlatAppearance.BorderColor = Color.Blue;
            // Ширина рамки кнопки
            this.FlatAppearance.BorderSize = 15;
            }
        }
        
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()    // Конструктор
            {
                // Настройка формы
                // Заголовок окна
                this.Text = "Автоподстройка размещения двух кнопок";
                // Включили автоподстройку формы
                this.AutoSize = true;
                // Установили режим: пусть форма растягивается и сжимается
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создание и настройка объекта первой кнопки
                Button btnFirst = new Button();
                // Привязали первую кнопку к форме
                btnFirst.Parent = this;
                // Включили автоподстройку первой кнопки
                btnFirst.AutoSize = true;
                // Задаем содержимое первой кнопки
                btnFirst.Text = "Хороши весной в 
        саду цветочки" + Environment.NewLine +
                    "Еще лучше девушки весной";
                // Все остальное для первой кнопки по умолчанию ...
        
                // Создание и настройка объекта второй кнопки
                MyButton btnSecond = new MyButton();
                // Привязали вторую кнопку к форме
                btnSecond.Parent = this;
                // Позиционируем вторую кнопку справа от первой
                btnSecond.Location = new Point(btnFirst.Right, 0);
                // Все остальное для второй кнопки настроено в расширении ...
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());// Создание объекта формы
            }
        }
    }

    Результат выполнения примера будет таким

    В классе Control есть событие Layout (размещение), которое генерируется всякий раз, когда родителю необходимо переразместить свои дочерние элементы управления. В обработчике этого события мы можем руками пересчитать новые позиции элементов управления и их размеры. Для решения той же задачи класс Control имеет виртуальное свойство LayoutEngine (механизм размещения), в реализацию которого можно заложить свою логику динамического размещения дочерних элементов управления.

    Имеются, также, специальные классы динамического размещения:

  • System.Windows.Forms.FlowLayoutPanel - панель плавающего размещения дочерних элементов управления
  • System.Windows.Forms.TableLayoutPanel - панель табличного размещения дочерних элементов управления
  • Панели и контейнеры

    Все потомки класса Control наследуют от него свойство Controls, в котором можно хранить ссылки на дочерние элементы управления. Иными словами, любой элемент управления способен быть контейнером других элементов. Но обычно в качестве контейнеров выступают такие элементы, как формы и панели. Именно они компонуют пользовательский интерфейс.

    Далее рассмотрим избранную иерархию классов, связанную с решением задачи динамического размещения элементов интерфейса:

    Стыковка дочерних элементов (свойство Dock)

    Стыковка элементов управления в клиентской области родителя обеспечивается значением их свойства Dock, которое ожидает одно из значений перечисления System.Windows.Forms.DockStyle:

    Значения перечисления DockStyle
    None = 0 Нестыкуемая (по умолчанию)
    Top = 1 Пристыковать сверху
    Bottom = 2 Пристыковать снизу
    Left = 3 Пристыковать слева
    Right = 4 Пристыковать справа
    Fill = 5 Развернуть на всю клиентскую область

    Если контейнер содержит несколько элементов управления и если в одном из них свойству Dock присвоено значение, отличное от None, то и другим элементам нужно присвоить отличное от None значение. И только один элемент может иметь значение Fill. Иначе элементы будут перекрываться.

    Дочерние элементы в контейнере выстраиваются в Z -последовательность в порядке их добавления в контейнер с помощью свойства Parent самого элемента управления или методом Controls.Add() контейнера. В таком же порядке они и отображаются. Изменить место элемента в Z -последовательности можно методами BringToFront() и SendToBack(). Если элементы Z -последовательности перекрываются, то элемент с меньшим индексом будет располагаться сверху.

    Если элементы пристыковываются к одному краю контейнера, то каждый новый добавленный в контейнер элемент будет пристыковываться непосредственно к краю, оттесняя к центру клиентской области контейнера ранее пристыкованные к этому краю элементы. Поэтому вначале нужно дабавить к контейнеру элемент с параметром Dock=Fill, а потом пристыковывать элементы к краям родителя.

    Вот простая программа, иллюстрирующая размещение элементов управления в зависимости от порядка их добавления в родительский контейнер.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
                this.Text = "Схематичный Блокнот";
        
                // Создаем многострочный TextBox
                TextBox textBox = new TextBox();
                textBox.Parent = this;
                textBox.Multiline = true;
                textBox.Dock = DockStyle.Fill;// Добавляется первым
        
                // Создаем объект дерева
                TreeView tree = new TreeView();
                tree.Parent = this;// Один способ
                tree.Dock = DockStyle.Left;// Оттесняет первого слева
                tree.Nodes.Add("tree");// Добавили узел
        
                // Создаем панель инструментов
                ToolStrip tool = new ToolStrip();
                this.Controls.Add(tool);// Другой способ
                tool.Items.Add("tool");// Оттесняет другие сверху
                   
                // Создаем меню
                MenuStrip menu = new MenuStrip();
                this.Controls.Add(menu);// Другой способ
                menu.Items.Add("menu");// Оттесняет другие сверху
        
                // Создаем панель состояния
                StatusStrip status = new StatusStrip();
                status.Parent = this;// Один способ
                status.Items.Add("status");// Оттесняет другие снизу
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Результат выполнения такой

    Красными цифрами показаны индексы элементов в коллекции Form.Controls, соответствующие порядку их добавления. Если в коде изменить порядок добавления элементов управления в контейнер формы, то размещение элементов станет другим.

    Стыковка с применением панельных контейнеров

    Для более сложного алгоритма размещения можно использовать элемент управления Panel как контейнер, дочерний по отношению к форме. Этот элемент служит именно для компоновки своих дочерних элементов управления как единой группы. Он хорош тем, что имеет встроенные полосы прокрутки и настраиваемую рамку. Но в обычном элементе Panel нужно явно позиционировать элементы управления или использовать стыковку, которую мы и рассматриваем. Более развитыми с точки зрения размещения являются другие панели: FlowLayoutPanel, TableLayoutPanel, SplitContainer.

    Ниже повторен предыдущий пример с применением сдвоенной панели SplitContainer, в которой пользователь может интерактивно перемещать внутреннюю границу.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
                this.Text = "Применение панелей";
        
                // Создаем двухпанельный элемент с изменяемой границей
                SplitContainer split = new SplitContainer();
                split.Parent = this;// Привязали к форме
                split.Dock = DockStyle.Fill;
                split.Orientation = Orientation.Vertical;// По умолчанию
                // Блокируем автоматическое изменение левой панели
                split.FixedPanel = FixedPanel.Panel1;
        
                // Создаем многострочный TextBox в правой панели
                TextBox textBox = new TextBox();
                split.Panel2.Controls.Add(textBox);// Привязали к правой панели
                textBox.Multiline = true;
                textBox.Dock = DockStyle.Fill;// Распахнули
                // Задаем начальный текст
                textBox.Text = "Буря мглою небо 
        кроет" + Environment.NewLine
                    + "Вихри снежные крутя" + Environment.NewLine
                    + "То как зверь она завоет\r\n"
                    + "То заплачет как дитя\r\n\r\n"
                    + "Привет всем!";
                textBox.WordWrap = false;// Отключаем перенос слов
        
                // Создаем объект дерева в левой панели
                TreeView tree = new TreeView();
                split.Panel1.Controls.Add(tree);// Привязали к левой панели
                tree.Dock = DockStyle.Fill;// Распахнули
                tree.Nodes.Add("tree");// Добавили узел
        
                // Создаем панель инструментов
                ToolStrip tool = new ToolStrip();
                this.Controls.Add(tool);
                tool.Items.Add("tool");// Оттесняет другие сверху
                   
                // Создаем меню
                MenuStrip menu = new MenuStrip();
                this.Controls.Add(menu);// Другой способ
                menu.Items.Add("menu");// Оттесняет другие сверху
        
                // Создаем панель состояния
                StatusStrip status = new StatusStrip();
                status.Parent = this;// Один способ
                status.Items.Add("status");// Оттесняет другие снизу
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Результат выполнения будет таким

    Теперь между деревом и текстом есть изменяемая граница шириной 4 пиксела по умолчанию (задается свойством SplitterWidth ). Когда меняются размеры окна, то и размеры панелей, составляющих объект класса SplitContainer, тоже пропорционально меняются. Чтобы отключить автоматическое изменение одной из панелей, нужно задать свойству SplitContainer.FixedPanel одно из значений Panel1 или Panel2 одноименного перечисления FixedPanel (по умолчанию установлено значение None ).

    Стыковка с применением составных разделителей

    Можно создавать более сложные панельные разделители, помещая внутрь составляющих панелей новые объекты SplitContainer и делая составляющие панели родительскими для этих элементов. Подобным образом построено окно Object Browser оболочки. Например

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
            this.Text = "Составные разделители";
        
            // Создаем родительский двухпанельный элемент
            SplitContainer splitParent = new SplitContainer();
            splitParent.Parent = this;// Привязали к форме
            splitParent.Dock = DockStyle.Fill;
            splitParent.Orientation = Orientation.Vertical;
            splitParent.SplitterWidth = 2;// Толщина разделителя
        
            // Создаем объект дерева в левой панели
            TreeView tree = new TreeView();
            splitParent.Panel1.Controls.Add(tree);// Привязали к левой панели
            tree.Dock = DockStyle.Fill;// Распахнули
            tree.Nodes.Add("tree");// Добавили узел
        
            // В правую панель помещаем дочерний двухпанельный элемент
            SplitContainer splitChild = new SplitContainer();
            splitChild.Parent = splitParent.Panel2;
            splitChild.Dock = DockStyle.Fill;
            splitChild.Orientation = Orientation.Horizontal;
            splitChild.SplitterWidth = 2;// Толщина разделителя
            // Позиционируем разделитель как 2/3 к 1/3 от верха
            splitChild.SplitterDistance = splitChild.Size.Height
                    - splitChild.Size.Height / 3;
        
            // Создаем многострочный TextBox в верхней дочерней панели
            TextBox textBox1 = new TextBox();
            textBox1.Parent = splitChild.Panel1; // Привязали
            textBox1.Multiline = true;
            textBox1.Dock = DockStyle.Fill;// Распахнули
            // Задаем начальный текст
            textBox1.Text = "Буря мглою небо кроет" + 
        Environment.NewLine
                    + "Вихри снежные крутя" + Environment.NewLine
                    + "То как зверь она завоет\r\n"
                    + "То заплачет как дитя\r\n\r\n";
            textBox1.WordWrap = false;// Отключаем перенос слов
        
            // Создаем многострочный TextBox в нижней дочерней панели
            TextBox textBox2 = new TextBox();
            textBox2.Parent = splitChild.Panel2; // Привязали
            textBox2.Multiline = true;
            textBox2.Dock = DockStyle.Fill;// Распахнули
            // Задаем начальный текст
            textBox2.Text = "Привет всем!";
            textBox2.WordWrap = false;// Отключаем перенос слов
        
            // Создаем панель инструментов
            ToolStrip tool = new ToolStrip();
            this.Controls.Add(tool);
            tool.Items.Add("tool");// Оттесняет другие сверху
        
            // Создаем меню
            MenuStrip menu = new MenuStrip();
            this.Controls.Add(menu);// Другой способ
            menu.Items.Add("menu");// Оттесняет другие сверху
        
            // Создаем панель состояния
            StatusStrip status = new StatusStrip();
            status.Parent = this;// Один способ
            status.Items.Add("status");// Оттесняет другие снизу
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат

    Теперь пользователь может интерактивно менять вертикальную и горизонтальную границы панелей.

    Привязка дочерних элементов (свойство Anchor - якорь)

    Свойство Anchor замораживает расстояние дочернего элемента управления от соответствующего края родительского контейнера при изменении размеров последнего. Это свойство может принимать одно из значений или их побитовую комбинацию перечисления AnchorStyles, приведенного в таблице

    Значения перечисления AnchorStyles
    None = 0 (00000000) Нет привязки (по умолчанию)
    Top = 1 (00000001) Привязать к верхнему краю контейнера
    Bottom = 2 (00000010) Привязать к нижнему краю контейнера
    Left = 4 ((00000100) Привязать к левому краю контейнера
    Right = 8 (00001000) Привязать к правому краю контейнера

    Свойство Anchor класса Control является аниподом свойства Dock. Когда присваивается одному свойству значение, отличное от None, автоматически сбрасывается в значение None другое свойство.

    По умолчанию свойство Anchor имеет значение AnchorStyles.Top | AnchorStyles.Left. Если добавить в дочерний элемент к значению Anchor по умолчанию еще AnchoStyles.Right, то при изменении размеров контейнера зазор слева, сверху и справа между элементом и контейнером будет заморожен.

    Приведем пример, в котором на форме расположим 4 текстовых поля с метками. Текстовые поля привяжем к трем сторонам контейнера, кроме нижней.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            int y; // Вынесено в поле класса для видимости в обработчике
        
            public MyClass() // Конструктор
            {
                this.Text = "Привязка Anchor";
        
                int iSpace = Font.Height;// Интерлиньяж
                y = iSpace;// Позиционирование по вертикали
        
                string[] fieldInfo = new string[] {
                    "Фамилия:",
                    "Имя:",
                    "Адрес:",
                    "Профессия:"
                };
        
                // Создаем и настраиваем объекты
                for (int i = 0; i < fieldInfo.Length; i++)
                {
                    Label lbl = new Label();
                    lbl.Parent = this;
                    lbl.AutoSize = true;
                    lbl.Text = fieldInfo[i];
                    lbl.Location = new Point(iSpace, y);
        
                    TextBox txtBox = new TextBox();
                    txtBox.Parent = this;
                    txtBox.Location = new Point(lbl.Right + iSpace, y);
                    int width = this.ClientSize.Width - 
          iSpace - txtBox.Left;
                    txtBox.Size = new Size(width, txtBox.Height);
                    txtBox.Anchor |= AnchorStyles.Right;
        
                    y = txtBox.Bottom + iSpace;// Сдвиг по вертикали
                }
        
                // Подстраиваем форму
                this.ClientSize = new Size(this.ClientSize.Width, y);
                // Убираем системные кнопки
                this.MaximizeBox = false;
                this.MinimizeBox = false;
            }
        
            // Блокируем изменение высоты формы
            protected override void OnResize(EventArgs e)
            {
                base.OnResize(e);
                this.ClientSize = new Size(this.ClientSize.Width, y);
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат

    Если в этой программе интерактивно изменять ширину формы, то ширина полей также будет меняться, поскольку они привязаны справа.

    Последовательная компоновка элементов управления с помощью панели FlowLayoutPanel

    Панель FlowLayoutPanel напоминает окно броузера, компонующего текст и рисунки, только вместо них осуществляется разметка элементов управления. Это тоже контейнер, в котором дочерние элементы панели размещаются последовательно по горизонтали (вертикали) и автоматически перетекают ( flow ) на новые строки (столбцы), если не вмещаются в соответствующие габариты контейнера, если булево свойство WrapContents установлено в true (по умолчанию).

    При WrapContents=false автоматический переток дочерних элементов отключается и программисту нужно организовывать его вручную. Независимо от значения свойства WrapContents для принудительного переноса элемента на новую строку (столбец) используется метод контейнера SetFlowBreak() с аргументом true (по аналогии с дескриптором <br> в HTML ). Он же используется и для отмены этого состояния с аргументом false. Для проверки закрепленного состояния принудительного переноса применяется метод GetFlowBreak().

    Направление перемещения элементов определяется в свойстве FlowLayoutPanel.FlowDirection значениями одноименного перечисления System.Windows.Forms.FlowDirection:

    Значения перечисления FlowDirection
    LeftToRight = 0 Слева направо (по умолчанию)
    TopDown = 1 Сверху вниз
    RightToLeft = 2 Справа налево
    BottomUp = 3 Снизу вверх

    Рассмотренные ранее свойства Dock и Anchor дочерних элементов управления используются в рассматриваемой панели для их выравнивания по вертикали или горизонтали. Если элементы управления размещаются построчно, то размеры условного ограничивающего прямоугольника по высоте определяются самым высоким элементом строки. Тогда значение взаимоисключающих свойств Anchor или Dock элемента определяет его верикальное положение в этом условном прямоугольнике, а именно:

  • AnchorStyles.Top (или DockStyle.Top ) - выравнивание по верхней границе (по умолчанию)
  • AnchorStyles.Bottom (или DockStyle.Bottom ) - выравнивание по нижней границе
  • AnchorStyles.None (или DockStyle.Fill, или DockStyle.Left, или DockStyle.Right ) - выравнивание по центру (с помощью свойства Dock это выполнить нельзя)
  • AnchorStyles.Top | AnchorStyles.Bottom - растяжение по вертикали до достижения границ
  • Значения AnchorStyles.Left и AnchorStyles.Right (или DockStyle.None ) - игнорируются панелью
  • При вертикальном размещении элементы по умолчанию выравниваются по левой границе, но свойства Anchor или Dock позволяют этим выравниванием управлять по той же самой схеме:

  • AnchorStyles.Left - выравнивание по левой границе
  • AnchorStyles.Right - выравнивание по правой границе
  • AnchorStyles.None - выравнивание по центру
  • AnchorStyles.Left | AnchorStyles.Right - растяжение по горизонтали до достижения границ
  • AnchorStyles.Top и AnchorStyles.Bottom - игнорируются панелью
  • Пример использования элемента FlowLayoutPanel для динамического размещения кнопок

    Вот простая программа, иллюстрирующая использование компонующей панели FlowLayoutPanel

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
            // Настраиваем форму
            this.Text = "Компоновочная панель FlowLayoutPanel";
        
            // Создаем и настраиваем компоновочную панель
            FlowLayoutPanel flow = new FlowLayoutPanel();// Создали
            flow.Parent = this;// Привязали к форме
            flow.Dock = DockStyle.Fill;// Растянули по форме
            // Задаем направление размещения дочерних элементов
            flow.FlowDirection = FlowDirection.LeftToRight;
            flow.AutoScroll = true;// Включаем полосы прокрутки
            flow.Name = "flow";// Просто назначаем имя
            // Регистрируем обработчик компоновочной панели
            flow.Click += elementClick;
        
            // Создаем и инициализируем генератор случайных чисел
            // текущим значением секунд в диапазоне от 0 до 59
            Random rand = new Random(DateTime.Now.Millisecond);
        
            // Создаем btnCount кнопок случайных размеров
            const int btnCount = 10;
            for (int i = 0; i < btnCount; i++)
              {
              Button btn = new Button();// Создали кнопку
              btn.Parent = flow;// Привязали к панели
              btn.Text = "Кнопка " + Convert.ToString(i + 1);// Подписали
              btn.AutoSize = true;// Подстроили под текст
              // Регистрируем общий с компоновочной панелью обработчик кнопок
              btn.Click += elementClick;
              // Создаем структуру и инициализируем начальным размером
              Size btnSize = btn.PreferredSize;// Предпочтительный размер
              // Корректируем структуру случайным образом в сторону увеличения
              btnSize.Width = (int)(btnSize.Width * (1 + 2 * 
        rand.NextDouble()));
              btnSize.Height = (int)(btnSize.Height * (1 + 2 * 
        rand.NextDouble()));
              // Задаем размер кнопки
              btn.Size = btnSize;
              // Маркируем
              btn.Name = "btn" + (i + 1);
              // Центрируем построчно
              btn.Anchor = AnchorStyles.None;
              }
            }
        
            // Общий обработчик для кнопок и панели
            void elementClick(object sender, EventArgs e)
            {
                Form frm = new Form();// Создаем вспомогательную форму
                frm.Owner = this;// Отображать поверх владельца
        
                // Формируем заголовок вспомогательного окна
                string title = sender.GetType().Name;// Имя типа
                // Повышаем полномочия ссылки для адресации к свойству Text
                Control ctrl = (Control)sender;
                if (ctrl.Name.Length > 0)
                    title += " (" + ctrl.Name + 
          ")";// Имя объекта, если есть
                if (ctrl.Text.Length > 0)
                    title += " - " + 
        ctrl.Text;// Надпись, если есть
                frm.Text = title;
        
                // Создаем панель отображения свойств выделенного объекта
                PropertyGrid properties = new PropertyGrid();
                properties.Parent = frm;// Привязать
                properties.Dock = DockStyle.Fill;// Распахнуть
                properties.SelectedObject = sender;// Чьи свойства отображать
                properties.PropertySort = PropertySort.Alphabetical;// Сортировать
        
                frm.Show();// Показать окно
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Вот примерный результат

    При горизонтальном направлении размещения кнопки выравниваются по горизонтали, но по вертикали они пристыковываются друг к другу на равных расстояниях и не подлежат выравниванию. Расстоянием между пристыкованными в строке элементами можно управлять с помощью свойств Padding (внутри) и Margin (снаружи), которые являются структурами, ждущими соответствующих настроек.

    Пример использования элемента FlowLayoutPanel для формирования окна About

    Приведем еще пример использования элемента управления FlowLayoutPanel для формирования интерфейса простого окна About.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Diagnostics;// для класса Process
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
            // Настраиваем форму
            this.Text = "Компоновка окна About";
            //Включили способность менять размеры
            this.AutoSize = true;
            // Разрешить растягиваться и сжиматься
            this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
            // Запретить интерактивное изменение и настроить вид
            this.FormBorderStyle = FormBorderStyle.FixedDialog;
        
            // Создаем контейнер для рисунка
            PictureBox photo = new PictureBox();
            photo.Parent = this;// Привязываем к форме
            photo.Size = new Size(200, 200);// Размер контейнера
            photo.Image = Image.FromFile("MyPhoto.jpg");// Закачали
            // Распахнуть по контейнеру с соблюдением пропорций
            photo.SizeMode = PictureBoxSizeMode.Zoom;
        
            // Создаем и настраиваем компоновочную панель
            FlowLayoutPanel flow = new FlowLayoutPanel();// Создали
            flow.Parent = this;// Привязали к форме
            flow.AutoSize = true;// Пусть подстраивается
            // Направление размещения по столбцам
            flow.FlowDirection = FlowDirection.TopDown;
            // Стыковка к рисунку справа
            flow.Location = new Point(photo.Right, 0);
        
            // Создаем и настраиваем текстовую метку
            Label lbl = new Label();
            lbl.Parent = flow;// Привязали к панели
            lbl.AutoSize = true;// Подстройка под текст
            lbl.Anchor = AnchorStyles.None;// Центрировать в столбце
            lbl.Margin = new Padding(this.Font.Height);// Окаймление
            lbl.Text = "AboutBox Version 1.0";// Содержимое надписи
            lbl.Font = new Font(FontFamily.GenericSansSerif, 
        24, FontStyle.Italic);
        
            // Создаем и настраиваем метку-ссылку
            LinkLabel lnk = new LinkLabel();
            lnk.Parent = flow;// Привязали к панели
            lnk.AutoSize = true;// Подстройка под текст
            lnk.Anchor = AnchorStyles.None;// Центрировать в столбце
            lnk.Margin = new Padding(this.Font.Height);// Окаймление
            lnk.Text = "\x00A9 2007 СФУ";// Значок и текст авторских прав
            lnk.Font = new Font(FontFamily.GenericSansSerif, 16);
            // Область интерактивности ссылки
            lnk.LinkArea = new LinkArea(lnk.Text.Length - 
        3, lnk.Text.Length);
            // Подписываем на событие анонимный обработчик
    lnk.LinkClicked += delegate { Process.Start(@"http://www.gold.krasu.ru"); };
        
            // Создаем и настраиваем кнопку
            Button btn = new Button();
            btn.Parent = flow;// Привязали к панели
            btn.AutoSize = true;// Подстройка под текст
            btn.Anchor = AnchorStyles.None;// Центрировать в столбце
            btn.Margin = new Padding(this.Font.Height);// Окаймление
            btn.Text = "Выход";// Надпись на кнопке
            // Регистрируем анонимный обработчик для закрытия формы
            btn.Click += delegate { this.Close(); };
        
            // Создаем и настраиваем всплывающие подсказки
            ToolTip tips = new ToolTip();
            tips.SetToolTip(photo, "Это значок");
            tips.SetToolTip(lnk, "Зайти на сайт");
            tips.SetToolTip(btn, "Закрыть окно");
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Файл рисунка нужно добавить к проекту командой Add оболочки и настроить его свойства Properties через контекстное меню:

  • Build Action = Embedded Resource - назначить внедренным ресурсом
  • Copy to Output Directory = Copy always - копировать в рабочий каталог всегда при каждой компиляции
  • Вот результат выполнения программы

    Большой пример автоматического размещения элементов управления

    Для размещения радиокнопок как единой группы применяется элемент управления GroupBox. Но он требует жесткого позиционирования своих дочерних элементов. Составим на основе элемента FlowLayoutPanel расширение, которое будет служить контейнером для автоматического размещения радиокнопок. Затем составим класс диалогового окна, в котором динамически разместим пользовательские элементы управления, включая радиокнопки. И наконец, создадим класс главного окна, вызывающего диалоговое окно и реализующего выбор пользователя.

    Вот этот код, который для удобства размещен в нескольких файлах

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс расширения стандартной панели размещения
        class GroupPanel : FlowLayoutPanel
        {
            // Поля класса для видимости в методах
            int xDpi, yDpi;
            new public string Text; // Скрываем наследуемое поле 
                                    // Надпись в рамке
        
            // Конструктор
            public GroupPanel()
            {
                // Перетекание по столбцам
                this.FlowDirection = FlowDirection.TopDown;
                // Отключаем автоматический переток элементов
                this.WrapContents = false;
                //Включили способность менять размеры
                this.AutoSize = true;
                // Разрешить контейнеру растягиваться и сжиматься
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создаем временный контекст устройства
                Graphics gr = this.CreateGraphics();
                // Извлекаем текущее разрешение экрана
                xDpi = (int)gr.DpiX;
                yDpi = (int)gr.DpiY;
                // Освобождаем критичный ресурс
                gr.Dispose();
                // Устанавливаем отступы внутри рамки контейнера
                this.Padding = new Padding(xDpi / 10, yDpi / 10 + 
        Font.Height, xDpi / 10, yDpi / 10);
            }
        
            // Свойство для управления включением кнопки
            public string Check
            {
                // Аксессоры
                set
                {
                // Проверяем, что это радиокнопка и выделяем ее
                // Поиск по имени радиокнопки
                RadioButton radio = this.Controls[value] as RadioButton;
                if (radio != null)
                  radio.Checked = true;
                }
                get
                {
                    foreach (Control ctrl in this.Controls)
                    {
                        // Ищем выделенную радиокнопку и возвращаем имя
                        RadioButton radio = ctrl as RadioButton;
                        if (radio != null  radio.Checked)
                            return radio.Name;
                    }
                    return "";
                }
            }
        
            // Переопределяем виртуальный обработчик изменения шрифта
            protected override void OnFontChanged(EventArgs e)
            {
                base.OnFontChanged(e);// Базовый обработчик
                this.Padding = new Padding(this.Padding.Left,
                    yDpi / 10 + this.Font.Height,
                    this.Padding.Right, this.Padding.Bottom);
            }
        
            // Переопределяем виртуальный обработчик перерисовки формы
            // Сами перекомпоновываем элементы и перерисовываем результат
            protected override void OnPaint(PaintEventArgs e)
            {
                // Извлекаем контекст устройства
                Graphics gr = e.Graphics;
                // Смещение 
                int yIdent = yDpi / 25 + this.Font.Height / 2;
                int xIdent1 = xDpi / 10;
                int xIdent2;// Пока просто объявили
        
                // Отображаем надпись внутри рамки, если она задана
                if (this.Text != null  this.Text.Length > 0)
                {
                    gr.DrawString(
                        " " + this.Text + " ",
                        this.Font,
                        new SolidBrush(this.ForeColor),
                        xIdent1, yDpi / 25
                        );
                    xIdent2 = xIdent1 + (int)(gr.MeasureString(
                        " " + this.Text + " ",
                        this.Font).Width) + 4;// Увеличили отступ справа
                }
                else
                {
                    xIdent2 = xIdent1;
                }
        
                // Создаем перья стандартного цвета и рисуем рамку
                Pen pnDark = new Pen(ControlPaint.Dark
        (BackColor));// Темный
                Pen pnLight = new Pen(ControlPaint.Light
        (BackColor));// Светлый
                
                // Рисуем темную рамку
                gr.DrawLine(pnDark, xIdent1, yIdent, 0, yIdent);
                gr.DrawLine(pnDark, 0, yIdent, 0, this.Height - 2);
                gr.DrawLine(pnDark, 0, this.Height - 2, 
        this.Width - 2, this.Height - 2);
                gr.DrawLine(pnDark, this.Width - 2, this.Height - 2, 
        this.Width - 2, yIdent);
                gr.DrawLine(pnDark, this.Width - 2, yIdent, 
        xIdent2, yIdent);
        
                // Рисуем светлую рамку
                gr.DrawLine(pnLight, xIdent1, yIdent + 1, 
        1, yIdent + 1);
                gr.DrawLine(pnLight, 1, yIdent + 1, 1, 
        this.Height - 3);
                gr.DrawLine(pnLight, 0, this.Height - 1, 
        this.Width - 1, this.Height - 1);
                gr.DrawLine(pnLight, this.Width - 1, this.Height - 1, 
        this.Width - 1, yIdent);
                gr.DrawLine(pnLight, this.Width - 3, yIdent + 1, 
        xIdent2, yIdent + 1);
            }
        }
    }

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    using System.Reflection;// Для динамического определения типов
        
    namespace Test
    {
        // Заготавливаем диалоговое окно, как класс размещения
        // двух пользовательских компоновочных панелей
        class ColorFillDialog : Form
        {
            // Видимые поля
            protected GroupPanel grPanel1, grPanel2;
            protected CheckBox checkBox;
        
            public ColorFillDialog()
            {
            // Настройка диалогового окна
            // Заголовок диалогового окна
            this.Text = "Выбор цвета элипса";
            // Стиль диалогового окна фиксированных размеров
            this.FormBorderStyle = FormBorderStyle.FixedDialog;
            // Отключаем системные кнопки
            this.ControlBox = false;// Системная пиктограмма
            this.MaximizeBox = this.MinimizeBox = false;// Сворачивание
            this.ShowInTaskbar = false;// Не отображать в панели задач
            // Подстраивать размер диалогового окна формы под содержимое
            this.AutoSize = true;
            // Может растягиваться и сжиматься
            this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
            // Создаем внешнюю компоновочную панель
            FlowLayoutPanel flow1 = new FlowLayoutPanel();
            flow1.Parent = this;// Привязали к форме
            flow1.AutoSize = true;// Способность изменять размер
            // Компоновать внутренние элементы по столбцам
            flow1.FlowDirection = FlowDirection.TopDown;
        
            // Создаем внутреннюю компоновочную панель
            FlowLayoutPanel flow2 = new FlowLayoutPanel();
            flow2.Parent = flow1;// Привязали к внешней панели
            flow2.AutoSize = true;//Способность изменять размер
            // Компоновать содержимое по строкам (по умолчанию)
            flow2.FlowDirection = FlowDirection.LeftToRight;
            // Дочерние элементы (выравнивать по высоте
            flow2.Anchor = AnchorStyles.None;
        
            // Размещаем внутри еще две пользовательских (наших) панели
            grPanel1 = new GroupPanel();
            grPanel1.Parent = flow2;
            grPanel1.AutoSize = true;
            grPanel1.Text = "Цвет элипса";// Надпись внутри рамки
        
            grPanel2 = new GroupPanel();
            grPanel2.Parent = flow2;
            grPanel2.AutoSize = true;
            grPanel2.Text = "Фон формы";// Надпись внутри рамки
        
            // Временно замораживаем авторазмещение в панелях
            grPanel1.SuspendLayout();
            grPanel2.SuspendLayout();
        
            // Считываем все общедоступные свойства 
            // библиотечной структуры System.Drawing.Color,
            // применяя технологию отражения
            Type type = typeof(System.Drawing.Color);
            PropertyInfo[] propertiesColor = type.GetProperties();
        
            // Анализируем все свойства и заполняем панели радиокнопками
            foreach (PropertyInfo prop in propertiesColor)
              {
              // Выбираем свойства, которые могут читаться
              // и имеют статические get-аксессоры (а это есть цвета)
              if (prop.CanRead  prop.GetGetMethod().IsStatic)
                {
                // Выбираем цвета с именами на буквы S и P
                if (prop.Name[0] == 'S' || prop.Name[0] == 'P')
                  {
                  // Создаем радиокнопку
                  RadioButton radio = new RadioButton();
                  // Добавляем радиокнопку к нужной панели
                  radio.Parent = prop.Name[0] == 'S' ? 
          grPanel1 : grPanel2;
                  // Включаем способность изменять размер
                  radio.AutoSize = true;
                  // Назначаем надпись и имя для узнаваемости
                  radio.Text = radio.Name = prop.Name;
                  }
                }
              }
        
                // Размораживаем авторазмещение в панелях,
                // чтобы элементы могли скомпоноваться
                grPanel1.ResumeLayout();
                grPanel2.ResumeLayout();
        
                // Добавляем флажок
                checkBox = new CheckBox();
                checkBox.Parent = flow1;// Добавили во внешнюю панель
                checkBox.AutoSize = true; // Подстройка под надпись
                checkBox.Text = "Залитый элипс";
                checkBox.Anchor = AnchorStyles.None;// Выравнивать посредине столбца
        
                // Добавляем еще одну компоновочную панель снизу
                FlowLayoutPanel flow3 = new FlowLayoutPanel();
                flow3.Parent = flow1;
                flow3.AutoSize = true;
                flow3.Anchor = AnchorStyles.None;//Выравнивание посредине столюца
        
                // Добавляем в нижнюю компоновочную панель две кнопки
                Button btn = new Button();
                btn.Parent = flow3;
                btn.AutoSize = true;
                btn.Text = "Выполнить";
                // Для распознавания формой
                btn.DialogResult = DialogResult.OK;
                this.AcceptButton = btn;
        
                btn = new Button();
                btn.Parent = flow3;
                btn.AutoSize = true;
                btn.Text = "Отмена";
                // Для распознавания формой
                btn.DialogResult = DialogResult.Cancel;
                this.CancelButton = btn;
            }
        
            // Свойства
            public System.Drawing.Color Color
            {
                get { return System.Drawing.Color.FromName
        (grPanel1.Check); }
                set { grPanel1.Check = value.Name; }
            }
        
            public System.Drawing.Color Background
            {
                get { return System.Drawing.Color.FromName
        (grPanel2.Check); }
                set { grPanel2.Check = value.Name; }
            }
        
            public bool Fill
            {
                get { return checkBox.Checked; }
                set { checkBox.Checked = value; }
            }
        }
    }

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Главная форма приложения
        class MyClass : Form
        {
            // Поля для хранения выбранных состояний диалогового окна
            Color colorEllipse = Color.Salmon;// Цвет элипса
            bool bFillEllipse = false;// Какой элипс рисовать
        
            public MyClass()
            {
            // Заголовок окна
            this.Text = "Цветовая заливка";
            // Включаем возможность перерисовки формы
            // при изменении ее размеров пользователем
            this.ResizeRedraw = true;
            // Цвет формы
            this.BackColor = Color.PowderBlue;
        
            // Создаем меню
            this.Menu = new MainMenu();// Сам объект меню
            this.Menu.MenuItems.Add("Опции");// Верхний уровень меню
            this.Menu.MenuItems[0].MenuItems.Add(
               "Цвет...", MenuColorOnClick);// Опция и обработчик
            }
        
            // Обработчик опции Color
            private void MenuColorOnClick(object sender, EventArgs e)
            {
            // Создаем наше диалоговое окно налету
            ColorFillDialog dlg = new ColorFillDialog();
            dlg.Color = colorEllipse;// Присваиваем значение поля
            dlg.Fill = bFillEllipse;// Присваиваем состояние флажка
            dlg.Background = this.BackColor;// Включаем нужную радиокнопку
        
            // Показываем диалоговое окно и анализируем выбор пользователя
            if (dlg.ShowDialog() == DialogResult.OK)
              {
              // Запоминаем в поле главной формы новый цвет 
              colorEllipse = dlg.Color;
              // Запоминаем состояние флажка
              bFillEllipse = dlg.Fill;
              // Меняем цвет главной формы
              this.BackColor = dlg.Background;
              // Генерируем необходимость перерисовки 
              // главной формы с новыми установками
              this.Invalidate();
              }
            }
        
            // Переопределяем стандартный обработчик события перерисовки формы
            protected override void OnPaint(PaintEventArgs e)
            {
            // Определяем контекст устройства
            Graphics gr = e.Graphics;
            // Строим прямоугольник над формой
            Rectangle rect = new Rectangle(0, 0,
                    this.ClientSize.Width - 1, this.ClientSize.Height - 1);
            if (bFillEllipse)// Рисуем залитый элипс на главной форме
              gr.FillEllipse(new SolidBrush(colorEllipse), rect);
            else // Рисуем контур незалитого элипса на главной форме
                    gr.DrawEllipse(new Pen(colorEllipse), rect);
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Результат выполнения будет таким

    Если не использовать свойство Check, то GroupPanel можно использовать для размещения и других элементов управления, например, флажков.

    Табличная компоновка элементов управления с помощью панели TableLayoutPanel

    Панель TableLayoutPanel позволяет размещать дочерние элементы в сетке строк и столбцов, наподобие таблиц в HTML. Свойство AutoSize, установленное в true, позволяет автоматически подгонять ячейки таблицы под размеры размещаемых в ней элементов. Границы ячеек таблицы можно сделать видимыми с помощью ее свойства CellBorderStyle, присвоив одно из значений перечисления TableLayoutPanelCellBorderStyle, но для размещения обычно это не нужно.

    Размер сетки задается с помощью свойств RowCount и ColumnCount, которые по умолчанию равны 0, и это то же самое, что задать 1. Для автоматического добавления новых строк или столбцов используется свойство GrowStyle, ожидающее одно из значений перечисления TableLayoutPanelGrowStyle: FixedSize, AddRows (по умолчанию), AddColumns. Если одна координата таблицы добавляется автоматически, то другая координата определяется свойством RowCount или ColumnCount соответственно.

    Значение FixedSize нужно присваивать только после того, как будут заданы значения свойств RowCount и ColumnCount. При этом, если количество динамически размещаемых в таблице элементов управления превысит количество ячеек, будет сгенерировано исключение. Ячейки таблицы можно заполнять избирательно. Если предположить, что table - экземпляр таблицы, а ctrl - размещаемый в ячейке ( row, col ) элемент управления, то код будет таким

    table.Controls.Add(ctrl, col, row);

    Целые числа col и row отсчитываются от нуля. Если при вызове метода Add() в адресуемой ячейке таблицы уже находится элемент управления, то он будет перемещен в следующую ячейку.

    Можно узнать координаты размещенного в таблице элемента управления, используя следующий код

    TableLayoutPanelCellPosition cellPos = table.GetCellPosition(ctrl);

    Поля Row и Column структуры TableLayoutPanelCellPosition дают координаты ячейки таблицы:

    int row = cellPos.Row;
    int col = cellPos.Column;

    Для элемента управления ячейка таблицы считается родителем, поэтому размещение в рамках ячейки определяется свойствами элемента Dock или Anchor.

    Приведем пример, иллюстрирующий работу элемента TableLayoutPanel.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Форма приложения
        class MyClass : Form
        {
        // Вынесены в поля для видимости в обработчике
        Panel panelColor;// Ссылка для адресации правой панели SplitContainer
        Label[] arrayLabelValue = new Label[3];// Массив для значений цветов
        
        public MyClass()
        {
        this.Text = "Использование табличной компоновки TableLayoutPanel";
        this.Width *= 2;// Увеличим ширину формы
        
        // Создаем двухпанельный элемент с изменяемой границей
        SplitContainer split = new SplitContainer();
        split.Parent = this;// Привязали к форме
        split.Dock = DockStyle.Fill;// Развернули на всю форму
        split.Orientation = Orientation.Vertical;// По умолчанию
        split.SplitterDistance = this.ClientSize.Width / 2;// Позиция движка
        split.TabStop = false;// Отключаем получение фокуса по клавише Tab
        
        // Создаем в левой панели элемент TableLayoutPanel
        TableLayoutPanel table = new TableLayoutPanel();
        table.Parent = split.Panel1;// Назначили родителя
        table.Dock = DockStyle.Fill;// Развернули на всю левую панель
        table.BackColor = Color.White;// Цвет фона
        // Установили размер таблицы 3x3
        table.RowCount = 3;
        table.ColumnCount = 3;
        
        // Адресуем правую панель полем
        panelColor = split.Panel2;
        
        // Создаем двумерный массив для названий RGB
        string[,] arrayNameColors = {
          {"Красный", "Зеленый", "Синий" },
          {"Red", "Green", "Blue"}
                                            };
        // Создаем массив для числовых значений цветов 
        // и инициализируем начальным цветом фона правой панели
        int[] arrayValueColors ={ 
          panelColor.BackColor.R,     // Red - красный
          panelColor.BackColor.G,     // Green - зеленый
          panelColor.BackColor.B };   // Blue - синий
        
        // Создаем шрифт для текстовых меток
        Font font = new Font("Arial", 10, FontStyle.Bold);
        
        // Заполнение таблицы по столбцам
        for (int col = 0; col < 3; col++)
          {
          // Первая строка с названиями цветов
          Label lbl = new Label();// Создали объект
          lbl.AutoSize = true;// Подстройка метки под текст
          lbl.Anchor = AnchorStyles.None;// Расположиться в центре ячейки
          lbl.Text = arrayNameColors[0, col];// Русское название цвета
          lbl.ForeColor=Color.FromName(arrayNameColors[1, col]);// Цвет метки
          lbl.Font = font;// Установили шрифт
          table.Controls.Add(lbl, col, 0);// Поместить в ячейку таблицы
        
          // Вторая строка с полосами прокрутки
          VScrollBar scroll = new VScrollBar();// Создали объект
          // Растянули по высоте средней строки
          scroll.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
          // Включили отклик на клавишу Tab для смены фокуса ввода
          scroll.TabStop = true;
          // Установили длину ползунка и шаг скролирования
          scroll.LargeChange = 16;// 16 * 16 = 256 цветов
          // Когда ползунок внизу
          scroll.Maximum = 255 + scroll.LargeChange - 1;
          // Устанавливаем начальное значение ползунка
          scroll.Value = arrayValueColors[col];
          // Подписались на событие при отпускании ползунка 
          scroll.ValueChanged += scroll_ValueChanged;
          table.Controls.Add(scroll, col, 1);// Поместить в ячейку таблицы
        
          // Третья строка со значениями цвета
          arrayLabelValue[col] = new Label();// Создали объект
          arrayLabelValue[col].AutoSize = true;// Подстройка под текст
          arrayLabelValue[col].Anchor = AnchorStyles.None;// В центре ячейки
          // Установим цвет текстовой метки
          arrayLabelValue[col].ForeColor = Color.FromName
        (arrayNameColors[1, col]);
          // Связываем свойство Text метки и свойство Value полосы прокрутки
          arrayLabelValue[col].DataBindings.Add("Text", 
        scroll, "Value");
          arrayLabelValue[col].Font = font;// Установили шрифт
          // Добавляем настроенную текстовую метку значения цвета в ячейку таблицы
          table.Controls.Add(arrayLabelValue[col], col, 2);
        
          // Устанавливаем столбцы таблицы на три равных ширины
          table.ColumnStyles.Add(new ColumnStyle
      (SizeType.Percent, 100.0f / 3));
          }
        
        // Устанавливаем среднюю строку таблицы максимально большой
        table.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Первая строка
        table.RowStyles.Add(new RowStyle(SizeType.Percent, 
      100.0f));// Вторая строка
        table.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Третья строка
        }
        
        void scroll_ValueChanged(object sender, EventArgs e)
        {
        // Меняем цвет фона правой панели
        panelColor.BackColor = Color.FromArgb(
          int.Parse(arrayLabelValue[0].Text),// Преобразуем в число
          int.Parse(arrayLabelValue[1].Text),// Преобразуем в число
          int.Parse(arrayLabelValue[2].Text) // Преобразуем в число
        );
        }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат выполнения программы

    Поизменяйте размеры формы и разделителя и подивитесь, как автоматически перекомпоновываются элементы управления слева.

    Соседние ячейки таблицы можно объединять как вдоль столбцов, так и вдоль строк. При этом в первой из объединяемых ячеек прежде уже должен находиться элемент управления. Другие элементы управления, которые находятся в следующих из объединяемых ячеек, сдвигаются на новые позиции. При этом, если свободных ячеек не хватает, приложение генерирует исключение.

    Объединение ячеек панели TablelayoutPanel можно выполнить ее методами

    SetColumnSpan(ctrl, span);SetRowSpan(ctrl, span);
    где ctrl - элемент управления, расположенный в первой 
               из объединяемых ячеек
        span - количество объединяемых ячеек

    Эти методы можно вызвать, при необходимости, для одного и того же элемента управления, освободив для него пространство как по горизонтали, так и по вертикали. Для такого элемента управления методы

    GetColumnSpan(ctrl);
    GetRowSpan(ctrl);

    возвращают количество объединенных ячеек.

    Для элемента управления, занимающего несколько строк и столбцов, метод GetPositionFromControl(ctrl) возвращает структуру TableLayoutPanelCellPosition, в которой поля Row и Column дают координаты самой левой и самой верхней из объединенных ячеек таблицы, которую занимает элемент управления ctrl. При адресации к любой из объединенных ячеек таблицы с помощью метода GetControlFromPosition(col, row) можно получить адрес находящегося там элемента управления.

    Большой пример разработки диалогового окна выбора шрифта

    Попробуем разработать свою программу, которая дублирует стандартный диалог выбора шрифта и реализует максимум его функциональности. Для начала приведем программу, вызывающую этот стандартный диалог, чтобы посмотреть, как он выглядит. Вот эта простая вызывающая программа

    using System.Windows.Forms;
        
    namespace Test
    {
        // Запуск
        class Program
        {
            static void Main()
            {
                // Создаем объект окна стандартного диалога выбора шрифта
                FontDialog fontDialog = new FontDialog();
                // Показать дополнительные части диалогового окна
                fontDialog.ShowEffects = true;
                fontDialog.ShowColor = true;
                //fontDialog.ShowApply = true;// Кнопка "Применить"
                //fontDialog.ShowHelp = true; // Кнопка "Справка"
        
                // Показать само окно
                fontDialog.ShowDialog();
            }
        }
    }

    Вот так примерно должен выглядеть наш диалог выбора шрифта

    Можно заметить, что элементы управления стандартного диалогового окна размещены в таблице, которая имеет 3 строки и 4 столбца. Код каждой сложной части диалога изолируем в отдельные классы и поместим в отдельные одноименные файлы. Ниже приведены все составляющие данного примера.

    Самоподстраивающийся контейнер GroupPanel

    Приведем рассмотренный ранее код компоновочного контейнера с рамкой. Предварительно удалим из него свойство Check, связанное с размещением радиокнопок и организацией их согласованной работы. Хотя можно это свойство и не удалять - все равно мы его использовать в данном примере не будем. Вот код панели, который нужно разместить в отдельном файле GroupPanel.cs

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс расширения стандартной панели размещения
        class GroupPanel : FlowLayoutPanel
        {
            // Поля класса для видимости в методах
            int xDpi, yDpi;
            new public string Text; // Скрываем наследуемое поле 
            // Надпись в рамке
        
            // Конструктор
            public GroupPanel()
            {
                // Перетекание по столбцам
                this.FlowDirection = FlowDirection.TopDown;
                // Отключаем автоматический переток элементов
                this.WrapContents = false;
                //Включили способность менять размеры
                this.AutoSize = true;
                // Разрешить панели растягиваться и сжиматься
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создаем временный контекст устройства
                Graphics gr = this.CreateGraphics();
                // Извлекаем текущее разрешение экрана
                xDpi = (int)gr.DpiX;
                yDpi = (int)gr.DpiY;
                // Освобождаем критичный ресурс - контекст устройства
                gr.Dispose();
                // Устанавливаем отступы внутри рамки контейнера
                this.Padding = new Padding(xDpi / 10, yDpi / 10 + 
        Font.Height, xDpi / 10, yDpi / 10);
            }
        
            // Переопределяем виртуальный обработчик изменения шрифта
            protected override void OnFontChanged(EventArgs e)
            {
                base.OnFontChanged(e);// Базовый обработчик
                this.Padding = new Padding(this.Padding.Left,
                    yDpi / 10 + this.Font.Height,
                    this.Padding.Right, this.Padding.Bottom);
            }
        
            // Переопределяем виртуальный обработчик перерисовки формы
            // Сами перекомпоновываем элементы и перерисовываем результат
            protected override void OnPaint(PaintEventArgs e)
            {
                // Извлекаем контекст устройства
                Graphics gr = e.Graphics;
                // Смещение 
                int yIdent = yDpi / 25 + this.Font.Height / 2;
                int xIdent1 = xDpi / 10;
                int xIdent2;// Пока просто объявили
        
                // Отображаем надпись внутри рамки, если она задана
                if (this.Text != null  this.Text.Length > 0)
                {
                    gr.DrawString(
                        " " + this.Text + " ",
                        this.Font,
                        new SolidBrush(this.ForeColor),
                        xIdent1, yDpi / 25
                        );
                    xIdent2 = xIdent1 + (int)(gr.MeasureString(
                        " " + this.Text + " ",
                        this.Font).Width) + 7;// Увеличили отступ справа
                }
                else
                {
                    xIdent2 = xIdent1;
                }
        
                // Создаем перья стандартного цвета и рисуем рамку
                Pen pnDark = new Pen(ControlPaint.Dark
        (BackColor));// Темный
                Pen pnLight = new Pen(ControlPaint.Light
        (BackColor));// Светлый
        
                // Рисуем темную рамку
                gr.DrawLine(pnDark, xIdent1, yIdent, 0, yIdent);
                gr.DrawLine(pnDark, 0, yIdent, 0, this.Height - 2);
                gr.DrawLine(pnDark, 0, this.Height - 2, 
        this.Width - 2, this.Height - 2);
                gr.DrawLine(pnDark, this.Width - 2, this.Height - 2, 
        this.Width - 2, yIdent);
                gr.DrawLine(pnDark, this.Width - 2, yIdent, 
        xIdent2, yIdent);
        
                // Рисуем светлую рамку
                gr.DrawLine(pnLight, xIdent1, yIdent + 1, 1, yIdent + 1);
                gr.DrawLine(pnLight, 1, yIdent + 1, 1, this.Height - 3);
                gr.DrawLine(pnLight, 0, this.Height - 1, 
        this.Width - 1, this.Height - 1);
                gr.DrawLine(pnLight, this.Width - 1, this.Height - 1, 
        this.Width - 1, yIdent);
                gr.DrawLine(pnLight, this.Width - 3, yIdent + 1, 
        xIdent2, yIdent + 1);
            }
        }
    }

    Класс управления списком стиля шрифта StyleComboBox

    Разработаем класс StyleComboBox, который будет реализовывать поддержку стилей шрифтов и его экземпляр будет размещен в ячейке под текстовой меткой "Начертание". Этот класс будет расширением класса стандартного элемента управления поля со списком ComboBox. В нем нужно учесть, что не все семейства шрифтов поддерживают полный набор стилей:

    Regular - обычный
    Bold - полужирный
    Italic - курсив
    Bold Italic - полужирный курсив

    а также нужно учесть дополнительные стили, применимые ко всем вышеперечисленным стилям:

    Strikeout - зачеркнутый
    Underline - подчеркнутый

    Этот класс должен самостоятельно заполнять список доступными стилями при выборе пользователем конкретного шрифта, а также поддерживать поле списка при щелчке на соответствующем стиле. Вот этот класс-расширение StyleComboBox, размещенный в отдельном одноименном файле

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Расширяем библиотечный ComboBox на управление стилем
        class StyleComboBox : ComboBox
        {
            // Конструктор настройки стиля как списка с редактируемым полем
            public StyleComboBox()
            {
                this.DropDownStyle = ComboBoxStyle.DropDown;// По умолчанию
            }
        
            // Свойство типа перечисления System.Drawing.FontStyle
            // Обеспечивает управление содержимым самого поля списка
            // Преобразует название стиля в поле в стиль и наоборот
            public FontStyle FontStyle
            {
            // Для того, чтобы заполнять русскими названиями стилей
            get
            {
            // Возвращаем стиль, соответствующий названию в поле
            if (this.SelectedItem.ToString() == "жирный курсив")
              return FontStyle.Bold | FontStyle.Italic;
            else if (this.SelectedItem.ToString() == "жирный")
              return FontStyle.Bold;
            else if (this.SelectedItem.ToString() == "курсив")
              return FontStyle.Italic;
            else
              return FontStyle.Regular;
            }
            set 
            {
            // Преобразуем выбранный в списке стиль 
            // в название стиля в поле списка
            // Применяется прием - побитовая проверка по маске
            if ((value  FontStyle.Bold)!=0 
               (value  FontStyle.Italic) != 0)
            this.SelectedItem = "жирный курсив";
            else if ((value  FontStyle.Bold) != 0)
               this.SelectedItem = "жирный";
            else if ((value  FontStyle.Italic) != 0)
              this.SelectedItem = "курсив";
            else
              this.SelectedItem = "обычный";
            }
            }
        
            // Метод добавляет в список название стиля, если он есть в шрифте,
            // но отсутствует в списке, или удаляет из списка старое название,
            // если оно есть в списке, но соответствующий стиль отсутствует в шрифте
            void TryStyle(string strFamilyName, FontStyle style, string strStyleName)
            {
            // Все параметры входные
            // strFamilyName - имя шрифта
            // style - стиль, соответствующий названию
            // strStyleName - название стиля в списке
        
            // Ищем название в списке. Если не найдено, то возвращается -1
            int index = this.FindStringExact(strStyleName);
        
            // Безопасный код
            try
            {
            // Пробуем создать объект заданного шрифта. Если он есть,
            // то исключение не возникает, и если ранее не найден в списке,
            // добавляем его название в список
            new Font(strFamilyName, 12, style);
            if (index == -1)
              Items.Add(strStyleName);
            }
            catch
            {
            // На случай, если при смене шрифта список не очищаем!!!
            // Откат, заказанного стиля в данном шрифте нет. Корректируем 
            // список, если в нем осталось название стиля от прежнего шрифта
            if (index != -1)
              {
              this.Items.Remove(strStyleName);
              }
            }
            }
        
            // Свойство, заполняющее список стилями для заданного шрифта
            public string FamilyName
            {
            set
            {
            // Для сохранения выделенного стиля при смене шрифта
            string oldStyleName = "";
            if (this.SelectedIndex != -1)// Игнорируем первый запуск
              oldStyleName = this.SelectedItem.ToString();
        
            // Очищаем список и заполняем заново,
            // чтобы соблюсти порядок следования названий стилей
            // необязательно, но тогда названия будут - как придется
            this.Items.Clear();
        
            // Выстраиваем порядок стилей как в стандартном диалоге
            TryStyle(value, FontStyle.Regular, "обычный");
            TryStyle(value, FontStyle.Italic, "курсив");
            TryStyle(value, FontStyle.Bold, "жирный");
            TryStyle(value, FontStyle.Bold | FontStyle.Italic, 
        "жирный курсив");
        
            // Выделяем позицию списка
            int index = this.FindStringExact(oldStyleName);
            if (index != -1)// Если выделенный стиль имеется и в новом шрифте
              this.SelectedIndex = index;
            else // Нет ранее выделенного стиля в новом шрифте, сброс в начало
              this.SelectedIndex = 0;
            }
            }
        
            // Свойство для возврата русского названия текущего стиля
            public string StyleNameRus
            {
                get { return this.SelectedItem.ToString(); }
            }
        }
    }

    Класс управления раскрывающимся списком цвета ColorComboBox

    Выберем несколько цветов для нашего списка управления цветом, которые приведены в таблице.

    Из-за трудностей перевода не будем вставлять в список русские названия цветов, а расположим в нем образцы цвета вместе с их оригинальными названиями.

    Вот класс-расширение ColorComboBox, размещенный в отдельном одноименном файле

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Расширяем библиотечный ComboBox на управление цветом
        class ColorComboBox : ComboBox
        {
        public ColorComboBox()// Конструктор начальных настроек
          {
          // Заполняем источник данных ComboBox массивом названий цветов
          this.DataSource = new string[] {
            "Black", "Maroon", "Green", 
      "Olive", "Navy", "Purple", 
      "Teal", "Gray", "Silver", 
      "Red", "Lime", "Yellow",
            "Blue", "Fuchsia", "Aqua", 
      "White"
            };
        
          // Устанавливаем режим работы как раскрывающийся список
          this.DropDownStyle = ComboBoxStyle.DropDownList;
          // Цветные прямоугольники в списке будет отрисовывать владелец
          this.DrawMode = DrawMode.OwnerDrawFixed;
          // Устанавливаем высоту элементов списка,
          // равную высоте унаследованного системного шрифта
          this.ItemHeight = this.Font.Height;
          }
        
            // Свойство чтения/установки цвета
            public Color Color
            {
            get { return Color.FromName(this.SelectedItem.ToString()); }
            set
              {
              // Ищем переданный цвет и позиционируем список
              this.SelectedItem = value.Name;
              /*// Либо вот такой код - вместо предыдущей строки!!!
              int index = this.FindStringExact(value.Name);
              if (index != -1)
                this.SelectedIndex = index;
              //*/
              }
            }
        
            // Переопределяем событие списка, требующее перерисовки элемента
            protected override void OnDrawItem(DrawItemEventArgs e)
            {
                // Определяем контекст устройства
                Graphics gr = e.Graphics;
        
                // Вычисляем размеры для прямоугольника самого цвета
                // как левая верхняя точка привязки, ширина, высота
                // по параметрам элемента списка
                Rectangle rectColor = new Rectangle(e.Bounds.Left, 
                    e.Bounds.Top,
                    2 * e.Bounds.Height, 
                    e.Bounds.Height);
                // Раздувает прямоугольник указанным количеством
                rectColor.Inflate(-1, -1);
        
                // Вычисляем размеры для прямоугольника названия цвета
                // как левая верхняя точка привязки, ширина, высота
                Rectangle rectText = new Rectangle(e.Bounds.Left + 
        2 * e.Bounds.Height,
                    e.Bounds.Top,
                    e.Bounds.Width - 2 * e.Bounds.Height,
                    e.Bounds.Height);
        
                // Рисуем фон заданным цветом
                e.DrawBackground();// Нацелились на фон
                gr.DrawRectangle(Pens.Black, rectColor);// Рамка
                gr.FillRectangle(
                    new SolidBrush(Color.FromName
          (this.Items[e.Index].ToString())),
                    rectColor);// Заливка
        
                // Рисуем название цвета
                gr.DrawString(this.Items[e.Index].ToString(), base.Font,
                    new SolidBrush(e.ForeColor), rectText);
            }
        }
    }

    Класс самого диалогового окна выбора шрифта NewFontDialog

    Теперь скомпонуем все необходимые элементы на форме, которая будет представлять наше диалоговое окно NewFontDialog выбора шрифта. Код можно разместить в отдельном одноименном файле NewFontDialog.cs

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
    // Расширяем библиотечный класс Form
    class NewFontDialog : Form
      {
      // Видимые поля-ссылки
      ComboBox comboFont;// Для списка "Шрифт:"
      ComboBox comboSize;// Для списка "Размер:"
      StyleComboBox comboStyle;// Для нашего списка "Начертание:"
      // Для флажков "Зачеркнутый" и "Подчеркнутый"
      CheckBox checkStrikeout, checkUnderline;
      ColorComboBox comboColor;// Для нашего списка "Цвет:"
      // Для надписей "Цвет:" и "Набор символов:"
      Label labelColor, labelSample;
        
      public NewFontDialog()// Конструктор диалогового окна
        {
        // Определяем стиль формы как диалоговое окно
        // с фиксированными размерами
        this.FormBorderStyle = FormBorderStyle.FixedDialog;
        // Отключаем системные кнопки
        this.MinimizeBox = this.MaximizeBox = false;
        // Не отображать в панели задач Windows
        this.ShowInTaskbar = false;
        this.Text = "Шрифт (самодельный диалог)";
        // Подстраиваться под содержимое
        this.AutoSize = true;
        
        // Создаем и настраиваем объект панели табличной компоновки
        TableLayoutPanel table = new TableLayoutPanel();
        table.Parent = this;// Привязали к форме
        table.AutoSize = true;// Подстраиваться под содержимое
        table.RowCount = 3;     // Количество строк таблицы
        table.ColumnCount = 4;  // Количество столбцов таблицы
        // Задаем отступы содержимого ячеек от краев ячеек, равные
        // высоте установленного в базовом классе Form шрифта 
        table.Padding = new Padding(base.Font.Height);
        
        // ***** Заполняем три ячейки верхней строки таблицы *****
        Label lbl = new Label();
        lbl.Text = "Шрифт:";
        lbl.AutoSize = true;// Подстроиться под текст
        table.Controls.Add(lbl, 0, 0);// Отдали ячейке (col, row)
        
        lbl = new Label();
        lbl.Text = "Начертание:";
        lbl.AutoSize = true;// Подстроиться под текст
        table.Controls.Add(lbl, 1, 0);// Отдали ячейке (col, row)
        
        lbl = new Label();
        lbl.Text = "Размер:";
        lbl.AutoSize = true;// Подстроиться под текст
        table.Controls.Add(lbl, 2, 0);// Отдали ячейке (col, row)
        
        // ***** Заполняем три ячейки средней строки таблицы *****
        // Заполняем ячейку с наименованиями шрифтов
        comboFont = new ComboBox();
        comboFont.DropDownStyle = ComboBoxStyle.Simple;// Простой список
        // Привязать к верхней и нижней границам ячейки
        //comboFont.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
        comboFont.Dock = DockStyle.Fill;// Распахнуть на всю ячейку
        // Подписываемся на событие выбора и регистрируем общий обработчик
        comboFont.TextChanged += new EventHandler(combo_Changed);
        // Заполняем поля списка именами семейства шрифтов
        foreach (FontFamily font in FontFamily.Families)
        {
        comboFont.Items.Add(font.Name);
        }
        // Позиционируем поле на текущий базовый шрифт
        comboFont.SelectedItem = base.Font.FontFamily.Name;
        table.Controls.Add(comboFont, 0, 1);// Отдали ячейке (col, row)
        
        // Заполняем ячейку списка стилей
        comboStyle = new StyleComboBox();// Наш класс
        comboStyle.DropDownStyle = ComboBoxStyle.Simple;// Простой список
        // Привязать к верхней и нижней границам ячейки
        comboStyle.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
        // Подписываемся на событие выбора и регистрируем общий обработчик
        comboStyle.TextChanged += new EventHandler(combo_Changed);
        // Принудительно вызываем обработчик
        // combo_Changed(comboStyle, new EventArgs());
        table.Controls.Add(comboStyle, 1, 1);
       // Отдали ячейке (col, row)
        
        // Заполняем ячейку списка выбора размеров шрифта
        comboSize = new ComboBox();
        comboSize.DropDownStyle = ComboBoxStyle.Simple;// Простой список
        // Привязать к верхней и нижней границам ячейки
        comboSize.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
        comboSize.Width = 5 * base.Font.Height;// Ширина
        // Подписываемся на событие выбора и регистрируем общий обработчик
        comboSize.TextChanged += new EventHandler(combo_Changed);
        // Заполняем поля списка стандартными размерами шрифтов
        for (int i = 8; i < 12; i++)
                  comboSize.Items.Add(i);
        for (int i = 12; i < 30; i += 2)
                  comboSize.Items.Add(i);
        for (int i = 36; i <= 72; i += 12)
                  comboSize.Items.Add(i);
        comboSize.SelectedIndex = 0;// Выделяем первое поле списка
        table.Controls.Add(comboSize, 2, 1);// Отдали ячейке (col, row)
        
        // ***** Заполняем три ячейки последней строки таблицы *****
        // Создаем экземпляр нашей ранее созданной рамки GroupPanel
        GroupPanel groupPanel = new GroupPanel();
        groupPanel.Text = "Видоизменение";
        table.Controls.Add(groupPanel, 0, 2);// Отдали ячейке (col, row)
        
        // Размещаем в панели GroupPanel флажок "Зачеркнутый"
        checkStrikeout = new CheckBox();
        checkStrikeout.Text = "Зачеркнутый";
        checkStrikeout.AutoSize = true;
        // Подписываем на событие анонимный обработчик, вызывающий
        // наш метод модификации текущего стиля в списке новым атрибутом
        checkStrikeout.Click += delegate { ShowNewFont(); };
        checkStrikeout.Parent = groupPanel;// Отдали панели
        
        // Размещаем в панели GroupPanel флажок "Подчеркнутый"
        checkUnderline = new CheckBox();
        checkUnderline.Text = "Подчеркнутый";
        checkUnderline.AutoSize = true;
        checkUnderline.Click += delegate { ShowNewFont(); };
        checkUnderline.Parent = groupPanel;// Отдали панели
        
        // Размещаем в панели GroupPanel текстовую метку "Цвет:"
        labelColor = new Label();
        labelColor.Text = "Цвет:";
        labelColor.AutoSize = true;
        labelColor.Visible = false;// Вначале отключили
        checkUnderline.Parent = groupPanel;// Отдали панели
        
        // Размещаем в панели GroupPanel поле со списком цветов
        comboColor = new ColorComboBox();
        comboColor.Anchor = AnchorStyles.Bottom;
        // Подписываемся на события выбора и программного
        // изменения названия цвета и регистрируем общий обработчик
        comboColor.SelectedValueChanged += new EventHandler(combo_Changed);
        comboColor.Visible = false;// Вначале отключили
        comboColor.Parent = groupPanel;// Отдали панели
        
        // Заполняем ячейку таблицы для размещения образца
        groupPanel = new GroupPanel();// Рамка
        groupPanel.Text = "Образец";
        table.Controls.Add(groupPanel, 1, 2);// Отдали ячейке (col, row)
        // Объединяем две средних ячейки последней строки
        table.SetColumnSpan(groupPanel, 2);
        groupPanel.Dock = DockStyle.Top;// Привязать к ячейке сверху 
        
        // Сам образец
        labelSample = new Label();
        labelSample.Text = "AaBbБбФф";
        labelSample.Font = base.Font;// Шрифт от формы
        // Текст размещать в середине текстовой метки
        labelSample.TextAlign = ContentAlignment.MiddleCenter;
        labelSample.BorderStyle = BorderStyle.Fixed3D;// Рельефно
        int width = groupPanel.Width - 2 * base.Font.Height;// Отнимаем Padding
        labelSample.Size = new Size(width, 3 * base.Font.Height);
        labelSample.Parent = groupPanel;// Отдали рамке
        
        // Создаем кнопки в последнем столбце средней строки
        // Панель FlowLayoutPanel для кнопок
        FlowLayoutPanel flow = new FlowLayoutPanel();
        flow.AutoSize = true;
        flow.FlowDirection = FlowDirection.TopDown;// Перетекать сверху вниз
        table.Controls.Add(flow, 3, 1);// Отдали ячейке (col, row)
        
        // Помещаем в панель кнопку OK
        Button btn = new Button();
        btn.Text = "OK";
        btn.AutoSize = true;
        btn.DialogResult = DialogResult.OK;// Для передачи форме
        this.AcceptButton = btn;// Для передачи форме
        btn.Parent = flow;// Отдали панели
        
        // Помещаем в панель кнопку Cancel
        btn = new Button();
        btn.Text = "Отмена";
        btn.AutoSize = true;
        btn.DialogResult = DialogResult.Cancel;// Для передачи форме
        this.CancelButton = btn;// Для передачи форме
        btn.Parent = flow;// Отдали панели
        }
        
        // Назначаем свое свойство Font
        new public Font Font
          {
          // Извлекаем настройки шрифта из образца
          get
          {
          return labelSample.Font;
          }
          // Препарируем объект Font и полученную информацию 
          // рассовываем по элементам пользовательского интерфейса
          set 
          {
          // Устанавливаем шрифт образца
          labelSample.Font = value;
          // Позиционируем поле списка шрифтов
          comboFont.SelectedItem = value.FontFamily.Name;
        
          // Заполняем список стилей для заданного шрифта
          comboStyle.FamilyName = value.FontFamily.Name;
          // Позиционируем поле списка стилей
          comboStyle.FontStyle = value.Style;
        
          // Включаем флажки, если есть дополнительные стили
          checkStrikeout.Checked = (value.Style  
         FontStyle.Strikeout) != 0;
          checkUnderline.Checked = (value.Style  
        FontStyle.Underline) != 0;
        
          // Извлекаем из объекта Font размер шрифта в пунктах
          // и сохраняем в списке
          comboSize.SelectedItem = value.SizeInPoints;
          comboSize.Text = value.SizeInPoints.ToString();
          }
          }
        
            // Собственное свойство диалогового окна
            // Имеет значение текущей установки списка цвета
            // Свойство ждет структуру System.Drawing.Color
            public Color Color
            {
                get
                {
                    return comboColor.Color;
                }
                set
                {
                    comboColor.Color = value;
                }
            }
        
            // Свойство проверки и управления видимостью 
            // интерфейсного блока "Цвет:"
            public bool ShowColor
            {
                get
                {
                    // Проверяем видимость
                    return comboColor.Visible;
                }
                set
                {
                    // Переключаем видимость
                    labelColor.Visible = comboColor.Visible = value;
                }
            }
        
            // Свойство русского названия текущего стиля 
            // Перекинули диалоговому окну из класса StyleComboBox
            public string StyleNameRus
            {
                get { return comboStyle.StyleNameRus; }
            }
        
            // Наш метод модификации текущего стиля в списке новым атрибутом
            void ShowNewFont()
            {
            FontStyle style;
        
            // Безопасный код
            try
            {
            style = comboStyle.FontStyle;
            }
            catch
            {
            return;
            }
        
            // Готовим образец модифицированного стиля
            if (checkStrikeout.Checked)
                    style |= FontStyle.Strikeout;
            if (checkUnderline.Checked)
                    style |= FontStyle.Underline;
        
            // Создаем новый шрифт и применяем его для текстовой метки
            try
            {
            System.Drawing.Font font = new Font(comboFont.Text,
                        float.Parse(comboSize.Text), style);
            labelSample.Font = font;
            }
            catch { }// Ничего не делать, но присутствия требует синтаксис
            }
        
            // Общий обработчик
            void combo_Changed(object sender, EventArgs e)
            {
            ComboBox combo = sender as ComboBox;// Преобразуем ссылку
            if (combo == null)
                    return;// Не является объектом ComboBox
        
            // Меняем цвет метки образца, если она уже создана
            if (combo == comboColor  labelSample != null)
            {
            labelSample.ForeColor = comboColor.Color;
            return;
            }
        
            if (combo == comboFont)
            {
            // Вычисляем индекс запрошенного шрифта
            int index = comboFont.FindStringExact(comboFont.Text);
        
            // Если нашли, выделяем шрифт и извлекаем для него стили
            // при условии, что объект ComboBox стилей уже создан
            if (index != -1  comboStyle != null)
            {
            comboFont.SelectedIndex = index;
            comboStyle.FamilyName = comboFont.Text;
            }
            }
        
            // Дополняем стили и меняем шрифт образца
            ShowNewFont();
            }
        }
    }

    Запускающий код приложения

    И наконец, последним разрабатываем код запускающего файла Program.cs для тестирования самопального диалога и сравнения его со стандартным диалогом

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Форма приложения
        class MyClass : Form
        {
        public MyClass()
        {
        this.Text = "Самодельный диалог шрифта";
        // Подстраиваем под ширину заголовка
        this.Width = this.Text.Length * this.Font.Height;
        this.ResizeRedraw = true;// Перерисовывать при изменении размеров
        this.ForeColor = Color.Red;// Писать красным
        
        this.Menu = new MainMenu();// Создать линейку для заголовков меню
        this.Menu.MenuItems.Add("Формат");// Добавить нулевой заголовок
        // Добавить в нулевой заголовок опции и общий обработчик
        this.Menu.MenuItems[0].MenuItems.Add
        ("Стандартный диалог...", OnStdFont);
        this.Menu.MenuItems[0].MenuItems.Add("Самодельный диалог...",
                    OnNewFont);
        
        Panel palelMenu = new Panel();// Создать панель для красоты
        palelMenu.BorderStyle = BorderStyle.Fixed3D;// Рельефная
        palelMenu.Dock = DockStyle.Top;// Причалить вверх
        palelMenu.Height = 4;// Минимально отображаемая высота
        palelMenu.Parent = this;// Привязать к форме
        }
        
        // Обработка стандартного диалога
        void OnStdFont(object sender, EventArgs args)
        {
        // Создаем окно
        FontDialog dlg = new FontDialog();
        // Настраиваем окно
        dlg.Font = this.Font;
        dlg.Color = this.ForeColor;
        dlg.ShowColor = true;
        
        // Отдаем окно для управления пользователю
        if (dlg.ShowDialog() == DialogResult.OK)
        {
        // Применяем выбор пользователя к форме
        this.Font = dlg.Font;
        this.ForeColor = dlg.Color;
        style = "";// Нечего присвоить из стандартного диалога
        // Обновляем вызывающую форму, при этом выводим параметры шрифта
        this.Invalidate();
        }
        }
        
        string style = null;// Поле для хранения значения стиля 
        // "Начертание:" для видимости в обработчиках
        // Обработка самодельного диалога
        void OnNewFont(object sender, EventArgs args)
        {
        // Создаем окно
        NewFontDialog dlg = new NewFontDialog();
        // Настраиваем окно
        dlg.Font = this.Font;
        dlg.Color = this.ForeColor;
        dlg.ShowColor = true;
        
        // Отдаем окно для управления пользователю
        if (dlg.ShowDialog() == DialogResult.OK)
        {
        // Применяем выбор пользователя к форме
        this.Font = dlg.Font;
        this.ForeColor = dlg.Color;
        style = dlg.StyleNameRus;
        // Обновляем вызывающую форму, при этом выводим параметры шрифта
        this.Invalidate();
        }
        }
        
        // Рисуем на форме параметры шрифта
        protected override void OnPaint(PaintEventArgs e)
        {
        Graphics gr = e.Graphics;
        // Формируем содержимое выводимого текста
        string message = "Текущие параметры шрифта\n" + 
        Environment.NewLine;
        message += "Шрифт: " + this.Font.Name + 
        Environment.NewLine;
        message += "Цвет: " + this.ForeColor.Name + 
        Environment.NewLine;
        // Добавим русское название стиля из нашего диалога
        if (style != null  style.Length > 0)
          message += "Начертание: " + style + 
        Environment.NewLine;
        message += "Размер: " + this.Font.SizeInPoints.ToString();
        // Добавляем дополнительный стиль, если есть
        bool flag = (this.Font.Style  FontStyle.Strikeout) != 0 ||
                    (this.Font.Style  FontStyle.Underline) != 0;
        if (flag)
          message += Environment.NewLine;
        if ((this.Font.Style  FontStyle.Strikeout) != 0)
                    message += "Зачеркнутый ";
        if ((this.Font.Style  FontStyle.Underline) != 0)
                    message += " Подчеркнутый";
        // Объект форматирования текстового блока
        StringFormat strFormat = new StringFormat();
        strFormat.LineAlignment = strFormat.Alignment =
                    StringAlignment.Center;
        // Рисуем текст новыми параметрами, 
        // установленными в обработчиках
        gr.DrawString(message, this.Font,
                    new SolidBrush(this.ForeColor),
                    this.ClientRectangle, strFormat);
        }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат работы кода

    Испытав работу нашего самодельного диалогового окна, мы можем убедиться, что оно ничем не отличается от библиотечного диалога. Для чего делали? А чтобы программистские мускулы нарастить!

    Страницы:

    Проблема с размещением элементов управления

    Визуальные средства проектирования формы (режим Designer ) хороши тем, что программисту не нужно вычислять местоположение и размер элементов управления в пределах клиентской области формы. Он это делает визуально. Но в то же время мы не знаем, на каких компьютерах и при каких условиях будет работать наше приложение. Это зависит от следующих факторов:

  • Приложение может работать на разных языках и тексты могут выступать за границы элементов управления и формы
  • На мониторах может быть установлено разное разрешение
  • Разные версии операционной системы могут по разному отображать элементы управления
  • Наиболее эффективный метод решения этих проблем - так спроектировать приложение, чтобы элементы управления и форма сами подстраивались под нестандартные условия именно во время выполнения программы, автоматически вычисляя свой размер и местоположение. Ниже рассматриваются средства, простые и более сложные, для динамической компоновки интерфейса пользователя.

    Свойство AutoSize

    Все элементы управления, включая форму, являются наследниками класса Control, в котором имеется булево свойство AutoSize. По умолчанию значение этого свойства равно false, но если изменить его на true, то элемент управления сам будет менять свои размеры в зависимости от содержимого. Следующие элементы имеют дополнительное уточняющее свойство AutoSizeMode:

  • System.Windows.Forms.Button.AutoSizeMode
  • System.Windows.Forms.DataGridViewColumn.AutoSizeMode
  • System.Windows.Forms.Form.AutoSizeMode
  • System.Windows.Forms.GroupBox.AutoSizeMode
  • System.Windows.Forms.Panel.AutoSizeMode
  • System.Windows.Forms.UserControl.AutoSizeMode
  • Это свойство ожидает одно из значений одноименного перечисления

  • System.Windows.Forms.AutoSizeMode.GrowAndShrink - растягиваться и сжиматься
  • System.Windows.Forms.AutoSizeMode.GrowOnly (по умолчанию) - только растягиваться
  • Пример размещения одной кнопки

    В следующем примере на форме размещается одна кнопка, внутри которой имеется многострочный текст. Настройка свойств кнопки и самой формы дает им возможность подстраиваться под размеры своего содержимого: кнопка подстраивается под размер текста, форма подстраивается под размер кнопки.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()    // Конструктор
            {
                // Настройка формы
                // Заголовок окна
                this.Text = "Автоподстройка размещения";
                // Включили автоподстройку формы
                this.AutoSize = true;
                // Пусть форма растягивается и сжимается
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создание и настройка объекта кнопки
                Button btn = new Button();
                // Привязали к форме
                btn.Parent = this;
                // Включили автоподстройку кнопки
                btn.AutoSize = true;
                // Задаем содержимое кнопки
                btn.Text =
                    "У Лукоморья дуб зеленый" + Environment.NewLine +
                    "Златая цепь на дубе том" + Environment.NewLine +
                    "И днем и ночью кот ученый" + Environment.NewLine +
                    "Все ходит по цепи кругом";
                // Задаем шрифт
                btn.Font = new Font("Arial", 16, FontStyle.Underline);
                // Задаем расположение текста
                btn.TextAlign = ContentAlignment.TopLeft;
                // Задаем цвет текста
                btn.ForeColor = Color.Red;
                // Задаем стиль кнопки
                btn.FlatStyle = FlatStyle.Flat;
                // Цвет рамки кнопки
                btn.FlatAppearance.BorderColor = Color.Blue;
                // Ширина рамки кнопки
                btn.FlatAppearance.BorderSize = 15;
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());// Создание объекта формы
            }
        }
    }

    Вот результат выполнения примера

    Обратите внимание, что при установке

    this.AutoSizeMode = AutoSizeMode.GrowAndShrink;

    возможность интерактивного изменения размеров формы отключается.

    Пример размещения двух кнопок

    Теперь попробуем разместить на форме две кнопки. Для тренировки упакуем уже разработанную кнопку в отдельный класс с именем MyButton, а вторую разместим как обычно и без лишних дополнительных настроек. Код программы будет таким

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс расширения кнопки
        class MyButton : Button
        {
            public MyButton()    // Конструктор
            {
            // Настройка класса расширения кнопки
            // Включили автоподстройку кнопки
            this.AutoSize = true;
            // Задаем содержимое кнопки
            this.Text =
              "У Лукоморья дуб зеленый" + Environment.NewLine +
              "Златая цепь на дубе том" + Environment.NewLine +
              "И днем и ночью кот ученый" + Environment.NewLine +
              "Все ходит по цепи кругом";
            // Задаем шрифт
            this.Font = new Font("Arial", 16, FontStyle.Underline);
            // Задаем расположение текста
            this.TextAlign = ContentAlignment.TopLeft;
            // Задаем цвет текста
            this.ForeColor = Color.Red;
            // Задаем стиль кнопки
            this.FlatStyle = FlatStyle.Flat;
            // Цвет рамки кнопки
            this.FlatAppearance.BorderColor = Color.Blue;
            // Ширина рамки кнопки
            this.FlatAppearance.BorderSize = 15;
            }
        }
        
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()    // Конструктор
            {
                // Настройка формы
                // Заголовок окна
                this.Text = "Автоподстройка размещения двух кнопок";
                // Включили автоподстройку формы
                this.AutoSize = true;
                // Установили режим: пусть форма растягивается и сжимается
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создание и настройка объекта первой кнопки
                Button btnFirst = new Button();
                // Привязали первую кнопку к форме
                btnFirst.Parent = this;
                // Включили автоподстройку первой кнопки
                btnFirst.AutoSize = true;
                // Задаем содержимое первой кнопки
                btnFirst.Text = "Хороши весной в 
        саду цветочки" + Environment.NewLine +
                    "Еще лучше девушки весной";
                // Все остальное для первой кнопки по умолчанию ...
        
                // Создание и настройка объекта второй кнопки
                MyButton btnSecond = new MyButton();
                // Привязали вторую кнопку к форме
                btnSecond.Parent = this;
                // Позиционируем вторую кнопку справа от первой
                btnSecond.Location = new Point(btnFirst.Right, 0);
                // Все остальное для второй кнопки настроено в расширении ...
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());// Создание объекта формы
            }
        }
    }

    Результат выполнения примера будет таким

    В классе Control есть событие Layout (размещение), которое генерируется всякий раз, когда родителю необходимо переразместить свои дочерние элементы управления. В обработчике этого события мы можем руками пересчитать новые позиции элементов управления и их размеры. Для решения той же задачи класс Control имеет виртуальное свойство LayoutEngine (механизм размещения), в реализацию которого можно заложить свою логику динамического размещения дочерних элементов управления.

    Имеются, также, специальные классы динамического размещения:

  • System.Windows.Forms.FlowLayoutPanel - панель плавающего размещения дочерних элементов управления
  • System.Windows.Forms.TableLayoutPanel - панель табличного размещения дочерних элементов управления
  • Панели и контейнеры

    Все потомки класса Control наследуют от него свойство Controls, в котором можно хранить ссылки на дочерние элементы управления. Иными словами, любой элемент управления способен быть контейнером других элементов. Но обычно в качестве контейнеров выступают такие элементы, как формы и панели. Именно они компонуют пользовательский интерфейс.

    Далее рассмотрим избранную иерархию классов, связанную с решением задачи динамического размещения элементов интерфейса:

    Стыковка дочерних элементов (свойство Dock)

    Стыковка элементов управления в клиентской области родителя обеспечивается значением их свойства Dock, которое ожидает одно из значений перечисления System.Windows.Forms.DockStyle:

    Значения перечисления DockStyle
    None = 0 Нестыкуемая (по умолчанию)
    Top = 1 Пристыковать сверху
    Bottom = 2 Пристыковать снизу
    Left = 3 Пристыковать слева
    Right = 4 Пристыковать справа
    Fill = 5 Развернуть на всю клиентскую область

    Если контейнер содержит несколько элементов управления и если в одном из них свойству Dock присвоено значение, отличное от None, то и другим элементам нужно присвоить отличное от None значение. И только один элемент может иметь значение Fill. Иначе элементы будут перекрываться.

    Дочерние элементы в контейнере выстраиваются в Z -последовательность в порядке их добавления в контейнер с помощью свойства Parent самого элемента управления или методом Controls.Add() контейнера. В таком же порядке они и отображаются. Изменить место элемента в Z -последовательности можно методами BringToFront() и SendToBack(). Если элементы Z -последовательности перекрываются, то элемент с меньшим индексом будет располагаться сверху.

    Если элементы пристыковываются к одному краю контейнера, то каждый новый добавленный в контейнер элемент будет пристыковываться непосредственно к краю, оттесняя к центру клиентской области контейнера ранее пристыкованные к этому краю элементы. Поэтому вначале нужно дабавить к контейнеру элемент с параметром Dock=Fill, а потом пристыковывать элементы к краям родителя.

    Вот простая программа, иллюстрирующая размещение элементов управления в зависимости от порядка их добавления в родительский контейнер.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
                this.Text = "Схематичный Блокнот";
        
                // Создаем многострочный TextBox
                TextBox textBox = new TextBox();
                textBox.Parent = this;
                textBox.Multiline = true;
                textBox.Dock = DockStyle.Fill;// Добавляется первым
        
                // Создаем объект дерева
                TreeView tree = new TreeView();
                tree.Parent = this;// Один способ
                tree.Dock = DockStyle.Left;// Оттесняет первого слева
                tree.Nodes.Add("tree");// Добавили узел
        
                // Создаем панель инструментов
                ToolStrip tool = new ToolStrip();
                this.Controls.Add(tool);// Другой способ
                tool.Items.Add("tool");// Оттесняет другие сверху
                   
                // Создаем меню
                MenuStrip menu = new MenuStrip();
                this.Controls.Add(menu);// Другой способ
                menu.Items.Add("menu");// Оттесняет другие сверху
        
                // Создаем панель состояния
                StatusStrip status = new StatusStrip();
                status.Parent = this;// Один способ
                status.Items.Add("status");// Оттесняет другие снизу
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Результат выполнения такой

    Красными цифрами показаны индексы элементов в коллекции Form.Controls, соответствующие порядку их добавления. Если в коде изменить порядок добавления элементов управления в контейнер формы, то размещение элементов станет другим.

    Стыковка с применением панельных контейнеров

    Для более сложного алгоритма размещения можно использовать элемент управления Panel как контейнер, дочерний по отношению к форме. Этот элемент служит именно для компоновки своих дочерних элементов управления как единой группы. Он хорош тем, что имеет встроенные полосы прокрутки и настраиваемую рамку. Но в обычном элементе Panel нужно явно позиционировать элементы управления или использовать стыковку, которую мы и рассматриваем. Более развитыми с точки зрения размещения являются другие панели: FlowLayoutPanel, TableLayoutPanel, SplitContainer.

    Ниже повторен предыдущий пример с применением сдвоенной панели SplitContainer, в которой пользователь может интерактивно перемещать внутреннюю границу.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
                this.Text = "Применение панелей";
        
                // Создаем двухпанельный элемент с изменяемой границей
                SplitContainer split = new SplitContainer();
                split.Parent = this;// Привязали к форме
                split.Dock = DockStyle.Fill;
                split.Orientation = Orientation.Vertical;// По умолчанию
                // Блокируем автоматическое изменение левой панели
                split.FixedPanel = FixedPanel.Panel1;
        
                // Создаем многострочный TextBox в правой панели
                TextBox textBox = new TextBox();
                split.Panel2.Controls.Add(textBox);// Привязали к правой панели
                textBox.Multiline = true;
                textBox.Dock = DockStyle.Fill;// Распахнули
                // Задаем начальный текст
                textBox.Text = "Буря мглою небо 
        кроет" + Environment.NewLine
                    + "Вихри снежные крутя" + Environment.NewLine
                    + "То как зверь она завоет\r\n"
                    + "То заплачет как дитя\r\n\r\n"
                    + "Привет всем!";
                textBox.WordWrap = false;// Отключаем перенос слов
        
                // Создаем объект дерева в левой панели
                TreeView tree = new TreeView();
                split.Panel1.Controls.Add(tree);// Привязали к левой панели
                tree.Dock = DockStyle.Fill;// Распахнули
                tree.Nodes.Add("tree");// Добавили узел
        
                // Создаем панель инструментов
                ToolStrip tool = new ToolStrip();
                this.Controls.Add(tool);
                tool.Items.Add("tool");// Оттесняет другие сверху
                   
                // Создаем меню
                MenuStrip menu = new MenuStrip();
                this.Controls.Add(menu);// Другой способ
                menu.Items.Add("menu");// Оттесняет другие сверху
        
                // Создаем панель состояния
                StatusStrip status = new StatusStrip();
                status.Parent = this;// Один способ
                status.Items.Add("status");// Оттесняет другие снизу
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Результат выполнения будет таким

    Теперь между деревом и текстом есть изменяемая граница шириной 4 пиксела по умолчанию (задается свойством SplitterWidth ). Когда меняются размеры окна, то и размеры панелей, составляющих объект класса SplitContainer, тоже пропорционально меняются. Чтобы отключить автоматическое изменение одной из панелей, нужно задать свойству SplitContainer.FixedPanel одно из значений Panel1 или Panel2 одноименного перечисления FixedPanel (по умолчанию установлено значение None ).

    Стыковка с применением составных разделителей

    Можно создавать более сложные панельные разделители, помещая внутрь составляющих панелей новые объекты SplitContainer и делая составляющие панели родительскими для этих элементов. Подобным образом построено окно Object Browser оболочки. Например

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
            this.Text = "Составные разделители";
        
            // Создаем родительский двухпанельный элемент
            SplitContainer splitParent = new SplitContainer();
            splitParent.Parent = this;// Привязали к форме
            splitParent.Dock = DockStyle.Fill;
            splitParent.Orientation = Orientation.Vertical;
            splitParent.SplitterWidth = 2;// Толщина разделителя
        
            // Создаем объект дерева в левой панели
            TreeView tree = new TreeView();
            splitParent.Panel1.Controls.Add(tree);// Привязали к левой панели
            tree.Dock = DockStyle.Fill;// Распахнули
            tree.Nodes.Add("tree");// Добавили узел
        
            // В правую панель помещаем дочерний двухпанельный элемент
            SplitContainer splitChild = new SplitContainer();
            splitChild.Parent = splitParent.Panel2;
            splitChild.Dock = DockStyle.Fill;
            splitChild.Orientation = Orientation.Horizontal;
            splitChild.SplitterWidth = 2;// Толщина разделителя
            // Позиционируем разделитель как 2/3 к 1/3 от верха
            splitChild.SplitterDistance = splitChild.Size.Height
                    - splitChild.Size.Height / 3;
        
            // Создаем многострочный TextBox в верхней дочерней панели
            TextBox textBox1 = new TextBox();
            textBox1.Parent = splitChild.Panel1; // Привязали
            textBox1.Multiline = true;
            textBox1.Dock = DockStyle.Fill;// Распахнули
            // Задаем начальный текст
            textBox1.Text = "Буря мглою небо кроет" + 
        Environment.NewLine
                    + "Вихри снежные крутя" + Environment.NewLine
                    + "То как зверь она завоет\r\n"
                    + "То заплачет как дитя\r\n\r\n";
            textBox1.WordWrap = false;// Отключаем перенос слов
        
            // Создаем многострочный TextBox в нижней дочерней панели
            TextBox textBox2 = new TextBox();
            textBox2.Parent = splitChild.Panel2; // Привязали
            textBox2.Multiline = true;
            textBox2.Dock = DockStyle.Fill;// Распахнули
            // Задаем начальный текст
            textBox2.Text = "Привет всем!";
            textBox2.WordWrap = false;// Отключаем перенос слов
        
            // Создаем панель инструментов
            ToolStrip tool = new ToolStrip();
            this.Controls.Add(tool);
            tool.Items.Add("tool");// Оттесняет другие сверху
        
            // Создаем меню
            MenuStrip menu = new MenuStrip();
            this.Controls.Add(menu);// Другой способ
            menu.Items.Add("menu");// Оттесняет другие сверху
        
            // Создаем панель состояния
            StatusStrip status = new StatusStrip();
            status.Parent = this;// Один способ
            status.Items.Add("status");// Оттесняет другие снизу
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат

    Теперь пользователь может интерактивно менять вертикальную и горизонтальную границы панелей.

    Привязка дочерних элементов (свойство Anchor - якорь)

    Свойство Anchor замораживает расстояние дочернего элемента управления от соответствующего края родительского контейнера при изменении размеров последнего. Это свойство может принимать одно из значений или их побитовую комбинацию перечисления AnchorStyles, приведенного в таблице

    Значения перечисления AnchorStyles
    None = 0 (00000000) Нет привязки (по умолчанию)
    Top = 1 (00000001) Привязать к верхнему краю контейнера
    Bottom = 2 (00000010) Привязать к нижнему краю контейнера
    Left = 4 ((00000100) Привязать к левому краю контейнера
    Right = 8 (00001000) Привязать к правому краю контейнера

    Свойство Anchor класса Control является аниподом свойства Dock. Когда присваивается одному свойству значение, отличное от None, автоматически сбрасывается в значение None другое свойство.

    По умолчанию свойство Anchor имеет значение AnchorStyles.Top | AnchorStyles.Left. Если добавить в дочерний элемент к значению Anchor по умолчанию еще AnchoStyles.Right, то при изменении размеров контейнера зазор слева, сверху и справа между элементом и контейнером будет заморожен.

    Приведем пример, в котором на форме расположим 4 текстовых поля с метками. Текстовые поля привяжем к трем сторонам контейнера, кроме нижней.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            int y; // Вынесено в поле класса для видимости в обработчике
        
            public MyClass() // Конструктор
            {
                this.Text = "Привязка Anchor";
        
                int iSpace = Font.Height;// Интерлиньяж
                y = iSpace;// Позиционирование по вертикали
        
                string[] fieldInfo = new string[] {
                    "Фамилия:",
                    "Имя:",
                    "Адрес:",
                    "Профессия:"
                };
        
                // Создаем и настраиваем объекты
                for (int i = 0; i < fieldInfo.Length; i++)
                {
                    Label lbl = new Label();
                    lbl.Parent = this;
                    lbl.AutoSize = true;
                    lbl.Text = fieldInfo[i];
                    lbl.Location = new Point(iSpace, y);
        
                    TextBox txtBox = new TextBox();
                    txtBox.Parent = this;
                    txtBox.Location = new Point(lbl.Right + iSpace, y);
                    int width = this.ClientSize.Width - 
          iSpace - txtBox.Left;
                    txtBox.Size = new Size(width, txtBox.Height);
                    txtBox.Anchor |= AnchorStyles.Right;
        
                    y = txtBox.Bottom + iSpace;// Сдвиг по вертикали
                }
        
                // Подстраиваем форму
                this.ClientSize = new Size(this.ClientSize.Width, y);
                // Убираем системные кнопки
                this.MaximizeBox = false;
                this.MinimizeBox = false;
            }
        
            // Блокируем изменение высоты формы
            protected override void OnResize(EventArgs e)
            {
                base.OnResize(e);
                this.ClientSize = new Size(this.ClientSize.Width, y);
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат

    Если в этой программе интерактивно изменять ширину формы, то ширина полей также будет меняться, поскольку они привязаны справа.

    Последовательная компоновка элементов управления с помощью панели FlowLayoutPanel

    Панель FlowLayoutPanel напоминает окно броузера, компонующего текст и рисунки, только вместо них осуществляется разметка элементов управления. Это тоже контейнер, в котором дочерние элементы панели размещаются последовательно по горизонтали (вертикали) и автоматически перетекают ( flow ) на новые строки (столбцы), если не вмещаются в соответствующие габариты контейнера, если булево свойство WrapContents установлено в true (по умолчанию).

    При WrapContents=false автоматический переток дочерних элементов отключается и программисту нужно организовывать его вручную. Независимо от значения свойства WrapContents для принудительного переноса элемента на новую строку (столбец) используется метод контейнера SetFlowBreak() с аргументом true (по аналогии с дескриптором <br> в HTML ). Он же используется и для отмены этого состояния с аргументом false. Для проверки закрепленного состояния принудительного переноса применяется метод GetFlowBreak().

    Направление перемещения элементов определяется в свойстве FlowLayoutPanel.FlowDirection значениями одноименного перечисления System.Windows.Forms.FlowDirection:

    Значения перечисления FlowDirection
    LeftToRight = 0 Слева направо (по умолчанию)
    TopDown = 1 Сверху вниз
    RightToLeft = 2 Справа налево
    BottomUp = 3 Снизу вверх

    Рассмотренные ранее свойства Dock и Anchor дочерних элементов управления используются в рассматриваемой панели для их выравнивания по вертикали или горизонтали. Если элементы управления размещаются построчно, то размеры условного ограничивающего прямоугольника по высоте определяются самым высоким элементом строки. Тогда значение взаимоисключающих свойств Anchor или Dock элемента определяет его верикальное положение в этом условном прямоугольнике, а именно:

  • AnchorStyles.Top (или DockStyle.Top ) - выравнивание по верхней границе (по умолчанию)
  • AnchorStyles.Bottom (или DockStyle.Bottom ) - выравнивание по нижней границе
  • AnchorStyles.None (или DockStyle.Fill, или DockStyle.Left, или DockStyle.Right ) - выравнивание по центру (с помощью свойства Dock это выполнить нельзя)
  • AnchorStyles.Top | AnchorStyles.Bottom - растяжение по вертикали до достижения границ
  • Значения AnchorStyles.Left и AnchorStyles.Right (или DockStyle.None ) - игнорируются панелью
  • При вертикальном размещении элементы по умолчанию выравниваются по левой границе, но свойства Anchor или Dock позволяют этим выравниванием управлять по той же самой схеме:

  • AnchorStyles.Left - выравнивание по левой границе
  • AnchorStyles.Right - выравнивание по правой границе
  • AnchorStyles.None - выравнивание по центру
  • AnchorStyles.Left | AnchorStyles.Right - растяжение по горизонтали до достижения границ
  • AnchorStyles.Top и AnchorStyles.Bottom - игнорируются панелью
  • Пример использования элемента FlowLayoutPanel для динамического размещения кнопок

    Вот простая программа, иллюстрирующая использование компонующей панели FlowLayoutPanel

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
            // Настраиваем форму
            this.Text = "Компоновочная панель FlowLayoutPanel";
        
            // Создаем и настраиваем компоновочную панель
            FlowLayoutPanel flow = new FlowLayoutPanel();// Создали
            flow.Parent = this;// Привязали к форме
            flow.Dock = DockStyle.Fill;// Растянули по форме
            // Задаем направление размещения дочерних элементов
            flow.FlowDirection = FlowDirection.LeftToRight;
            flow.AutoScroll = true;// Включаем полосы прокрутки
            flow.Name = "flow";// Просто назначаем имя
            // Регистрируем обработчик компоновочной панели
            flow.Click += elementClick;
        
            // Создаем и инициализируем генератор случайных чисел
            // текущим значением секунд в диапазоне от 0 до 59
            Random rand = new Random(DateTime.Now.Millisecond);
        
            // Создаем btnCount кнопок случайных размеров
            const int btnCount = 10;
            for (int i = 0; i < btnCount; i++)
              {
              Button btn = new Button();// Создали кнопку
              btn.Parent = flow;// Привязали к панели
              btn.Text = "Кнопка " + Convert.ToString(i + 1);// Подписали
              btn.AutoSize = true;// Подстроили под текст
              // Регистрируем общий с компоновочной панелью обработчик кнопок
              btn.Click += elementClick;
              // Создаем структуру и инициализируем начальным размером
              Size btnSize = btn.PreferredSize;// Предпочтительный размер
              // Корректируем структуру случайным образом в сторону увеличения
              btnSize.Width = (int)(btnSize.Width * (1 + 2 * 
        rand.NextDouble()));
              btnSize.Height = (int)(btnSize.Height * (1 + 2 * 
        rand.NextDouble()));
              // Задаем размер кнопки
              btn.Size = btnSize;
              // Маркируем
              btn.Name = "btn" + (i + 1);
              // Центрируем построчно
              btn.Anchor = AnchorStyles.None;
              }
            }
        
            // Общий обработчик для кнопок и панели
            void elementClick(object sender, EventArgs e)
            {
                Form frm = new Form();// Создаем вспомогательную форму
                frm.Owner = this;// Отображать поверх владельца
        
                // Формируем заголовок вспомогательного окна
                string title = sender.GetType().Name;// Имя типа
                // Повышаем полномочия ссылки для адресации к свойству Text
                Control ctrl = (Control)sender;
                if (ctrl.Name.Length > 0)
                    title += " (" + ctrl.Name + 
          ")";// Имя объекта, если есть
                if (ctrl.Text.Length > 0)
                    title += " - " + 
        ctrl.Text;// Надпись, если есть
                frm.Text = title;
        
                // Создаем панель отображения свойств выделенного объекта
                PropertyGrid properties = new PropertyGrid();
                properties.Parent = frm;// Привязать
                properties.Dock = DockStyle.Fill;// Распахнуть
                properties.SelectedObject = sender;// Чьи свойства отображать
                properties.PropertySort = PropertySort.Alphabetical;// Сортировать
        
                frm.Show();// Показать окно
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Вот примерный результат

    При горизонтальном направлении размещения кнопки выравниваются по горизонтали, но по вертикали они пристыковываются друг к другу на равных расстояниях и не подлежат выравниванию. Расстоянием между пристыкованными в строке элементами можно управлять с помощью свойств Padding (внутри) и Margin (снаружи), которые являются структурами, ждущими соответствующих настроек.

    Пример использования элемента FlowLayoutPanel для формирования окна About

    Приведем еще пример использования элемента управления FlowLayoutPanel для формирования интерфейса простого окна About.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Diagnostics;// для класса Process
        
    namespace Test
    {
        // Класс приложения
        class MyClass : Form
        {
            public MyClass()
            {
            // Настраиваем форму
            this.Text = "Компоновка окна About";
            //Включили способность менять размеры
            this.AutoSize = true;
            // Разрешить растягиваться и сжиматься
            this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
            // Запретить интерактивное изменение и настроить вид
            this.FormBorderStyle = FormBorderStyle.FixedDialog;
        
            // Создаем контейнер для рисунка
            PictureBox photo = new PictureBox();
            photo.Parent = this;// Привязываем к форме
            photo.Size = new Size(200, 200);// Размер контейнера
            photo.Image = Image.FromFile("MyPhoto.jpg");// Закачали
            // Распахнуть по контейнеру с соблюдением пропорций
            photo.SizeMode = PictureBoxSizeMode.Zoom;
        
            // Создаем и настраиваем компоновочную панель
            FlowLayoutPanel flow = new FlowLayoutPanel();// Создали
            flow.Parent = this;// Привязали к форме
            flow.AutoSize = true;// Пусть подстраивается
            // Направление размещения по столбцам
            flow.FlowDirection = FlowDirection.TopDown;
            // Стыковка к рисунку справа
            flow.Location = new Point(photo.Right, 0);
        
            // Создаем и настраиваем текстовую метку
            Label lbl = new Label();
            lbl.Parent = flow;// Привязали к панели
            lbl.AutoSize = true;// Подстройка под текст
            lbl.Anchor = AnchorStyles.None;// Центрировать в столбце
            lbl.Margin = new Padding(this.Font.Height);// Окаймление
            lbl.Text = "AboutBox Version 1.0";// Содержимое надписи
            lbl.Font = new Font(FontFamily.GenericSansSerif, 
        24, FontStyle.Italic);
        
            // Создаем и настраиваем метку-ссылку
            LinkLabel lnk = new LinkLabel();
            lnk.Parent = flow;// Привязали к панели
            lnk.AutoSize = true;// Подстройка под текст
            lnk.Anchor = AnchorStyles.None;// Центрировать в столбце
            lnk.Margin = new Padding(this.Font.Height);// Окаймление
            lnk.Text = "\x00A9 2007 СФУ";// Значок и текст авторских прав
            lnk.Font = new Font(FontFamily.GenericSansSerif, 16);
            // Область интерактивности ссылки
            lnk.LinkArea = new LinkArea(lnk.Text.Length - 
        3, lnk.Text.Length);
            // Подписываем на событие анонимный обработчик
    lnk.LinkClicked += delegate { Process.Start(@"http://www.gold.krasu.ru"); };
        
            // Создаем и настраиваем кнопку
            Button btn = new Button();
            btn.Parent = flow;// Привязали к панели
            btn.AutoSize = true;// Подстройка под текст
            btn.Anchor = AnchorStyles.None;// Центрировать в столбце
            btn.Margin = new Padding(this.Font.Height);// Окаймление
            btn.Text = "Выход";// Надпись на кнопке
            // Регистрируем анонимный обработчик для закрытия формы
            btn.Click += delegate { this.Close(); };
        
            // Создаем и настраиваем всплывающие подсказки
            ToolTip tips = new ToolTip();
            tips.SetToolTip(photo, "Это значок");
            tips.SetToolTip(lnk, "Зайти на сайт");
            tips.SetToolTip(btn, "Закрыть окно");
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Файл рисунка нужно добавить к проекту командой Add оболочки и настроить его свойства Properties через контекстное меню:

  • Build Action = Embedded Resource - назначить внедренным ресурсом
  • Copy to Output Directory = Copy always - копировать в рабочий каталог всегда при каждой компиляции
  • Вот результат выполнения программы

    Большой пример автоматического размещения элементов управления

    Для размещения радиокнопок как единой группы применяется элемент управления GroupBox. Но он требует жесткого позиционирования своих дочерних элементов. Составим на основе элемента FlowLayoutPanel расширение, которое будет служить контейнером для автоматического размещения радиокнопок. Затем составим класс диалогового окна, в котором динамически разместим пользовательские элементы управления, включая радиокнопки. И наконец, создадим класс главного окна, вызывающего диалоговое окно и реализующего выбор пользователя.

    Вот этот код, который для удобства размещен в нескольких файлах

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс расширения стандартной панели размещения
        class GroupPanel : FlowLayoutPanel
        {
            // Поля класса для видимости в методах
            int xDpi, yDpi;
            new public string Text; // Скрываем наследуемое поле 
                                    // Надпись в рамке
        
            // Конструктор
            public GroupPanel()
            {
                // Перетекание по столбцам
                this.FlowDirection = FlowDirection.TopDown;
                // Отключаем автоматический переток элементов
                this.WrapContents = false;
                //Включили способность менять размеры
                this.AutoSize = true;
                // Разрешить контейнеру растягиваться и сжиматься
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создаем временный контекст устройства
                Graphics gr = this.CreateGraphics();
                // Извлекаем текущее разрешение экрана
                xDpi = (int)gr.DpiX;
                yDpi = (int)gr.DpiY;
                // Освобождаем критичный ресурс
                gr.Dispose();
                // Устанавливаем отступы внутри рамки контейнера
                this.Padding = new Padding(xDpi / 10, yDpi / 10 + 
        Font.Height, xDpi / 10, yDpi / 10);
            }
        
            // Свойство для управления включением кнопки
            public string Check
            {
                // Аксессоры
                set
                {
                // Проверяем, что это радиокнопка и выделяем ее
                // Поиск по имени радиокнопки
                RadioButton radio = this.Controls[value] as RadioButton;
                if (radio != null)
                  radio.Checked = true;
                }
                get
                {
                    foreach (Control ctrl in this.Controls)
                    {
                        // Ищем выделенную радиокнопку и возвращаем имя
                        RadioButton radio = ctrl as RadioButton;
                        if (radio != null  radio.Checked)
                            return radio.Name;
                    }
                    return "";
                }
            }
        
            // Переопределяем виртуальный обработчик изменения шрифта
            protected override void OnFontChanged(EventArgs e)
            {
                base.OnFontChanged(e);// Базовый обработчик
                this.Padding = new Padding(this.Padding.Left,
                    yDpi / 10 + this.Font.Height,
                    this.Padding.Right, this.Padding.Bottom);
            }
        
            // Переопределяем виртуальный обработчик перерисовки формы
            // Сами перекомпоновываем элементы и перерисовываем результат
            protected override void OnPaint(PaintEventArgs e)
            {
                // Извлекаем контекст устройства
                Graphics gr = e.Graphics;
                // Смещение 
                int yIdent = yDpi / 25 + this.Font.Height / 2;
                int xIdent1 = xDpi / 10;
                int xIdent2;// Пока просто объявили
        
                // Отображаем надпись внутри рамки, если она задана
                if (this.Text != null  this.Text.Length > 0)
                {
                    gr.DrawString(
                        " " + this.Text + " ",
                        this.Font,
                        new SolidBrush(this.ForeColor),
                        xIdent1, yDpi / 25
                        );
                    xIdent2 = xIdent1 + (int)(gr.MeasureString(
                        " " + this.Text + " ",
                        this.Font).Width) + 4;// Увеличили отступ справа
                }
                else
                {
                    xIdent2 = xIdent1;
                }
        
                // Создаем перья стандартного цвета и рисуем рамку
                Pen pnDark = new Pen(ControlPaint.Dark
        (BackColor));// Темный
                Pen pnLight = new Pen(ControlPaint.Light
        (BackColor));// Светлый
                
                // Рисуем темную рамку
                gr.DrawLine(pnDark, xIdent1, yIdent, 0, yIdent);
                gr.DrawLine(pnDark, 0, yIdent, 0, this.Height - 2);
                gr.DrawLine(pnDark, 0, this.Height - 2, 
        this.Width - 2, this.Height - 2);
                gr.DrawLine(pnDark, this.Width - 2, this.Height - 2, 
        this.Width - 2, yIdent);
                gr.DrawLine(pnDark, this.Width - 2, yIdent, 
        xIdent2, yIdent);
        
                // Рисуем светлую рамку
                gr.DrawLine(pnLight, xIdent1, yIdent + 1, 
        1, yIdent + 1);
                gr.DrawLine(pnLight, 1, yIdent + 1, 1, 
        this.Height - 3);
                gr.DrawLine(pnLight, 0, this.Height - 1, 
        this.Width - 1, this.Height - 1);
                gr.DrawLine(pnLight, this.Width - 1, this.Height - 1, 
        this.Width - 1, yIdent);
                gr.DrawLine(pnLight, this.Width - 3, yIdent + 1, 
        xIdent2, yIdent + 1);
            }
        }
    }

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    using System.Reflection;// Для динамического определения типов
        
    namespace Test
    {
        // Заготавливаем диалоговое окно, как класс размещения
        // двух пользовательских компоновочных панелей
        class ColorFillDialog : Form
        {
            // Видимые поля
            protected GroupPanel grPanel1, grPanel2;
            protected CheckBox checkBox;
        
            public ColorFillDialog()
            {
            // Настройка диалогового окна
            // Заголовок диалогового окна
            this.Text = "Выбор цвета элипса";
            // Стиль диалогового окна фиксированных размеров
            this.FormBorderStyle = FormBorderStyle.FixedDialog;
            // Отключаем системные кнопки
            this.ControlBox = false;// Системная пиктограмма
            this.MaximizeBox = this.MinimizeBox = false;// Сворачивание
            this.ShowInTaskbar = false;// Не отображать в панели задач
            // Подстраивать размер диалогового окна формы под содержимое
            this.AutoSize = true;
            // Может растягиваться и сжиматься
            this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
            // Создаем внешнюю компоновочную панель
            FlowLayoutPanel flow1 = new FlowLayoutPanel();
            flow1.Parent = this;// Привязали к форме
            flow1.AutoSize = true;// Способность изменять размер
            // Компоновать внутренние элементы по столбцам
            flow1.FlowDirection = FlowDirection.TopDown;
        
            // Создаем внутреннюю компоновочную панель
            FlowLayoutPanel flow2 = new FlowLayoutPanel();
            flow2.Parent = flow1;// Привязали к внешней панели
            flow2.AutoSize = true;//Способность изменять размер
            // Компоновать содержимое по строкам (по умолчанию)
            flow2.FlowDirection = FlowDirection.LeftToRight;
            // Дочерние элементы (выравнивать по высоте
            flow2.Anchor = AnchorStyles.None;
        
            // Размещаем внутри еще две пользовательских (наших) панели
            grPanel1 = new GroupPanel();
            grPanel1.Parent = flow2;
            grPanel1.AutoSize = true;
            grPanel1.Text = "Цвет элипса";// Надпись внутри рамки
        
            grPanel2 = new GroupPanel();
            grPanel2.Parent = flow2;
            grPanel2.AutoSize = true;
            grPanel2.Text = "Фон формы";// Надпись внутри рамки
        
            // Временно замораживаем авторазмещение в панелях
            grPanel1.SuspendLayout();
            grPanel2.SuspendLayout();
        
            // Считываем все общедоступные свойства 
            // библиотечной структуры System.Drawing.Color,
            // применяя технологию отражения
            Type type = typeof(System.Drawing.Color);
            PropertyInfo[] propertiesColor = type.GetProperties();
        
            // Анализируем все свойства и заполняем панели радиокнопками
            foreach (PropertyInfo prop in propertiesColor)
              {
              // Выбираем свойства, которые могут читаться
              // и имеют статические get-аксессоры (а это есть цвета)
              if (prop.CanRead  prop.GetGetMethod().IsStatic)
                {
                // Выбираем цвета с именами на буквы S и P
                if (prop.Name[0] == 'S' || prop.Name[0] == 'P')
                  {
                  // Создаем радиокнопку
                  RadioButton radio = new RadioButton();
                  // Добавляем радиокнопку к нужной панели
                  radio.Parent = prop.Name[0] == 'S' ? 
          grPanel1 : grPanel2;
                  // Включаем способность изменять размер
                  radio.AutoSize = true;
                  // Назначаем надпись и имя для узнаваемости
                  radio.Text = radio.Name = prop.Name;
                  }
                }
              }
        
                // Размораживаем авторазмещение в панелях,
                // чтобы элементы могли скомпоноваться
                grPanel1.ResumeLayout();
                grPanel2.ResumeLayout();
        
                // Добавляем флажок
                checkBox = new CheckBox();
                checkBox.Parent = flow1;// Добавили во внешнюю панель
                checkBox.AutoSize = true; // Подстройка под надпись
                checkBox.Text = "Залитый элипс";
                checkBox.Anchor = AnchorStyles.None;// Выравнивать посредине столбца
        
                // Добавляем еще одну компоновочную панель снизу
                FlowLayoutPanel flow3 = new FlowLayoutPanel();
                flow3.Parent = flow1;
                flow3.AutoSize = true;
                flow3.Anchor = AnchorStyles.None;//Выравнивание посредине столюца
        
                // Добавляем в нижнюю компоновочную панель две кнопки
                Button btn = new Button();
                btn.Parent = flow3;
                btn.AutoSize = true;
                btn.Text = "Выполнить";
                // Для распознавания формой
                btn.DialogResult = DialogResult.OK;
                this.AcceptButton = btn;
        
                btn = new Button();
                btn.Parent = flow3;
                btn.AutoSize = true;
                btn.Text = "Отмена";
                // Для распознавания формой
                btn.DialogResult = DialogResult.Cancel;
                this.CancelButton = btn;
            }
        
            // Свойства
            public System.Drawing.Color Color
            {
                get { return System.Drawing.Color.FromName
        (grPanel1.Check); }
                set { grPanel1.Check = value.Name; }
            }
        
            public System.Drawing.Color Background
            {
                get { return System.Drawing.Color.FromName
        (grPanel2.Check); }
                set { grPanel2.Check = value.Name; }
            }
        
            public bool Fill
            {
                get { return checkBox.Checked; }
                set { checkBox.Checked = value; }
            }
        }
    }

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Главная форма приложения
        class MyClass : Form
        {
            // Поля для хранения выбранных состояний диалогового окна
            Color colorEllipse = Color.Salmon;// Цвет элипса
            bool bFillEllipse = false;// Какой элипс рисовать
        
            public MyClass()
            {
            // Заголовок окна
            this.Text = "Цветовая заливка";
            // Включаем возможность перерисовки формы
            // при изменении ее размеров пользователем
            this.ResizeRedraw = true;
            // Цвет формы
            this.BackColor = Color.PowderBlue;
        
            // Создаем меню
            this.Menu = new MainMenu();// Сам объект меню
            this.Menu.MenuItems.Add("Опции");// Верхний уровень меню
            this.Menu.MenuItems[0].MenuItems.Add(
               "Цвет...", MenuColorOnClick);// Опция и обработчик
            }
        
            // Обработчик опции Color
            private void MenuColorOnClick(object sender, EventArgs e)
            {
            // Создаем наше диалоговое окно налету
            ColorFillDialog dlg = new ColorFillDialog();
            dlg.Color = colorEllipse;// Присваиваем значение поля
            dlg.Fill = bFillEllipse;// Присваиваем состояние флажка
            dlg.Background = this.BackColor;// Включаем нужную радиокнопку
        
            // Показываем диалоговое окно и анализируем выбор пользователя
            if (dlg.ShowDialog() == DialogResult.OK)
              {
              // Запоминаем в поле главной формы новый цвет 
              colorEllipse = dlg.Color;
              // Запоминаем состояние флажка
              bFillEllipse = dlg.Fill;
              // Меняем цвет главной формы
              this.BackColor = dlg.Background;
              // Генерируем необходимость перерисовки 
              // главной формы с новыми установками
              this.Invalidate();
              }
            }
        
            // Переопределяем стандартный обработчик события перерисовки формы
            protected override void OnPaint(PaintEventArgs e)
            {
            // Определяем контекст устройства
            Graphics gr = e.Graphics;
            // Строим прямоугольник над формой
            Rectangle rect = new Rectangle(0, 0,
                    this.ClientSize.Width - 1, this.ClientSize.Height - 1);
            if (bFillEllipse)// Рисуем залитый элипс на главной форме
              gr.FillEllipse(new SolidBrush(colorEllipse), rect);
            else // Рисуем контур незалитого элипса на главной форме
                    gr.DrawEllipse(new Pen(colorEllipse), rect);
            }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Результат выполнения будет таким

    Если не использовать свойство Check, то GroupPanel можно использовать для размещения и других элементов управления, например, флажков.

    Табличная компоновка элементов управления с помощью панели TableLayoutPanel

    Панель TableLayoutPanel позволяет размещать дочерние элементы в сетке строк и столбцов, наподобие таблиц в HTML. Свойство AutoSize, установленное в true, позволяет автоматически подгонять ячейки таблицы под размеры размещаемых в ней элементов. Границы ячеек таблицы можно сделать видимыми с помощью ее свойства CellBorderStyle, присвоив одно из значений перечисления TableLayoutPanelCellBorderStyle, но для размещения обычно это не нужно.

    Размер сетки задается с помощью свойств RowCount и ColumnCount, которые по умолчанию равны 0, и это то же самое, что задать 1. Для автоматического добавления новых строк или столбцов используется свойство GrowStyle, ожидающее одно из значений перечисления TableLayoutPanelGrowStyle: FixedSize, AddRows (по умолчанию), AddColumns. Если одна координата таблицы добавляется автоматически, то другая координата определяется свойством RowCount или ColumnCount соответственно.

    Значение FixedSize нужно присваивать только после того, как будут заданы значения свойств RowCount и ColumnCount. При этом, если количество динамически размещаемых в таблице элементов управления превысит количество ячеек, будет сгенерировано исключение. Ячейки таблицы можно заполнять избирательно. Если предположить, что table - экземпляр таблицы, а ctrl - размещаемый в ячейке ( row, col ) элемент управления, то код будет таким

    table.Controls.Add(ctrl, col, row);

    Целые числа col и row отсчитываются от нуля. Если при вызове метода Add() в адресуемой ячейке таблицы уже находится элемент управления, то он будет перемещен в следующую ячейку.

    Можно узнать координаты размещенного в таблице элемента управления, используя следующий код

    TableLayoutPanelCellPosition cellPos = table.GetCellPosition(ctrl);

    Поля Row и Column структуры TableLayoutPanelCellPosition дают координаты ячейки таблицы:

    int row = cellPos.Row;
    int col = cellPos.Column;

    Для элемента управления ячейка таблицы считается родителем, поэтому размещение в рамках ячейки определяется свойствами элемента Dock или Anchor.

    Приведем пример, иллюстрирующий работу элемента TableLayoutPanel.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Форма приложения
        class MyClass : Form
        {
        // Вынесены в поля для видимости в обработчике
        Panel panelColor;// Ссылка для адресации правой панели SplitContainer
        Label[] arrayLabelValue = new Label[3];// Массив для значений цветов
        
        public MyClass()
        {
        this.Text = "Использование табличной компоновки TableLayoutPanel";
        this.Width *= 2;// Увеличим ширину формы
        
        // Создаем двухпанельный элемент с изменяемой границей
        SplitContainer split = new SplitContainer();
        split.Parent = this;// Привязали к форме
        split.Dock = DockStyle.Fill;// Развернули на всю форму
        split.Orientation = Orientation.Vertical;// По умолчанию
        split.SplitterDistance = this.ClientSize.Width / 2;// Позиция движка
        split.TabStop = false;// Отключаем получение фокуса по клавише Tab
        
        // Создаем в левой панели элемент TableLayoutPanel
        TableLayoutPanel table = new TableLayoutPanel();
        table.Parent = split.Panel1;// Назначили родителя
        table.Dock = DockStyle.Fill;// Развернули на всю левую панель
        table.BackColor = Color.White;// Цвет фона
        // Установили размер таблицы 3x3
        table.RowCount = 3;
        table.ColumnCount = 3;
        
        // Адресуем правую панель полем
        panelColor = split.Panel2;
        
        // Создаем двумерный массив для названий RGB
        string[,] arrayNameColors = {
          {"Красный", "Зеленый", "Синий" },
          {"Red", "Green", "Blue"}
                                            };
        // Создаем массив для числовых значений цветов 
        // и инициализируем начальным цветом фона правой панели
        int[] arrayValueColors ={ 
          panelColor.BackColor.R,     // Red - красный
          panelColor.BackColor.G,     // Green - зеленый
          panelColor.BackColor.B };   // Blue - синий
        
        // Создаем шрифт для текстовых меток
        Font font = new Font("Arial", 10, FontStyle.Bold);
        
        // Заполнение таблицы по столбцам
        for (int col = 0; col < 3; col++)
          {
          // Первая строка с названиями цветов
          Label lbl = new Label();// Создали объект
          lbl.AutoSize = true;// Подстройка метки под текст
          lbl.Anchor = AnchorStyles.None;// Расположиться в центре ячейки
          lbl.Text = arrayNameColors[0, col];// Русское название цвета
          lbl.ForeColor=Color.FromName(arrayNameColors[1, col]);// Цвет метки
          lbl.Font = font;// Установили шрифт
          table.Controls.Add(lbl, col, 0);// Поместить в ячейку таблицы
        
          // Вторая строка с полосами прокрутки
          VScrollBar scroll = new VScrollBar();// Создали объект
          // Растянули по высоте средней строки
          scroll.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
          // Включили отклик на клавишу Tab для смены фокуса ввода
          scroll.TabStop = true;
          // Установили длину ползунка и шаг скролирования
          scroll.LargeChange = 16;// 16 * 16 = 256 цветов
          // Когда ползунок внизу
          scroll.Maximum = 255 + scroll.LargeChange - 1;
          // Устанавливаем начальное значение ползунка
          scroll.Value = arrayValueColors[col];
          // Подписались на событие при отпускании ползунка 
          scroll.ValueChanged += scroll_ValueChanged;
          table.Controls.Add(scroll, col, 1);// Поместить в ячейку таблицы
        
          // Третья строка со значениями цвета
          arrayLabelValue[col] = new Label();// Создали объект
          arrayLabelValue[col].AutoSize = true;// Подстройка под текст
          arrayLabelValue[col].Anchor = AnchorStyles.None;// В центре ячейки
          // Установим цвет текстовой метки
          arrayLabelValue[col].ForeColor = Color.FromName
        (arrayNameColors[1, col]);
          // Связываем свойство Text метки и свойство Value полосы прокрутки
          arrayLabelValue[col].DataBindings.Add("Text", 
        scroll, "Value");
          arrayLabelValue[col].Font = font;// Установили шрифт
          // Добавляем настроенную текстовую метку значения цвета в ячейку таблицы
          table.Controls.Add(arrayLabelValue[col], col, 2);
        
          // Устанавливаем столбцы таблицы на три равных ширины
          table.ColumnStyles.Add(new ColumnStyle
      (SizeType.Percent, 100.0f / 3));
          }
        
        // Устанавливаем среднюю строку таблицы максимально большой
        table.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Первая строка
        table.RowStyles.Add(new RowStyle(SizeType.Percent, 
      100.0f));// Вторая строка
        table.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Третья строка
        }
        
        void scroll_ValueChanged(object sender, EventArgs e)
        {
        // Меняем цвет фона правой панели
        panelColor.BackColor = Color.FromArgb(
          int.Parse(arrayLabelValue[0].Text),// Преобразуем в число
          int.Parse(arrayLabelValue[1].Text),// Преобразуем в число
          int.Parse(arrayLabelValue[2].Text) // Преобразуем в число
        );
        }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат выполнения программы

    Поизменяйте размеры формы и разделителя и подивитесь, как автоматически перекомпоновываются элементы управления слева.

    Соседние ячейки таблицы можно объединять как вдоль столбцов, так и вдоль строк. При этом в первой из объединяемых ячеек прежде уже должен находиться элемент управления. Другие элементы управления, которые находятся в следующих из объединяемых ячеек, сдвигаются на новые позиции. При этом, если свободных ячеек не хватает, приложение генерирует исключение.

    Объединение ячеек панели TablelayoutPanel можно выполнить ее методами

    SetColumnSpan(ctrl, span);SetRowSpan(ctrl, span);
    где ctrl - элемент управления, расположенный в первой 
               из объединяемых ячеек
        span - количество объединяемых ячеек

    Эти методы можно вызвать, при необходимости, для одного и того же элемента управления, освободив для него пространство как по горизонтали, так и по вертикали. Для такого элемента управления методы

    GetColumnSpan(ctrl);
    GetRowSpan(ctrl);

    возвращают количество объединенных ячеек.

    Для элемента управления, занимающего несколько строк и столбцов, метод GetPositionFromControl(ctrl) возвращает структуру TableLayoutPanelCellPosition, в которой поля Row и Column дают координаты самой левой и самой верхней из объединенных ячеек таблицы, которую занимает элемент управления ctrl. При адресации к любой из объединенных ячеек таблицы с помощью метода GetControlFromPosition(col, row) можно получить адрес находящегося там элемента управления.

    Большой пример разработки диалогового окна выбора шрифта

    Попробуем разработать свою программу, которая дублирует стандартный диалог выбора шрифта и реализует максимум его функциональности. Для начала приведем программу, вызывающую этот стандартный диалог, чтобы посмотреть, как он выглядит. Вот эта простая вызывающая программа

    using System.Windows.Forms;
        
    namespace Test
    {
        // Запуск
        class Program
        {
            static void Main()
            {
                // Создаем объект окна стандартного диалога выбора шрифта
                FontDialog fontDialog = new FontDialog();
                // Показать дополнительные части диалогового окна
                fontDialog.ShowEffects = true;
                fontDialog.ShowColor = true;
                //fontDialog.ShowApply = true;// Кнопка "Применить"
                //fontDialog.ShowHelp = true; // Кнопка "Справка"
        
                // Показать само окно
                fontDialog.ShowDialog();
            }
        }
    }

    Вот так примерно должен выглядеть наш диалог выбора шрифта

    Можно заметить, что элементы управления стандартного диалогового окна размещены в таблице, которая имеет 3 строки и 4 столбца. Код каждой сложной части диалога изолируем в отдельные классы и поместим в отдельные одноименные файлы. Ниже приведены все составляющие данного примера.

    Самоподстраивающийся контейнер GroupPanel

    Приведем рассмотренный ранее код компоновочного контейнера с рамкой. Предварительно удалим из него свойство Check, связанное с размещением радиокнопок и организацией их согласованной работы. Хотя можно это свойство и не удалять - все равно мы его использовать в данном примере не будем. Вот код панели, который нужно разместить в отдельном файле GroupPanel.cs

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Класс расширения стандартной панели размещения
        class GroupPanel : FlowLayoutPanel
        {
            // Поля класса для видимости в методах
            int xDpi, yDpi;
            new public string Text; // Скрываем наследуемое поле 
            // Надпись в рамке
        
            // Конструктор
            public GroupPanel()
            {
                // Перетекание по столбцам
                this.FlowDirection = FlowDirection.TopDown;
                // Отключаем автоматический переток элементов
                this.WrapContents = false;
                //Включили способность менять размеры
                this.AutoSize = true;
                // Разрешить панели растягиваться и сжиматься
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        
                // Создаем временный контекст устройства
                Graphics gr = this.CreateGraphics();
                // Извлекаем текущее разрешение экрана
                xDpi = (int)gr.DpiX;
                yDpi = (int)gr.DpiY;
                // Освобождаем критичный ресурс - контекст устройства
                gr.Dispose();
                // Устанавливаем отступы внутри рамки контейнера
                this.Padding = new Padding(xDpi / 10, yDpi / 10 + 
        Font.Height, xDpi / 10, yDpi / 10);
            }
        
            // Переопределяем виртуальный обработчик изменения шрифта
            protected override void OnFontChanged(EventArgs e)
            {
                base.OnFontChanged(e);// Базовый обработчик
                this.Padding = new Padding(this.Padding.Left,
                    yDpi / 10 + this.Font.Height,
                    this.Padding.Right, this.Padding.Bottom);
            }
        
            // Переопределяем виртуальный обработчик перерисовки формы
            // Сами перекомпоновываем элементы и перерисовываем результат
            protected override void OnPaint(PaintEventArgs e)
            {
                // Извлекаем контекст устройства
                Graphics gr = e.Graphics;
                // Смещение 
                int yIdent = yDpi / 25 + this.Font.Height / 2;
                int xIdent1 = xDpi / 10;
                int xIdent2;// Пока просто объявили
        
                // Отображаем надпись внутри рамки, если она задана
                if (this.Text != null  this.Text.Length > 0)
                {
                    gr.DrawString(
                        " " + this.Text + " ",
                        this.Font,
                        new SolidBrush(this.ForeColor),
                        xIdent1, yDpi / 25
                        );
                    xIdent2 = xIdent1 + (int)(gr.MeasureString(
                        " " + this.Text + " ",
                        this.Font).Width) + 7;// Увеличили отступ справа
                }
                else
                {
                    xIdent2 = xIdent1;
                }
        
                // Создаем перья стандартного цвета и рисуем рамку
                Pen pnDark = new Pen(ControlPaint.Dark
        (BackColor));// Темный
                Pen pnLight = new Pen(ControlPaint.Light
        (BackColor));// Светлый
        
                // Рисуем темную рамку
                gr.DrawLine(pnDark, xIdent1, yIdent, 0, yIdent);
                gr.DrawLine(pnDark, 0, yIdent, 0, this.Height - 2);
                gr.DrawLine(pnDark, 0, this.Height - 2, 
        this.Width - 2, this.Height - 2);
                gr.DrawLine(pnDark, this.Width - 2, this.Height - 2, 
        this.Width - 2, yIdent);
                gr.DrawLine(pnDark, this.Width - 2, yIdent, 
        xIdent2, yIdent);
        
                // Рисуем светлую рамку
                gr.DrawLine(pnLight, xIdent1, yIdent + 1, 1, yIdent + 1);
                gr.DrawLine(pnLight, 1, yIdent + 1, 1, this.Height - 3);
                gr.DrawLine(pnLight, 0, this.Height - 1, 
        this.Width - 1, this.Height - 1);
                gr.DrawLine(pnLight, this.Width - 1, this.Height - 1, 
        this.Width - 1, yIdent);
                gr.DrawLine(pnLight, this.Width - 3, yIdent + 1, 
        xIdent2, yIdent + 1);
            }
        }
    }

    Класс управления списком стиля шрифта StyleComboBox

    Разработаем класс StyleComboBox, который будет реализовывать поддержку стилей шрифтов и его экземпляр будет размещен в ячейке под текстовой меткой "Начертание". Этот класс будет расширением класса стандартного элемента управления поля со списком ComboBox. В нем нужно учесть, что не все семейства шрифтов поддерживают полный набор стилей:

    Regular - обычный
    Bold - полужирный
    Italic - курсив
    Bold Italic - полужирный курсив

    а также нужно учесть дополнительные стили, применимые ко всем вышеперечисленным стилям:

    Strikeout - зачеркнутый
    Underline - подчеркнутый

    Этот класс должен самостоятельно заполнять список доступными стилями при выборе пользователем конкретного шрифта, а также поддерживать поле списка при щелчке на соответствующем стиле. Вот этот класс-расширение StyleComboBox, размещенный в отдельном одноименном файле

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Расширяем библиотечный ComboBox на управление стилем
        class StyleComboBox : ComboBox
        {
            // Конструктор настройки стиля как списка с редактируемым полем
            public StyleComboBox()
            {
                this.DropDownStyle = ComboBoxStyle.DropDown;// По умолчанию
            }
        
            // Свойство типа перечисления System.Drawing.FontStyle
            // Обеспечивает управление содержимым самого поля списка
            // Преобразует название стиля в поле в стиль и наоборот
            public FontStyle FontStyle
            {
            // Для того, чтобы заполнять русскими названиями стилей
            get
            {
            // Возвращаем стиль, соответствующий названию в поле
            if (this.SelectedItem.ToString() == "жирный курсив")
              return FontStyle.Bold | FontStyle.Italic;
            else if (this.SelectedItem.ToString() == "жирный")
              return FontStyle.Bold;
            else if (this.SelectedItem.ToString() == "курсив")
              return FontStyle.Italic;
            else
              return FontStyle.Regular;
            }
            set 
            {
            // Преобразуем выбранный в списке стиль 
            // в название стиля в поле списка
            // Применяется прием - побитовая проверка по маске
            if ((value  FontStyle.Bold)!=0 
               (value  FontStyle.Italic) != 0)
            this.SelectedItem = "жирный курсив";
            else if ((value  FontStyle.Bold) != 0)
               this.SelectedItem = "жирный";
            else if ((value  FontStyle.Italic) != 0)
              this.SelectedItem = "курсив";
            else
              this.SelectedItem = "обычный";
            }
            }
        
            // Метод добавляет в список название стиля, если он есть в шрифте,
            // но отсутствует в списке, или удаляет из списка старое название,
            // если оно есть в списке, но соответствующий стиль отсутствует в шрифте
            void TryStyle(string strFamilyName, FontStyle style, string strStyleName)
            {
            // Все параметры входные
            // strFamilyName - имя шрифта
            // style - стиль, соответствующий названию
            // strStyleName - название стиля в списке
        
            // Ищем название в списке. Если не найдено, то возвращается -1
            int index = this.FindStringExact(strStyleName);
        
            // Безопасный код
            try
            {
            // Пробуем создать объект заданного шрифта. Если он есть,
            // то исключение не возникает, и если ранее не найден в списке,
            // добавляем его название в список
            new Font(strFamilyName, 12, style);
            if (index == -1)
              Items.Add(strStyleName);
            }
            catch
            {
            // На случай, если при смене шрифта список не очищаем!!!
            // Откат, заказанного стиля в данном шрифте нет. Корректируем 
            // список, если в нем осталось название стиля от прежнего шрифта
            if (index != -1)
              {
              this.Items.Remove(strStyleName);
              }
            }
            }
        
            // Свойство, заполняющее список стилями для заданного шрифта
            public string FamilyName
            {
            set
            {
            // Для сохранения выделенного стиля при смене шрифта
            string oldStyleName = "";
            if (this.SelectedIndex != -1)// Игнорируем первый запуск
              oldStyleName = this.SelectedItem.ToString();
        
            // Очищаем список и заполняем заново,
            // чтобы соблюсти порядок следования названий стилей
            // необязательно, но тогда названия будут - как придется
            this.Items.Clear();
        
            // Выстраиваем порядок стилей как в стандартном диалоге
            TryStyle(value, FontStyle.Regular, "обычный");
            TryStyle(value, FontStyle.Italic, "курсив");
            TryStyle(value, FontStyle.Bold, "жирный");
            TryStyle(value, FontStyle.Bold | FontStyle.Italic, 
        "жирный курсив");
        
            // Выделяем позицию списка
            int index = this.FindStringExact(oldStyleName);
            if (index != -1)// Если выделенный стиль имеется и в новом шрифте
              this.SelectedIndex = index;
            else // Нет ранее выделенного стиля в новом шрифте, сброс в начало
              this.SelectedIndex = 0;
            }
            }
        
            // Свойство для возврата русского названия текущего стиля
            public string StyleNameRus
            {
                get { return this.SelectedItem.ToString(); }
            }
        }
    }

    Класс управления раскрывающимся списком цвета ColorComboBox

    Выберем несколько цветов для нашего списка управления цветом, которые приведены в таблице.

    Из-за трудностей перевода не будем вставлять в список русские названия цветов, а расположим в нем образцы цвета вместе с их оригинальными названиями.

    Вот класс-расширение ColorComboBox, размещенный в отдельном одноименном файле

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Расширяем библиотечный ComboBox на управление цветом
        class ColorComboBox : ComboBox
        {
        public ColorComboBox()// Конструктор начальных настроек
          {
          // Заполняем источник данных ComboBox массивом названий цветов
          this.DataSource = new string[] {
            "Black", "Maroon", "Green", 
      "Olive", "Navy", "Purple", 
      "Teal", "Gray", "Silver", 
      "Red", "Lime", "Yellow",
            "Blue", "Fuchsia", "Aqua", 
      "White"
            };
        
          // Устанавливаем режим работы как раскрывающийся список
          this.DropDownStyle = ComboBoxStyle.DropDownList;
          // Цветные прямоугольники в списке будет отрисовывать владелец
          this.DrawMode = DrawMode.OwnerDrawFixed;
          // Устанавливаем высоту элементов списка,
          // равную высоте унаследованного системного шрифта
          this.ItemHeight = this.Font.Height;
          }
        
            // Свойство чтения/установки цвета
            public Color Color
            {
            get { return Color.FromName(this.SelectedItem.ToString()); }
            set
              {
              // Ищем переданный цвет и позиционируем список
              this.SelectedItem = value.Name;
              /*// Либо вот такой код - вместо предыдущей строки!!!
              int index = this.FindStringExact(value.Name);
              if (index != -1)
                this.SelectedIndex = index;
              //*/
              }
            }
        
            // Переопределяем событие списка, требующее перерисовки элемента
            protected override void OnDrawItem(DrawItemEventArgs e)
            {
                // Определяем контекст устройства
                Graphics gr = e.Graphics;
        
                // Вычисляем размеры для прямоугольника самого цвета
                // как левая верхняя точка привязки, ширина, высота
                // по параметрам элемента списка
                Rectangle rectColor = new Rectangle(e.Bounds.Left, 
                    e.Bounds.Top,
                    2 * e.Bounds.Height, 
                    e.Bounds.Height);
                // Раздувает прямоугольник указанным количеством
                rectColor.Inflate(-1, -1);
        
                // Вычисляем размеры для прямоугольника названия цвета
                // как левая верхняя точка привязки, ширина, высота
                Rectangle rectText = new Rectangle(e.Bounds.Left + 
        2 * e.Bounds.Height,
                    e.Bounds.Top,
                    e.Bounds.Width - 2 * e.Bounds.Height,
                    e.Bounds.Height);
        
                // Рисуем фон заданным цветом
                e.DrawBackground();// Нацелились на фон
                gr.DrawRectangle(Pens.Black, rectColor);// Рамка
                gr.FillRectangle(
                    new SolidBrush(Color.FromName
          (this.Items[e.Index].ToString())),
                    rectColor);// Заливка
        
                // Рисуем название цвета
                gr.DrawString(this.Items[e.Index].ToString(), base.Font,
                    new SolidBrush(e.ForeColor), rectText);
            }
        }
    }

    Класс самого диалогового окна выбора шрифта NewFontDialog

    Теперь скомпонуем все необходимые элементы на форме, которая будет представлять наше диалоговое окно NewFontDialog выбора шрифта. Код можно разместить в отдельном одноименном файле NewFontDialog.cs

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
    // Расширяем библиотечный класс Form
    class NewFontDialog : Form
      {
      // Видимые поля-ссылки
      ComboBox comboFont;// Для списка "Шрифт:"
      ComboBox comboSize;// Для списка "Размер:"
      StyleComboBox comboStyle;// Для нашего списка "Начертание:"
      // Для флажков "Зачеркнутый" и "Подчеркнутый"
      CheckBox checkStrikeout, checkUnderline;
      ColorComboBox comboColor;// Для нашего списка "Цвет:"
      // Для надписей "Цвет:" и "Набор символов:"
      Label labelColor, labelSample;
        
      public NewFontDialog()// Конструктор диалогового окна
        {
        // Определяем стиль формы как диалоговое окно
        // с фиксированными размерами
        this.FormBorderStyle = FormBorderStyle.FixedDialog;
        // Отключаем системные кнопки
        this.MinimizeBox = this.MaximizeBox = false;
        // Не отображать в панели задач Windows
        this.ShowInTaskbar = false;
        this.Text = "Шрифт (самодельный диалог)";
        // Подстраиваться под содержимое
        this.AutoSize = true;
        
        // Создаем и настраиваем объект панели табличной компоновки
        TableLayoutPanel table = new TableLayoutPanel();
        table.Parent = this;// Привязали к форме
        table.AutoSize = true;// Подстраиваться под содержимое
        table.RowCount = 3;     // Количество строк таблицы
        table.ColumnCount = 4;  // Количество столбцов таблицы
        // Задаем отступы содержимого ячеек от краев ячеек, равные
        // высоте установленного в базовом классе Form шрифта 
        table.Padding = new Padding(base.Font.Height);
        
        // ***** Заполняем три ячейки верхней строки таблицы *****
        Label lbl = new Label();
        lbl.Text = "Шрифт:";
        lbl.AutoSize = true;// Подстроиться под текст
        table.Controls.Add(lbl, 0, 0);// Отдали ячейке (col, row)
        
        lbl = new Label();
        lbl.Text = "Начертание:";
        lbl.AutoSize = true;// Подстроиться под текст
        table.Controls.Add(lbl, 1, 0);// Отдали ячейке (col, row)
        
        lbl = new Label();
        lbl.Text = "Размер:";
        lbl.AutoSize = true;// Подстроиться под текст
        table.Controls.Add(lbl, 2, 0);// Отдали ячейке (col, row)
        
        // ***** Заполняем три ячейки средней строки таблицы *****
        // Заполняем ячейку с наименованиями шрифтов
        comboFont = new ComboBox();
        comboFont.DropDownStyle = ComboBoxStyle.Simple;// Простой список
        // Привязать к верхней и нижней границам ячейки
        //comboFont.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
        comboFont.Dock = DockStyle.Fill;// Распахнуть на всю ячейку
        // Подписываемся на событие выбора и регистрируем общий обработчик
        comboFont.TextChanged += new EventHandler(combo_Changed);
        // Заполняем поля списка именами семейства шрифтов
        foreach (FontFamily font in FontFamily.Families)
        {
        comboFont.Items.Add(font.Name);
        }
        // Позиционируем поле на текущий базовый шрифт
        comboFont.SelectedItem = base.Font.FontFamily.Name;
        table.Controls.Add(comboFont, 0, 1);// Отдали ячейке (col, row)
        
        // Заполняем ячейку списка стилей
        comboStyle = new StyleComboBox();// Наш класс
        comboStyle.DropDownStyle = ComboBoxStyle.Simple;// Простой список
        // Привязать к верхней и нижней границам ячейки
        comboStyle.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
        // Подписываемся на событие выбора и регистрируем общий обработчик
        comboStyle.TextChanged += new EventHandler(combo_Changed);
        // Принудительно вызываем обработчик
        // combo_Changed(comboStyle, new EventArgs());
        table.Controls.Add(comboStyle, 1, 1);
       // Отдали ячейке (col, row)
        
        // Заполняем ячейку списка выбора размеров шрифта
        comboSize = new ComboBox();
        comboSize.DropDownStyle = ComboBoxStyle.Simple;// Простой список
        // Привязать к верхней и нижней границам ячейки
        comboSize.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
        comboSize.Width = 5 * base.Font.Height;// Ширина
        // Подписываемся на событие выбора и регистрируем общий обработчик
        comboSize.TextChanged += new EventHandler(combo_Changed);
        // Заполняем поля списка стандартными размерами шрифтов
        for (int i = 8; i < 12; i++)
                  comboSize.Items.Add(i);
        for (int i = 12; i < 30; i += 2)
                  comboSize.Items.Add(i);
        for (int i = 36; i <= 72; i += 12)
                  comboSize.Items.Add(i);
        comboSize.SelectedIndex = 0;// Выделяем первое поле списка
        table.Controls.Add(comboSize, 2, 1);// Отдали ячейке (col, row)
        
        // ***** Заполняем три ячейки последней строки таблицы *****
        // Создаем экземпляр нашей ранее созданной рамки GroupPanel
        GroupPanel groupPanel = new GroupPanel();
        groupPanel.Text = "Видоизменение";
        table.Controls.Add(groupPanel, 0, 2);// Отдали ячейке (col, row)
        
        // Размещаем в панели GroupPanel флажок "Зачеркнутый"
        checkStrikeout = new CheckBox();
        checkStrikeout.Text = "Зачеркнутый";
        checkStrikeout.AutoSize = true;
        // Подписываем на событие анонимный обработчик, вызывающий
        // наш метод модификации текущего стиля в списке новым атрибутом
        checkStrikeout.Click += delegate { ShowNewFont(); };
        checkStrikeout.Parent = groupPanel;// Отдали панели
        
        // Размещаем в панели GroupPanel флажок "Подчеркнутый"
        checkUnderline = new CheckBox();
        checkUnderline.Text = "Подчеркнутый";
        checkUnderline.AutoSize = true;
        checkUnderline.Click += delegate { ShowNewFont(); };
        checkUnderline.Parent = groupPanel;// Отдали панели
        
        // Размещаем в панели GroupPanel текстовую метку "Цвет:"
        labelColor = new Label();
        labelColor.Text = "Цвет:";
        labelColor.AutoSize = true;
        labelColor.Visible = false;// Вначале отключили
        checkUnderline.Parent = groupPanel;// Отдали панели
        
        // Размещаем в панели GroupPanel поле со списком цветов
        comboColor = new ColorComboBox();
        comboColor.Anchor = AnchorStyles.Bottom;
        // Подписываемся на события выбора и программного
        // изменения названия цвета и регистрируем общий обработчик
        comboColor.SelectedValueChanged += new EventHandler(combo_Changed);
        comboColor.Visible = false;// Вначале отключили
        comboColor.Parent = groupPanel;// Отдали панели
        
        // Заполняем ячейку таблицы для размещения образца
        groupPanel = new GroupPanel();// Рамка
        groupPanel.Text = "Образец";
        table.Controls.Add(groupPanel, 1, 2);// Отдали ячейке (col, row)
        // Объединяем две средних ячейки последней строки
        table.SetColumnSpan(groupPanel, 2);
        groupPanel.Dock = DockStyle.Top;// Привязать к ячейке сверху 
        
        // Сам образец
        labelSample = new Label();
        labelSample.Text = "AaBbБбФф";
        labelSample.Font = base.Font;// Шрифт от формы
        // Текст размещать в середине текстовой метки
        labelSample.TextAlign = ContentAlignment.MiddleCenter;
        labelSample.BorderStyle = BorderStyle.Fixed3D;// Рельефно
        int width = groupPanel.Width - 2 * base.Font.Height;// Отнимаем Padding
        labelSample.Size = new Size(width, 3 * base.Font.Height);
        labelSample.Parent = groupPanel;// Отдали рамке
        
        // Создаем кнопки в последнем столбце средней строки
        // Панель FlowLayoutPanel для кнопок
        FlowLayoutPanel flow = new FlowLayoutPanel();
        flow.AutoSize = true;
        flow.FlowDirection = FlowDirection.TopDown;// Перетекать сверху вниз
        table.Controls.Add(flow, 3, 1);// Отдали ячейке (col, row)
        
        // Помещаем в панель кнопку OK
        Button btn = new Button();
        btn.Text = "OK";
        btn.AutoSize = true;
        btn.DialogResult = DialogResult.OK;// Для передачи форме
        this.AcceptButton = btn;// Для передачи форме
        btn.Parent = flow;// Отдали панели
        
        // Помещаем в панель кнопку Cancel
        btn = new Button();
        btn.Text = "Отмена";
        btn.AutoSize = true;
        btn.DialogResult = DialogResult.Cancel;// Для передачи форме
        this.CancelButton = btn;// Для передачи форме
        btn.Parent = flow;// Отдали панели
        }
        
        // Назначаем свое свойство Font
        new public Font Font
          {
          // Извлекаем настройки шрифта из образца
          get
          {
          return labelSample.Font;
          }
          // Препарируем объект Font и полученную информацию 
          // рассовываем по элементам пользовательского интерфейса
          set 
          {
          // Устанавливаем шрифт образца
          labelSample.Font = value;
          // Позиционируем поле списка шрифтов
          comboFont.SelectedItem = value.FontFamily.Name;
        
          // Заполняем список стилей для заданного шрифта
          comboStyle.FamilyName = value.FontFamily.Name;
          // Позиционируем поле списка стилей
          comboStyle.FontStyle = value.Style;
        
          // Включаем флажки, если есть дополнительные стили
          checkStrikeout.Checked = (value.Style  
         FontStyle.Strikeout) != 0;
          checkUnderline.Checked = (value.Style  
        FontStyle.Underline) != 0;
        
          // Извлекаем из объекта Font размер шрифта в пунктах
          // и сохраняем в списке
          comboSize.SelectedItem = value.SizeInPoints;
          comboSize.Text = value.SizeInPoints.ToString();
          }
          }
        
            // Собственное свойство диалогового окна
            // Имеет значение текущей установки списка цвета
            // Свойство ждет структуру System.Drawing.Color
            public Color Color
            {
                get
                {
                    return comboColor.Color;
                }
                set
                {
                    comboColor.Color = value;
                }
            }
        
            // Свойство проверки и управления видимостью 
            // интерфейсного блока "Цвет:"
            public bool ShowColor
            {
                get
                {
                    // Проверяем видимость
                    return comboColor.Visible;
                }
                set
                {
                    // Переключаем видимость
                    labelColor.Visible = comboColor.Visible = value;
                }
            }
        
            // Свойство русского названия текущего стиля 
            // Перекинули диалоговому окну из класса StyleComboBox
            public string StyleNameRus
            {
                get { return comboStyle.StyleNameRus; }
            }
        
            // Наш метод модификации текущего стиля в списке новым атрибутом
            void ShowNewFont()
            {
            FontStyle style;
        
            // Безопасный код
            try
            {
            style = comboStyle.FontStyle;
            }
            catch
            {
            return;
            }
        
            // Готовим образец модифицированного стиля
            if (checkStrikeout.Checked)
                    style |= FontStyle.Strikeout;
            if (checkUnderline.Checked)
                    style |= FontStyle.Underline;
        
            // Создаем новый шрифт и применяем его для текстовой метки
            try
            {
            System.Drawing.Font font = new Font(comboFont.Text,
                        float.Parse(comboSize.Text), style);
            labelSample.Font = font;
            }
            catch { }// Ничего не делать, но присутствия требует синтаксис
            }
        
            // Общий обработчик
            void combo_Changed(object sender, EventArgs e)
            {
            ComboBox combo = sender as ComboBox;// Преобразуем ссылку
            if (combo == null)
                    return;// Не является объектом ComboBox
        
            // Меняем цвет метки образца, если она уже создана
            if (combo == comboColor  labelSample != null)
            {
            labelSample.ForeColor = comboColor.Color;
            return;
            }
        
            if (combo == comboFont)
            {
            // Вычисляем индекс запрошенного шрифта
            int index = comboFont.FindStringExact(comboFont.Text);
        
            // Если нашли, выделяем шрифт и извлекаем для него стили
            // при условии, что объект ComboBox стилей уже создан
            if (index != -1  comboStyle != null)
            {
            comboFont.SelectedIndex = index;
            comboStyle.FamilyName = comboFont.Text;
            }
            }
        
            // Дополняем стили и меняем шрифт образца
            ShowNewFont();
            }
        }
    }

    Запускающий код приложения

    И наконец, последним разрабатываем код запускающего файла Program.cs для тестирования самопального диалога и сравнения его со стандартным диалогом

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        // Форма приложения
        class MyClass : Form
        {
        public MyClass()
        {
        this.Text = "Самодельный диалог шрифта";
        // Подстраиваем под ширину заголовка
        this.Width = this.Text.Length * this.Font.Height;
        this.ResizeRedraw = true;// Перерисовывать при изменении размеров
        this.ForeColor = Color.Red;// Писать красным
        
        this.Menu = new MainMenu();// Создать линейку для заголовков меню
        this.Menu.MenuItems.Add("Формат");// Добавить нулевой заголовок
        // Добавить в нулевой заголовок опции и общий обработчик
        this.Menu.MenuItems[0].MenuItems.Add
        ("Стандартный диалог...", OnStdFont);
        this.Menu.MenuItems[0].MenuItems.Add("Самодельный диалог...",
                    OnNewFont);
        
        Panel palelMenu = new Panel();// Создать панель для красоты
        palelMenu.BorderStyle = BorderStyle.Fixed3D;// Рельефная
        palelMenu.Dock = DockStyle.Top;// Причалить вверх
        palelMenu.Height = 4;// Минимально отображаемая высота
        palelMenu.Parent = this;// Привязать к форме
        }
        
        // Обработка стандартного диалога
        void OnStdFont(object sender, EventArgs args)
        {
        // Создаем окно
        FontDialog dlg = new FontDialog();
        // Настраиваем окно
        dlg.Font = this.Font;
        dlg.Color = this.ForeColor;
        dlg.ShowColor = true;
        
        // Отдаем окно для управления пользователю
        if (dlg.ShowDialog() == DialogResult.OK)
        {
        // Применяем выбор пользователя к форме
        this.Font = dlg.Font;
        this.ForeColor = dlg.Color;
        style = "";// Нечего присвоить из стандартного диалога
        // Обновляем вызывающую форму, при этом выводим параметры шрифта
        this.Invalidate();
        }
        }
        
        string style = null;// Поле для хранения значения стиля 
        // "Начертание:" для видимости в обработчиках
        // Обработка самодельного диалога
        void OnNewFont(object sender, EventArgs args)
        {
        // Создаем окно
        NewFontDialog dlg = new NewFontDialog();
        // Настраиваем окно
        dlg.Font = this.Font;
        dlg.Color = this.ForeColor;
        dlg.ShowColor = true;
        
        // Отдаем окно для управления пользователю
        if (dlg.ShowDialog() == DialogResult.OK)
        {
        // Применяем выбор пользователя к форме
        this.Font = dlg.Font;
        this.ForeColor = dlg.Color;
        style = dlg.StyleNameRus;
        // Обновляем вызывающую форму, при этом выводим параметры шрифта
        this.Invalidate();
        }
        }
        
        // Рисуем на форме параметры шрифта
        protected override void OnPaint(PaintEventArgs e)
        {
        Graphics gr = e.Graphics;
        // Формируем содержимое выводимого текста
        string message = "Текущие параметры шрифта\n" + 
        Environment.NewLine;
        message += "Шрифт: " + this.Font.Name + 
        Environment.NewLine;
        message += "Цвет: " + this.ForeColor.Name + 
        Environment.NewLine;
        // Добавим русское название стиля из нашего диалога
        if (style != null  style.Length > 0)
          message += "Начертание: " + style + 
        Environment.NewLine;
        message += "Размер: " + this.Font.SizeInPoints.ToString();
        // Добавляем дополнительный стиль, если есть
        bool flag = (this.Font.Style  FontStyle.Strikeout) != 0 ||
                    (this.Font.Style  FontStyle.Underline) != 0;
        if (flag)
          message += Environment.NewLine;
        if ((this.Font.Style  FontStyle.Strikeout) != 0)
                    message += "Зачеркнутый ";
        if ((this.Font.Style  FontStyle.Underline) != 0)
                    message += " Подчеркнутый";
        // Объект форматирования текстового блока
        StringFormat strFormat = new StringFormat();
        strFormat.LineAlignment = strFormat.Alignment =
                    StringAlignment.Center;
        // Рисуем текст новыми параметрами, 
        // установленными в обработчиках
        gr.DrawString(message, this.Font,
                    new SolidBrush(this.ForeColor),
                    this.ClientRectangle, strFormat);
        }
        }
        
        // Запуск
        class Program
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                // Создали форму и передали ее в цикл сообщений Windows
                Application.Run(new MyClass());
            }
        }
    }

    Вот результат работы кода

    Испытав работу нашего самодельного диалогового окна, мы можем убедиться, что оно ничем не отличается от библиотечного диалога. Для чего делали? А чтобы программистские мускулы нарастить!

    Вернуться к учебному плану