🎨 Графическое программирование на C#

← Вернуться к модулю 1

Урок 1.5: Расположение элементов управления

Модуль 1 ⏱️ ~1.5 часа

📌 Ключевые моменты этого урока:

  • Вспомните из урока 1.3: свойства формы определяют её внешний вид
  • Размещение элементов — ключ к красивому и удобному интерфейсу
  • Anchor привязывает элемент к сторонам формы (расстояние сохраняется)
  • Dock прилепляет элемент к краю и заполняет доступное пространство
  • Panel и GroupBox — контейнеры для группировки других элементов

Layout менеджеры — почему они нужны

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

Проблема без Layout менеджеров:

Начальный размер формы     Пользователь растягивает окно
┌─────────────────┐        ┌──────────────────────────┐
│  [Button1]      │        │  [Button1]               │
│  [Button2]      │        │  [Button2]               │
│                 │        │                          │
│  [TextBox1]     │        │  [TextBox1]              │
└─────────────────┘        └──────────────────────────┘

Элементы остаются на месте, но окно пусто справа →

Решение — правильное размещение элементов

Есть несколько подходов в Windows Forms:

  • Anchor — привязать расстояния элементов к краям формы
  • Dock — прилепить элемент к краю и заполнить доступное пространство
  • TableLayoutPanel — разместить как в таблице (строки и столбцы)
  • FlowLayoutPanel — разместить как текст (поток слева направо, сверху вниз)

Anchor (якорь) — сохранение расстояний

Anchor привязывает элемент к одной или нескольким сторонам формы. При изменении размера формы элемент сохраняет расстояние до привязанных сторон.

Как работает Anchor визуально:

Anchor = Top | Left (по умолчанию)
┌─────────────────┐   Растягиваем окно →   ┌──────────────────────┐
│[Button]         │                        │[Button]              │
│                 │                        │                      │
│                 │                        │                      │
└─────────────────┘                        └──────────────────────┘
Кнопка остается в левом верхнем углу

Anchor = Bottom | Right
┌─────────────────┐   Растягиваем →        ┌──────────────────────┐
│                 │                        │                      │
│                 │                        │              [Button]│
│         [Button]│                        │              [Button]│
└─────────────────┘                        └──────────────────────┘
Кнопка остается в правом нижнем углу

Anchor = All (Top|Bottom|Left|Right)
┌─────────────────┐   Растягиваем →        ┌──────────────────────┐
│[Button    ]     │                        │[Button          ]    │
│                 │                        │                      │
│                 │                        │                      │
└─────────────────┘                        └──────────────────────┘
Кнопка растягивается вместе с окном!

Примеры использования Anchor:

public Form1()
{
    InitializeComponent();
    
    // Кнопка OK в правом нижнем углу (классика диалогов)
    Button okBtn = new Button();
    okBtn.Text = "OK";
    okBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
    okBtn.Location = new Point(200, 150);
    this.Controls.Add(okBtn);
    
    // Текстовое поле заполняет всё пространство
    TextBox textBox = new TextBox();
    textBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | 
                     AnchorStyles.Left | AnchorStyles.Right;
    textBox.Multiline = true;
    textBox.Location = new Point(10, 10);
    textBox.Size = new Size(280, 130);
    this.Controls.Add(textBox);
    
    // Кнопка сверху слева (остается на месте)
    Button newBtn = new Button();
    newBtn.Text = "Новый";
    newBtn.Anchor = AnchorStyles.Top | AnchorStyles.Left; // Default
    newBtn.Location = new Point(10, 10);
    this.Controls.Add(newBtn);
}

Все возможные значения Anchor:

┌─────────────┬─────────────┐
│   Top       │             │
│   Left  │   │   Right     │
│             │             │
├─────────────┼─────────────┤ ← None,All пересекают оси
│   Bottom    │             │
└─────────────┴─────────────┘

Комбинации:
• AnchorStyles.Top | AnchorStyles.Left (углы и стороны)
• AnchorStyles.All (= Top|Bottom|Left|Right) — по всем сторонам
• AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right (сверху, растягивается слева-справа)

Dock (закрепление) — прилепление к краям

Dock более агрессивен чем Anchor. Элемент прилепляется к краю и заполняет всё доступное пространство в этом направлении. Dock часто используется для главных компонентов интерфейса (меню, статус-бар, области рисования).

Визуальный пример Dock:

Начально                   После Dock.Top
┌─────────────────────┐    ┌─────────────────────┐
│ Control (Dock.Top)  │    │ Control (заполнил всю ширину)│
│ ...остальное...     │    │────────────────────────────│
└─────────────────────┘    │ ...остальное... │
                           └─────────────────────┘

После Dock.Left                После Dock.Fill
┌────────────────┐            ┌─────────────────┐
│C │ ...остальное │            │ Control заполнит│
│o │   ...        │            │ всё всю форму! │
│n │              │            │                 │
│t │              │            │                 │
│r │              │            │                 │
│o │              │            │                 │
│l │              │            │                 │
└────────────────┘            └─────────────────┘

Примеры использования Dock:

public Form1()
{
    InitializeComponent();
    this.Size = new Size(800, 600);
    
    // Панель инструментов сверху
    ToolStrip toolStrip = new ToolStrip();
    toolStrip.Items.Add("Файл");
    toolStrip.Items.Add("Правка");
    toolStrip.Items.Add("Вид");
    toolStrip.Dock = DockStyle.Top;  // ← Заполняет ВСЮ ширину сверху
    this.Controls.Add(toolStrip);
    
    // Статус-бар снизу
    StatusStrip statusBar = new StatusStrip();
    statusBar.Items.Add("Готово");
    statusBar.Dock = DockStyle.Bottom; // ← Заполняет ВСЮ ширину снизу
    this.Controls.Add(statusBar);
    
    // Панель навигации слева
    Panel navPanel = new Panel();
    navPanel.BackColor = Color.LightGray;
    navPanel.Dock = DockStyle.Left;    // ← Заполняет ВСЮ высоту слева
    navPanel.Width = 200;              // Установить ширину
    this.Controls.Add(navPanel);
    
    // Область рисования в центре (заполняет оставшееся место)
    Panel drawingArea = new Panel();
    drawingArea.BackColor = Color.White;
    drawingArea.Dock = DockStyle.Fill; // ← Заполняет ВСЮ оставшуюся область
    drawingArea.Paint += (s, e) => DrawGraphics(e.Graphics);
    this.Controls.Add(drawingArea);
}

Порядок Dock имеет значение!

Когда несколько элементов имеют Dock, порядок их добавления к Controls influрует на результат:

// Правильный порядок (важно для кода выше):
// 1. toolStrip (Top) — займет верхнюю часть
// 2. statusBar (Bottom) — займет нижнюю часть  
// 3. navPanel (Left) — займет левую часть оставшегося пространства
// 4. drawingArea (Fill) — заполнит оставшееся место в центре

// НЕПРАВИЛЬНЫЙ порядок:
this.Controls.Add(drawingArea);    // ❌ Если добавить Fill первым
this.Controls.Add(navPanel);       // navPanel не будет видна!
this.Controls.Add(statusBar);
this.Controls.Add(toolStrip);

// Элементы обрабатываются в ОБРАТНОМ порядке для позиционирования

Panel — универсальный контейнер

Panel — это простой контейнер для группировки элементов управления. Он как пустая коробка, в которую вы можете поместить другие элементы.

Характеристики Panel:

  • Нет видимой границы — по умолчанию только фон видно
  • Можно добавлять элементы — новые элементы размещаются внутри Panel
  • Часто используется с Dock — для создания областей интерфейса
  • Можно скрывать/показывать целые группы — скрыть Panel = скрыть все элементы внутри

Пример создания рабочего интерфейса с Panel:

public Form1()
{
    InitializeComponent();
    
    // Основная панель для работы
    Panel mainPanel = new Panel();
    mainPanel.Dock = DockStyle.Fill;
    mainPanel.BackColor = Color.White;
    
    // Панель кнопок внутри (горизонтальные кнопки сверху)
    Panel buttonPanel = new Panel();
    buttonPanel.Dock = DockStyle.Top;
    buttonPanel.Height = 40;
    buttonPanel.BackColor = Color.LightGray;
    
    Button btnSave = new Button();
    btnSave.Text = "Сохранить";
    btnSave.Location = new Point(5, 5);
    buttonPanel.Controls.Add(btnSave);
    
    Button btnNew = new Button();
    btnNew.Text = "Новый";
    btnNew.Location = new Point(85, 5);
    buttonPanel.Controls.Add(btnNew);
    
    mainPanel.Controls.Add(buttonPanel);
    this.Controls.Add(mainPanel);
}

GroupBox — видимая группировка элементов

GroupBox похож на Panel, но имеет видимую рамку (border) и заголовок (Text). Идеален для логической группировки связанных элементов (например, "Параметры цвета", "Размер", "Трансформация").

Характеристики GroupBox:

  • Видимая рамка — пользователю понятно что элементы в группе
  • Заголовок (Text) — описывает что за группа
  • Визуальное разделение — для комплексных форм с несколькими параметрами

Создание GroupBox с элементами:

// Группа для параметров цвета
GroupBox colorGroup = new GroupBox();
colorGroup.Text = "Параметры цвета";      // Заголовок
colorGroup.Location = new Point(10, 10);
colorGroup.Size = new Size(250, 150);

// Внутри элементы для выбора цвета
Label rLabel = new Label();
rLabel.Text = "Красный:";
rLabel.Location = new Point(10, 25);
colorGroup.Controls.Add(rLabel);

TrackBar rTrack = new TrackBar();
rTrack.Location = new Point(70, 25);
rTrack.Size = new Size(170, 25);
rTrack.Minimum = 0;
rTrack.Maximum = 255;
colorGroup.Controls.Add(rTrack);

// Аналогично для Green и Blue...

this.Controls.Add(colorGroup);

// Вторая группа для размера
GroupBox sizeGroup = new GroupBox();
sizeGroup.Text = "Размер";
sizeGroup.Location = new Point(10, 170);
sizeGroup.Size = new Size(250, 100);
// Размещаем элементы для ширины/высоты...
this.Controls.Add(sizeGroup);

Практический пример: полный интерфейс для графического редактора

public Form1()
{
    InitializeComponent();
    this.Text = "Графический редактор";
    this.Size = new Size(1000, 700);
    
    // ГЛАВНАЯ СТРУКТУРА ИНТЕРФЕЙСА
    // ┌─────────────────────────────────┐
    // │[ Меню и инструменты (Dock.Top)]│
    // ├──────────┬──────────────────────┤
    // │Settings  │                      │
    // │(Dock.    │  Область рисования   │
    // │ Left)    │  (Dock.Fill)         │
    // ├──────────┤                      │
    // │          │                      │
    // ├─────────────────────────────────┤
    // │[ Статус-бар (Dock.Bottom)]     │
    // └─────────────────────────────────┘
    
    // 1. Инструменты сверху
    ToolStrip tools = new ToolStrip();
    tools.Items.Add("Линия");
    tools.Items.Add("Прямоугольник");
    tools.Items.Add("Эллипс");
    tools.Dock = DockStyle.Top;
    this.Controls.Add(tools);
    
    // 2. Статус-бар снизу
    StatusStrip status = new StatusStrip();
    status.Items.Add("Готово");
    status.Dock = DockStyle.Bottom;
    this.Controls.Add(status);
    
    // 3. Панель настроек слева
    Panel settingsPanel = new Panel();
    settingsPanel.Width = 200;
    settingsPanel.Dock = DockStyle.Left;
    settingsPanel.BackColor = SystemColors.ControlLight;
    
    // Группы настроек внутри падели
    GroupBox colorGroup = new GroupBox();
    colorGroup.Text = "Цвет";
    colorGroup.Dock = DockStyle.Top;
    colorGroup.Height = 100;
    // ... элементы для цвета ...
    settingsPanel.Controls.Add(colorGroup);
    
    this.Controls.Add(settingsPanel);
    
    // 4. Область рисования в центре (заполняет оставшееся)
    Panel drawingArea = new Panel();
    drawingArea.Dock = DockStyle.Fill;
    drawingArea.BackColor = Color.White;
    drawingArea.Paint += DrawingArea_Paint;
    this.Controls.Add(drawingArea);
}

private void DrawingArea_Paint(object sender, PaintEventArgs e)
{
    // Здесь рисуем
}

Best Practices для Layout:

  • Используйте Dock для фиксированных компонентов — меню, статус-бар, навигация
  • Используйте Anchor для относительного позиционирования — кнопки, которые должны остаться рядом с краем
  • Комбинируйте Panel для создания областей — большие блоки интерфейса
  • Используйте GroupBox для визуальной группировки — когда есть логичные группы параметров
  • Тестируйте при различных размерах окна — убедитесь что интерфейс масштабируется правильно

Практическое задание

Задание: Создание интерфейса с использованием Layout

  1. Создайте форму с панелью инструментов сверху (Dock = Top)
  2. Добавьте GroupBox слева с настройками (Dock = Left)
  3. Создайте область рисования в центре (Dock = Fill)
  4. Добавьте статус-бар снизу (Dock = Bottom)
  5. Проверьте, как интерфейс ведет себя при изменении размера окна

Проверка знаний

Ответьте на вопросы, чтобы проверить, насколько хорошо вы усвоили материал:

1. Что делает свойство Dock?
  • Привязывает элемент к сторонам формы
  • Закрепляет элемент на одной из сторон формы
  • Скрывает элемент
  • Изменяет размер элемента
2. Что делает свойство Anchor?
  • Привязывает элемент к сторонам формы, сохраняя расстояние
  • Закрепляет элемент на стороне
  • Изменяет позицию элемента
  • Скрывает элемент
3. В чем разница между Panel и GroupBox?
  • Нет разницы
  • Panel имеет заголовок
  • GroupBox имеет видимую рамку и заголовок
  • Panel нельзя использовать для группировки