Архив за месяц: Март 2014

Qt для начинающих. Урок 2. Менеджеры компоновки. Знакомство

Вернуться к общему содержанию "Qt для начинающих".

Способы размещения виджетов

В эволюции идей размещения элементов управления на оконных формах можно выделить три подхода.

  1. Координатное размещение. Это самый простой и самый неудачный способ размещения элементов в форме. Простота его заключается в том, что он совершенно понятен любому человеку без какой-либо подготовки. С каждым элементом управления можно связать прямоугольную область в которой он размещается. Более того, большинство элементов имеют прямоугольные границы, поэтому можно не усложнять систему определений. Размещая один элемент поверх другого, координатным способом, надо лишь указать, например, координаты верхнего левого угла размещения элемента и его протяженность — ширину и высоту. В другом варинте, вместо ширины и высоты можно указать координаты нижнего правого угла размещения. К недостаткам такого способа размещения объектов следует отнести проблемы возникающие при изменении стилей элементов управления и отсутствие возможностей автоматической адаптации размеров элементов к изменению размеров формы.
  2. Установка привязки границ (установка якорей). Фактически, этот способ размещения элементов является улучшенным способом координатного размещения. Основное отличие заключается в возможности установки так называемых якорей, которые определяют жесткую привязку границы одного элемента к границе другого элемента. Это позволяет успешно закладывать возможности изменения размеров некоторого вида форм. Например, привязывая нижнюю границу окна редактора к нижней границе формы, растягивая форму вниз, мы растягиваем окно редактора за счет привязки его нижней границы. Способ не является сложным для понимания, однако, не решает вопросы изменения размеров любых видов форм.
  3. Использование менеджеров компоновки. Менеджерами компоновки называют специального вида объекты, которые действуют как резиновая система ячеек. Объект менеджера компоновки "натягивается" на некоторый базовый виджет (например, на окно формы), а в ячейки компоновщика укладываются виджеты, которые следует разместить на базовом виджете.
  4. В зависимости от правил работы компоновщика и от политики изменения размеров виджетов, при изменении размеров базового виджета, производится автоматический пересчет размеров виджетов, уложенных в ячейки компоновщика. Существуют множество разных моделей компоновщиков в каждой из библиотек, где они используются. В ячейки компоновщиков можно вкладывать не только виджеты, но и другие компоновщики, в результате чего можно придумать сложные растягиваемые структуры. Кроме создания сложных компоновочных структур, можно управлять политиками растяжения виджетов, которые укладываются в ячейки компоновщиков. Таким образом, открываются очень широкие возможности для создания растягиваемых форм разной сложности и поведения. К недостаткам такого способа размещения объектов можно отнести лишь некоторую сложность использования компоновщиков, особенно на начальном этапе знакомства с ними. К некоторой радости можно заметить, что система менеджеров компоновки используемая в библиотеке Qt много проще, чем система менеджеров компоновки используемая в библиотеке Java SWING, при том, что возможности, за исключением некоторых идей и деталей, практически одинаковы.

Библиотека Qt поддерживает два способа размещения элементов управления. Координатное, пример которого был показан в конце прошлого урока, и размещение с ипользованием менеджеров компоновки.

Примеры использования менеджеров компоновки

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

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

  1. Цепи владения компоновщиков реализованы так, что владеть компоновщиком может только компоновщик.
  2. Каждый элемент, который наследуется от класса QWidget знает о классах компоновки и умеет их использовать. Для указания компоновщика, виджеты имеют специальный метод setLayout(). Таким образом, чтобы привязать компоновщик к виджету, нужно создать объект компоновщика, а потом передать его в метод виджета setLayout().
  3. Особое внимание требует конструктор компоновщиков с сигнатурой (QWidget * parent=0). По внешнему виду он похож на конструкторы других виджетов выполняющих роли создания цепей владения. В случае компоновщиков это не совсем так (см. пункт 1). С одной стороны, виджет действительно владеет цепями компоновщиков которые он использует и автоматически уничтожает их при своем удалении. Однако, в данном случае, параметр parent используется не для того, чтобы компоновщик подключился к цепи владения объекта на который указывает parent, а для того, чтобы выполнить parent->setLayout(), т.е. установить себя компоновщиком указанного виджета.
  4. Когда один менеджер компоновки (первичный) включает в свою ячейку другой менеджер компоновки (вторичный), то вторичный компоновщик создается без указания parent, так как у виджета может быть только один менеджер компоновки. Владение вторичным компоновщиком создается автоматически при добавлении вторичного компоновщика в ячейку первичного компоновщика. Таким образом, первичный компоновщик будет владеть вторичным и будет ответственным за его удаление. См. метод компоновщиков addLayout().
  5. В ячейки менеджеров компоновки можно установить либо виджет, либо вторичный менеджер компоновки. Для этого существуют методы addWidget() и addLayout() соответственно. При добавлении вторичного компоновщика, как уже говорилось, он включается в цепь владения первичного компоновщика. При добавлении виджета, он включается в цепь владения виджета, которому принадлежит цепь компоновщиков только в том случае, если для него не был ранее задан владелец. Такое поведение не характерно для старых версий Qt, поэтому еще существуют традиции, которые явно задают цепи владения для виджетов, которые устанавливаются в ячейки менеджеров компоновщики.

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

Первый вариант (явное использование setLayout())

    QGridLayout * grid = new QGridLayout();
    setLayout(grid);

Второй вариант (неявное использование setLayout())

    QGridLayout * grid = new QGridLayout(this);

Если объект менеджера выравнивания создается без передачи в его конструктор значения parent (т.е. будет использоваться значение по-умолчанию — 0), то это означает либо то, что объект менеджера компоновки будет связан с виджетом владельцем позже (через явный вызов setLayout()), либо это означает, что данный объект менеджера компоновки является вторичным и будет установлен в какую-нибудь ячейку другого менеджера.

Все примеры мы будем строить одинаковым образом, на основе определения приватного метода createFormInterior(). Традиционно, мы будем использовать данный метод для создания интерьера форм. Объявление метода делается в заголовочном файле формы следующим образом. Приведем измененное состояние файла widget.h.

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Widget : public QWidget
{
    Q_OBJECT    
public:
    Widget(QWidget *parent = 0);
    ~Widget();
    
private:
    // Объявим метод в приватной области, для личного использования.
    void createFormInterior();
};

#endif // WIDGET_H

Вызов метода создания интерьера формы должен производиться в конструкторе класса формы. Приведем измененное состояние файла widget.cpp с заготовкой метода createFormInterior().

#include "widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    // Зададим какой-нибудь заголовок для окна формы
    setWindowTitle(tr("Something title"));
    
    // Вызов приватного метода создания интерьера
    createFormInterior();
}

Widget::~Widget()
{
    
}

void Widget::createFormInterior()
{
   // здесь будут реализованы примеры создания интерьера формы   
}

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

Пример работы с менеджером компоновки QGridLayout

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

Сеточная модель компоновки редко используется для в качестве основного менеджера выравнивания формы. Причина этого станет понятна при получении некоторого опыта работы с компоновщиками. Однако он идеально подходит для компоновки некоторых участков формы, например, вертикальных списков из пар QLabel — QLineEdit.

Перейдем к рассмотрению примера. В начало файла widget.c вставим следующие строки для включения заголовочный файлов на требуемые классы Qt.

#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include <QPushButton>

Далее добавим реализацию метода создания интерьера, createFormInterior().

01. void Widget::createFormInterior()
02. {
03.     QGridLayout * grid = new QGridLayout(this);
04. 
05.     {
06.         QLabel * plb = new QLabel(tr("Some Text"), this);
07.         grid->addWidget(plb, 0,0);
08.     }
09. 
10.     {
11.         QLineEdit * ple = new QLineEdit(this);
12.         grid->addWidget(ple, 0,1);
13.     }
14. 
15.     {
16.         QLineEdit * ple = new QLineEdit(this);
17.         grid->addWidget(ple, 1,0, 1,2);
18.     }
19. 
20.     {
21.         QPushButton * ppb = new QPushButton(tr("Up"), this);
22.         grid->addWidget(ppb, 2,0);
23.     }
24. 
25.     {
26.         QTextEdit * pte = new QTextEdit(this);
27.         grid->addWidget(pte, 2,1, 3,1);
28.     }
29. 
30.     {
31.         QPushButton * ppb = new QPushButton(tr("Down"), this);
32.         grid->addWidget(ppb, 4,0);
33.     }
34. }

В строке 03 создается объект менеджер компоновки класса QGridLayout. Менеджер компоновки неявным образом (через параметр конструктора) устанавливается в текущий виджет.

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

  1. void QGridLayout::addWidget ( QWidget * widget, int row, int column, Qt::Alignment alignment = 0 )
  2. void QGridLayout::addWidget ( QWidget * widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = 0 )

Поясним смысл параметров, передаваемых в эти методы.

  • QWidget * widget — указатель на виджет, который устанавливается в ячейку менеджера компоновки.
  • int row — номер ряда, в который устанавливается виджет. Нумерация рядов начинается с нуля.
  • int column — номер столбца, в который устанавливается виджет. Нумерация столбцов начинается с нуля.
  • Qt::Alignment alignment = 0 ) — способ выравнивания виджета в ячейке. Параметр имеет значение по-умолчанию и может не указываться явно.
  • int fromRow — номер ряда, в который устанавливается верхняя левая часть виджета. Используется для случая, когда виджет необходимо разместить на несколько смежных ячеек.
  • int fromColumn — номер столбца, в который устанавливается верхняя левая часть виджета. Используется для случая, когда виджет необходимо разместить на несколько смежных ячеек.
  • int rowSpan — количество рядов, ячейки которых следует объединить для размещения виджета начиная с ряда fromRow.
  • int columnSpan — количество столбцов, ячейки которых следует объединить для размещения виджета начиная со столбца fromColumn.

Код по созданию и включению каждого из виджетов в менеджер компоновки заключен в отдельные операторные скобки { … }. Это позволяет максимально сужать область видимости имен создаваемых объектов. При достижении завершающей скобки, созданные в скобках переменные-указатели будут уничтожены и не будут засорять пространство имен дальнейшего кода метода. Во-первых, это позволяет многократно использовать простые имена для создания объектов. Во-вторых — избавляет нас от возможных ошибок неправильного использования имен, например, при некорректном повторении похожих фрагментов кода. Другими словами, такой подход максимально соответствует правилу о том, что объект должен быть описан и использован только там, где он используется. Язык C++ позволяет легко следовать этому правилу. На скорость работы результирующего кода такая локализация объектов не повлияет, а читабельность и надежность кода возрастет.

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