Глава 6. Управление размещением виджетов.

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

6.1. Основы компоновки виджетов.

Qt предоставляет три основных способа управления размещением подчиненных виджетов на форме: абсолютное позиционирование, ручное управление размещением и менеджеры компоновки. Мы рассмотрим каждый из них, на примере диалога "Find File", показанный на рисунке 6.1.

Рисунок 6.1. Диалог "Find File".


Абсолютное позиционирование -- это самый "неблагодарный" способ размещения виджетов. При таком подходе положение и размеры виджетов жестко зашиваются в программу, что, как правило, влечет за собой фиксированные размеры самой формы. Взглянем на конструктор диалога FindFileDialog, который строится по принципу абсолютного позиционирования: FindFileDialog::FindFileDialog(QWidget *parent, const char *name) : QDialog(parent, name) { ... namedLabel->setGeometry(10, 10, 50, 20); namedLineEdit->setGeometry(70, 10, 200, 20); lookInLabel->setGeometry(10, 35, 50, 20); lookInLineEdit->setGeometry(70, 35, 200, 20); subfoldersCheckBox->setGeometry(10, 60, 260, 20); listView->setGeometry(10, 85, 260, 100); messageLabel->setGeometry(10, 190, 260, 20); findButton->setGeometry(275, 10, 80, 25); stopButton->setGeometry(275, 40, 80, 25); closeButton->setGeometry(275, 70, 80, 25); helpButton->setGeometry(275, 185, 80, 25); setFixedSize(365, 220); } Абсолютное позиционирование имеет массу недостатков. Самый главный недостаток -- невозможность изменить размеры окна. Другой недостаток: текст меток может не умещаться в заданные размеры, если пользователь выбрал большой размер шрифта или, если интерфейс приложения был переведен на другой язык. Кроме того, этот подход требует от нас выполнения кропотливой работы по вычислению положения и размеров виджетов.

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

FindFileDialog::FindFileDialog(QWidget *parent, const char *name) : QDialog(parent, name) { ... setMinimumSize(215, 170); resize(365, 220); } void FindFileDialog::resizeEvent(QResizeEvent *) { int extraWidth = width() - minimumWidth(); int extraHeight = height() - minimumHeight(); namedLabel->setGeometry(10, 10, 50, 20); namedLineEdit->setGeometry(70, 10, 50 + extraWidth, 20); lookInLabel->setGeometry(10, 35, 50, 20); lookInLineEdit->setGeometry(70, 35, 50 + extraWidth, 20); subfoldersCheckBox->setGeometry(10, 60, 110 + extraWidth, 20); listView->setGeometry(10, 85, 110 + extraWidth, 50 + extraHeight); messageLabel->setGeometry(10, 140 + extraHeight, 110 + extraWidth, 20); findButton->setGeometry(125 + extraWidth, 10, 80, 25); stopButton->setGeometry(125 + extraWidth, 40, 80, 25); closeButton->setGeometry(125 + extraWidth, 70, 80, 25); helpButton->setGeometry(125 + extraWidth, 135 + extraHeight, 80, 25); } В конструкторе мы установили минимальные размеры формы 215 X 170 и начальный размер 365 X 220. В обработчике resizeEvent() устанавливаются новые размеры виджетов при изменении размеров окна.

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

Рисунок 6.2. Диалог "Find File" с изменяемыми размерами.


Наилучшим решением размещения виджетов на форме считается использование менеджеров компоновки Qt. Они обеспечивают разумные размеры по-умолчанию для каждого типа виджетов и учитывают "идеальные" размеры каждого из них, которые, в свою очередь, зависят от выбранного размера шрифта, стиля отображения и объема содержимого. Кроме того, менеджеры компоновки учитываю минимальные и максимальные размеры, и автоматически корректируют расположение виджетов, в ответ на изменение шрифта, содержимого или размеров окна.

В Qt имеется три вида менеджеров компоновки: QHBoxLayout, QVBoxLayout и QGridLayout. Это классы-потомки от QLayout, который реализует основные методы управления размещением. Все три класса полностью поддерживаются Qt Designer-ом, а так же могут использоваться при написании кода вручную. Оба варианта использования были рассмотрены в Главе 2.

Ниже приводится конструктор FindFileDialog, в котором используются менеджеры размещения:

FindFileDialog::FindFileDialog(QWidget *parent, const char *name) : QDialog(parent, name) { ... QGridLayout *leftLayout = new QGridLayout; leftLayout->addWidget(namedLabel, 0, 0); leftLayout->addWidget(namedLineEdit, 0, 1); leftLayout->addWidget(lookInLabel, 1, 0); leftLayout->addWidget(lookInLineEdit, 1, 1); leftLayout->addMultiCellWidget(subfoldersCheckBox, 2, 2, 0, 1); leftLayout->addMultiCellWidget(listView, 3, 3, 0, 1); leftLayout->addMultiCellWidget(messageLabel, 4, 4, 0, 1); QVBoxLayout *rightLayout = new QVBoxLayout; rightLayout->addWidget(findButton); rightLayout->addWidget(stopButton); rightLayout->addWidget(closeButton); rightLayout->addStretch(1); rightLayout->addWidget(helpButton); QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setMargin(11); mainLayout->setSpacing(6); mainLayout->addLayout(leftLayout); mainLayout->addLayout(rightLayout); } Размещением компонентов на форме управляют один QHBoxLayout, один QGridLayout и один QVBoxLayout. QGridLayout и QVBoxLayout расположены рядом друг с дружкой, внутри QHBoxLayout. Рамка вокруг формы имеет ширину 11 пикселей, промежутки между подчиненными виджетами -- 6 пикселей.

Рисунок 6.3. Раскладка диалога "Find File".


QGridLayout работает как плоская сетка ячеек. QLabel, в верхнем левом углу области, занимает ячейку (0, 0), а соответствующий ей QLineEdit -- (0, 1). QCheckBox объединяет две колонки и занимает ячейки (2, 0) и (2, 1). QListView и QLabel, расположенные снизу, так же занимают по две ячейки. Вызов addMultiCellWidget() имеет следующий синтаксис: leftLayout->addMultiCellWidget(widget, row1, row2, col1, col2); где widget -- это подчиненный виджет, передаваемый этому менеджеру компоновки, row1, col1 -- верхняя левая ячейка, которую занимает виджет и row2, col2 -- правая нижняя ячейка.

Тот же самый диалог может быть создан с помощью визуального построителя Qt Designer. Пример работы с визуальным построителем, мы рассматривали в Главе 2.

Использование менеджеров размещения дает определенные преимущества, которые мы уже обсуждали ранее. Если в область компоновки добавляется виджет или удаляется из нее, менеджер автоматически адаптируется под изменившиеся условия. То же самое применимо и к случаю, когда вызываются методы подчиненного компонента -- hide() и show(). Если подчиненный виджет изменит "идеальный" размер, то раскладка изменится, с учетом изменившихся обстоятельств. Кроме того, менеджеры размещения автоматически установят минимальный размер формы в целом, основываясь на минимальных и "идеальных" размерах дочерних виджетов.

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

Политика изменения размеров сообщает менеджеру компоновки, как виджет должен растягиваться или сжиматься. Qt по-умолчанию дает неплохие значения политики изменения размеров для всех стандартных виджетов, но никакое значение по-умолчанию не может идеально подходить под все случаи жизни. Поэтому, до сих пор обычной практикой считается дополнительная настройка политик изменения размеров для одного-двух виджетов на форме. Политика изменения размеров назначается для каждого из двух направлений (по вертикали и по горизонтали). Наиболее часто используются значения Fixed, Minimum, Maximum, Preferred и Expanding:

Рисунок 6.4 подытоживает все, что было сказано выше о политиках изменения размеров, на примере QLabel, отображающей текст "Some Text".

Рисунок 6.4. Различные политики изменения размеров.


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

Существует еще две политики изменения размеров: MinimumExpanding и Ignored. Первая из них использовалась в ранних версиях Qt, хотя и довольно редко, в настоящее время не играет большой роли, поскольку лучший результат дает назначение политики Expanding и повторная реализация (перекрытие) метода minimumSizeHint(). Вторая -- во многом похожа на Expanding, но при этом игнорирует "идеальные" размеры виджета.

В дополнение к политикам изменения размера, горизонтальная и вертикальная составляющие визуального компонента, QSizePolicy хранят факторы растяжения. Они используются для задания степени растяжимости. Например, предположим, что на форме находятся QListView, а под ним -- QTextEdit. Нам необходимо, чтобы при растягивании формы QTextEdit рос в два раза быстрее, чем QListView. Для этого, фактор растягивания по вертикали (verticalStretch) компонента QTextEdit устанавливаем равным 2, а QListView -- 1.

Еще один способ воздействовать на порядок расположения -- изменять минимальный и максимальный размеры подчиненных виджетов. Менеджер компоновки будет учитывать значения этих параметров.