Урок №8. Управление компоновкой виджетов в Qt5

  Дмитрий Бушуев  | 

  |

  Обновл. 16 Сен 2021  | 

 41383

 ǀ   10 

На этом уроке мы поговорим об управлении компоновкой виджетов в Qt5.

Системы компоновки в Qt5

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

   QHBoxLayout;

   QVBoxLayout;

   QFormLayout;

   QGridLayout.

На предыдущих уроках мы узнали, что типичное Qt-приложение состоит из различных виджетов, позиционированием которых занимается программист, пишущий программу. Библиотека Qt поддерживает два способа размещения элементов управления на форме приложения:

   абсолютное позиционирование;

   позиционирование с помощью менеджеров компоновки.

Абсолютное позиционирование


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

   размер и положение виджета не изменяются при изменении размера окна;

   приложения выглядят по-разному (часто плохо) на разных платформах;

   изменение шрифтов в приложении может испортить компоновку;

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

Несомненно, есть примеры, в которых нам никто не запрещает использовать способ абсолютного позиционирования элементов. Но в большинстве реальных проектов программисты стараются вместо этого использовать менеджеры компоновки.

Перейдем к рассмотрению примера, в котором задействован метод setGeometry() для размещения виджета в окне с использованием абсолютных координат.

Файл реализации — absolute.cpp:

Здесь мы создаем виджет QTextEdit и вручную размещаем его. Метод setGeometry() выполняет две функции: позиционирует виджет в абсолютных координатах и изменяет его размер.

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


Класс QVBoxLayout

Класс QVBoxLayout предназначен для создания вертикального ряда из выравниваемых объектов. Добавление виджетов в компоновку осуществляется с помощью метода addWidget().

Заголовочный файл — verticalbox.h:

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

Файл реализации — verticalbox.cpp:

Создаем объект класса QVBoxLayout и устанавливаем интервал между дочерними виджетами в 1 пиксель:

Далее мы создаем кнопку и устанавливаем для нее политику размера QSizePolicy::Expanding. Дочерние виджеты управляются менеджером компоновки. По умолчанию кнопка растягивается по горизонтали и имеет фиксированный размер по вертикали. Если мы хотим изменить его, то нужно и для вертикали установить новую политику размера (QSizePolicy::Expanding). Как видите, в нашем случае кнопка будет расширяться в обоих направлениях:

Добавляем дочерние виджеты в менеджер компоновки при помощи метода addWidget():

Сообщаем нашей программе, что необходимо использовать QVBoxLayout в качестве менеджера компоновки:

Основной файл приложения — main.cpp:

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


Добавление кнопок


В следующем примере мы создадим две кнопки в правом нижнем углу клиентской области окна.

Заголовочный файл — buttons.h:

Файл реализации — buttons.cpp:

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

Создаем две кнопки:

Данные кнопки с помощью метода addWidget() располагаются внутри горизонтального менеджера компоновки. При этом они выравниваются по правому краю. Метод addWidget() имеет три параметра:

   Параметр №1: Дочерний виджет.

   Параметр №2: Коэффициент (фактор) растяжения.

   Параметр №3: Выравнивание.

Установив коэффициент растяжения равным 1 для кнопки OK, виджет не будет растягиваться на все выделенное ему пространство, тем самым остается свободное место от левой до правой стороны окна. Наконец, константа Qt::AlignRight выравнивает виджет по правому краю:

При помощи метода addStretch() вставляем между виджетами промежуток динамического размера. Затем добавляем менеджер горизонтальной компоновки внутрь менеджера вертикальной компоновки:

Основной файл приложения — main.cpp:

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


Вложенные компоновки

Идея следующего примера состоит в том, чтобы показать, что менеджеры компоновки могут быть вложены друг в друга. Комбинируя даже простые способы размещения элементов на форме, мы можем создавать сложные формы и окна. Для того, чтобы вложить одну компоновку в другую, используется метод addLayout().

В следующем примере мы создадим окно, состоящее из 4 кнопок и 1 виджета QListWidget. Кнопки сгруппированы в вертикальный столбец и расположены справа от виджета-списка. Если мы изменим размер окна, размеры QListWidget также изменятся.

Заголовочный файл — nesting.h:

Файл реализации — nesting.cpp:

QVBoxLayout будет столбцом для кнопок:

QHBoxLayout будет базовой компоновкой для виджетов:

Создаем QListWidget и наполняем его данными:

А теперь создаем наши кнопки:

Создается вертикальная компоновка с четырьмя кнопками. Мы вставили немного свободного места между нашими кнопками. Обратите внимание, что мы добавляем коэффициент растяжения к верхней и нижней части вертикальной компоновки. Таким образом, кнопки расположены вертикально по центру:

Виджет списка и вертикальный блок кнопок помещаются в блок горизонтальной компоновки. Метод addLayout() используется для вложения одной компоновки в другую:

Устанавливаем базовую компоновку для родительского окна:

Основной файл приложения — main.cpp:

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


Класс QFormLayout


Класс QFormLayout является вспомогательным классом компоновки, который размещает свои дочерние элементы в двух столбцах. Левый столбец содержит метки, а правый столбец содержит виджеты ввода (однострочные редакторы, счетчики и т.д.).

В следующем примере мы создадим форму, которая будет состоять из 3 меток и 3 полей для ввода.

Заголовочный файл — form.h:

Файл реализации — form.cpp:

Создаем экземпляр класса QFormLayout:

С помощью метода setLabelAlignment() устанавливаем выравнивание виджетов-меток:

Метод addRow() добавляет новую строку в нижнюю часть компоновки формы с заданной меткой и виджетом ввода:

Основной файл приложения — main.cpp:

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


Класс QGridLayout

Класс QGridLayout размещает виджеты в сетке. Механизм работы QGridLayout следующий: он занимает отведенное ему место (отведенное родительским компоновщиком или parentWidget()), разбивает его на строки и столбцы и помещает каждый подконтрольный виджет в соответствующую ячейку.

Заголовочный файл — calculator.h:

Файл реализации — calculator.cpp:

Мы создаем компоновку по сетке и устанавливаем 2 пикселя пространства между дочерними виджетами:

А вот символы, которые отображаются на кнопках:

Далее помещаем шестнадцать виджетов в компоновку. Все кнопки имеют фиксированный размер:

Основной файл приложения — main.cpp:

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


Еще один пример

Теперь постараемся создать более сложное окно с помощью менеджера QGridLayout. В следующем примере мы создадим приложение, в котором можно будет написать отзыв о литературных произведениях авторов.

Заголовочный файл — review.h:

Файл реализации — review.cpp:

Создаем менеджер QGridLayout:

Добавляем вертикальный интервал с помощью метода setVerticalSpacing() и горизонтальный интервал с помощью метода setHorizontalSpacing():

Эти строки кода создают виджет-метку и помещают её в компоновку. Метод addWidget() имеет пять параметров:

   первый параметр — это дочерний виджет, метка в нашем случае;

   следующие два параметра — это строка и столбец в сетке компоновки, куда мы помещаем наш виджет;

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

Код:

Метод setAlignment() задает выравнивание метки-заголовка в своей ячейке. По горизонтали — выравнивание по правому краю, по вертикали — выравнивание по центру:

Виджет QTextEdit помещается в третью строку и второй столбец, он охватывает три строки и один столбец:

Основной файл приложения — main.cpp:

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


Заключение

Мы разобрали несколько примеров с использованием различных менеджеров компоновки. Теперь вы можете самостоятельно попробовать изменить параметры компоновок и понаблюдать за результатом. Посмотрите, как будут перестраиваться виджеты в форме при изменении размеров формы. Вспомните, как задать минимальный размер формы, и задайте его так, чтобы не допускать искажения виджетов. Попробуйте создать собственные проекты с использованием рассмотренных менеджеров компоновки. Используя данную статью, расширяйте свое знакомство с виджетами, самостоятельно устанавливая их в ячейки компоновщика.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (59 оценок, среднее: 4,86 из 5)
Загрузка...

Комментариев: 10

  1. Сергей Федоров:

    Может быть кому-нибудь будет полезно…
    В калькуляторе решил добавить обработчики нажатия кнопок. Ниже в коментариях нашел как получить доступ к каждому элементу списка кнопок:

    В результате получилась громоздкая конструкция из коннектов

    и куча почти одинаковых слотов нажатия кнопок.
    Чтобы избавиться от дублирования кода надо как-то подключить к разным сигналам один слот и передать в слот аргумент — номер нажатой клавиши, например просто элемент массива QList<QString> values = { «7», «8»,….
    Дочитал уроки по Qt до конца но решения так и не нашел. Гугление и изучение заняло довольно много времени…

    Решением оказалось простым — применение лямбда-функции. Описание тут: https://wiki.qt.io/New_Signal_Slot_Syntax
    В цикле создания кнопок добавилась одна строчка:

    И единственный метод в слоте private slots:

  2. Антон:

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

    1. Как работает int stretch когда мы добавляем новый виджет при помощи addWidget(QWidget*, int stretch);
    Я только понял, что принимать он может значения от 0 до 1 и в первом случае виджет "растягивается" (но как-то непредсказуемо), а во втором случае — нет.
    И как работает это всё работает вместе с, ну допустим, hbox->setStretch(1)?

    2. Как работают флаги выравнивания Qt::AlignCenter, Qt::AlignLeft и прочие.
    Допустим, есть у нас QLabel *lbl. B чём разница между этими командами:

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

  3. Георгий:

    Здравствуйте! Скажите, пожалуйста, зачем указатели на некоторые виджеты мы сохраняем в переменных — членах класса (например, в buttons.h и buttonts.cpp)?

    1. Антон:

      Я, конечно, не волшебник, а только учусь, но в заголовочном файле *.h обычно создаются те указатели, с которыми потом будут взаимодействовать слоты.

      Простой пример:
      Есть три указателя:
      QLineEdit — для ввода текста
      QLabel — изначально пустую
      QPushButton — которая по нажатию будет присваивать виджету QLabel значение, введённое в поле QLineEdit.

      Если объект QPushButton можно со спокойной душой объявить в конструкторе класса (который в файле с исходниками *.cpp) то указатели QLineEdit и QLabel придётся объявить в заголовочном файле. Если же объявить их в конструкторе класса, то и область их видимости будет ограничена конструктором. Слот для обработки события &QPushButton::clicked их просто не увидит, а значит не сможет вытянуть значение из QLineEdit и присвоить его QLabel.

      Но, лучше всего, попробуйте сами и отпишитесь. В отличие от курса по С++ курс по Qt не так детально разжевывается и многие моменты остаются непонятыми даже после чтения doc.qt.io. Возможно, есть какой-то способ запихнуть все указатели в конструктор, чтобы при этом всё работало. Но у меня ничего не получилось.

  4. Никита Маслов:

    В строчке "QVBoxLayout *vbox = new QVBoxLayout();" в конструктор не передаётся указатель this. Из-за этого, насколько я понял, появлятся возможность утечки памяти. Мне кажется, стоит везде в конструктор передавать this, а потом использовать setLayout, чтобы назначить нужную компановку.

  5. SafinGleb:

    В калькуляторе все кнопки будут иметь указатель btn ?
    Как потом их различать ,чтобы использовать ?

    1. Фото аватара Дмитрий Бушуев:

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

      Если же брать непосредственно функционал кнопок, то здесь есть один нюанс. Если я не ошибаюсь, переменная-указатель QPushButton *btn из двойного цикла for() будет создаваться/уничтожаться всякий раз, когда начинается/заканчивается итерация внешнего цикла for(). Но при этом память, выделяемая под QPushButton(values[pos], this), не освобождается. Если бы речь шла только о С++ без Qt, то мы бы получили стандартную утечку памяти (память выделили, а переменную-указатель — уничтожили, доступа к выделенной памяти больше нет).

      Но! В Qt есть такая штука, как "Иерархии объектов", суть которой вот в чём: каждый объект может иметь "родительские" и "дочерние" объекты, образуя тем самым иерархические структуры по типу дерева. Подобное отношение между объектами организуется (при создании) через последний параметр конструктора, представляющий собой указатель на объект-родитель.

      Обратите внимание на второй параметр, который мы передаём в конструктор кнопки при её создании:

      Параметр this — это указатель на объект-родитель (в нашем случае, это объект window класса Calculator), с которым мы связываем создаваемую кнопку. Т.е. каждый раз создавая в двойном цикле for() очередную кнопку, мы автоматически привязываем её к объекту window.

      Чтобы получить доступ к списку дочерних объектов, применяется функция findChildren<тип_дочернего_объекта>(), которая вернет список типа QList<тип_дочернего_объекта> дочерних объектов. На практике это можно реализовать следующим образом:

      (или, если находимся внутри объекта window, QList<QPushButton*> plist=this->findChildren<QPushButton*>(); )

      А дальше работать с plist как с обычным массивом:

      При этом стоит иметь в виду, что элементы в списке plist расположены в том порядке, в котором вновь создаваемые нами кнопки привязывались к своему объекту-родителю.
      Как-то так…

      1. SafinGleb:

        спасибо, получилось

  6. Jane:

    Почему в разделе "Добавление кнопок" нет в конце файла setLayout(vbox), как в остальных разделах? Было попробовано убрать setLayout() в других примерах — это не повлияло на внешний вид приложения! В чем секрет?

    1. Фото аватара Дмитрий Бушуев:

      Секрет в этой строчке:

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

      Попробуйте вместо:

      …использовать…

      P.S.: Спасибо за замечание, исправим 🙂

Добавить комментарий для Антон Отменить ответ

Ваш E-mail не будет опубликован. Обязательные поля помечены *