Qt:Документация 4.3.2/layout

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
40px Внимание: Актуальная версия перевода документации находится здесь

__NOTOC__

Image:qt-logo.png

Главная · Все классы · Основные классы · Классы по группам · Модули · Функции

Image:trolltech-logo.png

Содержание

Классы компоновки

Система компоновки Qt предоставляет простой и мощный способ компоновки дочерних виджетов.

Однажды задав логическое расположение, вы получаете следующие преимущества:

  • Позиционирование дочерних виджетов.
  • По умолчанию устанавливаются разумные размеры окон.
  • Разумные минимальные размеры окон.
  • Обработка изменения размеров.
  • Автоматическое обновление при смене следующих параметров содержимого:
    • Размера шрифта, текста или другого содержимого дочерних виджетов.
    • Сокрытие и отображение дочерних виджетов.
    • Удаление дочерних виджетов.


Неудобство ручного кодирования компоновки заключается в том, что это неудобно при экспериментах в проектом формы: вы должны пройти сборку, связь и запуск при каждом изменении. Наше решение - это Qt Designer: визуальный инструмент разработки GUI, делающий экспериментирование с расположением быстрым и лёгким и генерирующий для Вас код C++.

Классы компоновки Qt были разработаны для ручного написания кода C++, поэтому они легки и понятны в использовании. Код, сгенерированный для форм, созданных с помощью Qt Designer, также использует классы компоновки.

Разделы:

Горизонтальный, вертикальный компоновщики и компоновщик с сеткой

Самый легкий способ задания правильного расположения виджетов состоит в использовании встроенных менеджеров компоновки: QHBoxLayout, QVBoxLayout и QGridLayout. Эти классы являются наследниками QLayout, который, в свою очередь, происходит от QObject (а не от QWidget). Они берут на себя заботы об управлении геометрией и расположении виджетов. Для создания более сложных компоновок, вы можете помещать менеджеры компоновок друг в друга.

  • QHBoxLayout располагает виджеты в горизонтальную линию с направлением размещения слева-направо (или справа-налево для языков с написанием справа-налево).

center

  • QVBoxLayout располагает виджеты в вертикальную линия с направление сверху-вниз.

center

  • QGridLayout располагает виджеты в двумерной сетке. Виджеты могут занимать несколько ячеек.

center

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

        QWidget *window = new QWidget;
        QPushButton *button1 = new QPushButton("One");
        QPushButton *button2 = new QPushButton("Two");
        QPushButton *button3 = new QPushButton("Three");
        QPushButton *button4 = new QPushButton("Four");
        QPushButton *button5 = new QPushButton("Five");
 
        QHBoxLayout *layout = new QHBoxLayout;
        layout->addWidget(button1);
        layout->addWidget(button2);
        layout->addWidget(button3);
        layout->addWidget(button4);
        layout->addWidget(button5);
 
        window->setLayout(layout);
        window->show();

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

        QWidget *window = new QWidget;
        QPushButton *button1 = new QPushButton("One");
        QPushButton *button2 = new QPushButton("Two");
        QPushButton *button3 = new QPushButton("Three");
        QPushButton *button4 = new QPushButton("Four");
        QPushButton *button5 = new QPushButton("Five");
 
        QGridLayout *layout = new QGridLayout;
        layout->addWidget(button1, 0, 0);
        layout->addWidget(button2, 0, 1);
        layout->addWidget(button3, 1, 0, 1, 2);
        layout->addWidget(button4, 2, 0);
        layout->addWidget(button5, 2, 1);
 
        window->setLayout(layout);
        window->show();

Третий QPushButton растягивается на 2 колонки. Это задается с помощью передачи 2 в качестве четвёртого аргумента в QGridLayout::addWidget().

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

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

Вы можете вставлять компоновщики в другой компоновщик с помощью addLayout(); в этом случае внутренний компоновщик становится дочерним по отношению к внешнему. В примере Basic Layouts используется данную особенность для создания сложного диалога.

Добавление виджетов в компоновщик

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

  1. Все виджеты изначально размещаются на пространстве, соответствующем их QWidget::sizePolicy().
  2. Если какие либо из виджетов имеют значение фактора растяжения, большее, чем ноль, то такие виджеты занимают свободное место в соответствии с их факторами растяжения (объяснение ниже).
  3. Если какие либо из виджетов имеют значение фактора растяжения, равное нулю, то эти виджеты получают дополнительное место только в том случае, если на дополнительное место не претендуют другие виджеты. Дополнительное место сперва отдаётся тем из этих виджетов, у которых политика размера содержит Expanding.
  4. Любые виджеты, которые занимают меньше места, чем того требует их минимальный размер (или минимального места при отсутствии заданного минимального размера) располагаются на минимальном требуемом пространстве. (Виджеты не имеют минимального размера или предпочтения минимального размере, если задан их фактор растяжения.)
  5. Любые виджеты, которые занимают больше места, чем их максимальный размер, размещаются на пространстве, требуемом их максимальным размером. (Виджеты не имеют максимального размера или предпочтения максимального размера, если задан их фактор растяжения.)

Факторы растяжения

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

Если имеются три виджета, размещаемые с помощью QHBoxLayout без установленных факторов растяжения, по получается размещение подобное следующему:

center

При применении факторов растяжения к каждому виджету, они (виджеты) будут размещены пропорционально (но никогда не станут меньше своего минимального размера), например:

center

Пользовательские виджеты в компоновщиках

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

  • Повторная реализация QWidget::sizeHint(), возвращающей предпочитаемый размер виджета.
  • Повторная реализация QWidget::minimumSizeHint(), возвращающей минимальный размер, который может принимать виджет.
  • Вызов QWidget::setSizePolicy() для задания требования к расположению виджета.

Вызывайте QWidget::updateGeometry() всякий раз, когда изменяются минимальный размер, предпочитаемый минимальный размер или политика размера. Это вызовет перерасчет компоновки. Многократные вызовы QWidget::updateGeometry() приведут только к одному перерасчету.

Если предпочитаемая высота Вашего виджета зависит от его текущей ширины (например, в метке с автоматическим переносом слов), установите флаг высота-по-ширине в политике размера виджета и заново реализуйте QWidget::heightForWidth().

Даже если вы реализовали QWidget::heightForWidth(), будет неплохо определить также и разумный sizeHint().

Для получения дальнейших инструкций по реализации данных функций см. Установка Высоты в Зависимости от Ширины в Ежеквартальнике Qt.

Проблемы компоновки

Использование виджета метки с форматированным текстом может вызвать некоторые проблемы при компоновке его родительского виджета. Проблема возникает из-за способа обработки форматированного текста менеджерами компоновки Qt, если метка переносит текст по словам.

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

Собственный компоновщик

Если вы создаете один из видов специального компоновщика, вы также можете создать один из пользовательских виджетов, как это описано выше. Заново реализуйте QWidget::resizeEvent() для расчета требуемых размеров и вызова setGeometry() для каждого дочернего объекта.

Когда схема размещения элементов должна быть повторно расчитана, виджет получит сообщение типа QEvent::LayoutHint. Заново реализуйте QWidget::event() чтобы обработать сообщения QEvent::LayoutHint.

Написание собственного менеджера компоновки

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

Здесь мы представим подробный пример. Класс CardLayout является подобием менеджера компоновки Java с подобным названием. Он размещает элементы (виджеты или вложенные компоновщики) поверх друг друга, каждый элемент сдвигается на QLayout::spacing().

При написании Вашего класса компоновки, вы должен определить следующее:

  • Структура данных для хранения элементов, обрабатываемых компоновщиком. Каждый элемент является QLayoutItem. В этом примере мы будем использовать QList.
  • addItem() определяет, как добавляются записи в компоновщик.
  • setGeometry() определяет расположение компоновщика.
  • sizeHint() предпочитаемый размер компоновщика.
  • itemAt() определяет, как перебираются элементы компоновщика.
  • takeAt() определяет, как удаляются элементы из компоновщика.

В большинстве случаев, вы также должны реализовать minimumSize().

Файл заголовка (card.h)

    #ifndef CARD_H
    #define CARD_H
 
    #include <QLayout>
    #include <QList>
 
    class CardLayout : public QLayout
    {
    public:
        CardLayout(QWidget *parent, int dist)
            : QLayout(parent, 0, dist) {}
        CardLayout(QLayout *parent, int dist)
            : QLayout(parent, dist) {}
        CardLayout(int dist)
            : QLayout(dist) {}
        ~CardLayout();
 
        void addItem(QLayoutItem *item);
        QSize sizeHint() const;
        QSize minimumSize() const;
        QLayoutItem *itemAt(int) const;
        QLayoutItem *takeAt(int);
        void setGeometry(const QRect &amp;rect);
 
    private:
        QList<QLayoutItem*> list;
    };
    #endif

Файл реализации (card.cpp)

    #include "card.h"

Сперва мы реализуем две функции, позволяющие перебирать элементы компоновщика и удалять их: itemAt() и takeAt(). Эти функции используются внутренней системой компоновщика для удаления виджетов. Они также доступны прикладным программистам.

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

    QLayoutItem *CardLayout::itemAt(int idx) const
    {
        // QList::value() выполняет проверку индекса и возвращает 0, если он находится
        // вне диапазона значений
        return list.value(idx);
    }
 
    QLayoutItem *CardLayout::takeAt(int idx)
    {
        // QList::take не выполняет проверку индекса
        return idx >= 0 &amp;&amp; idx < list.size() ? list.takeAt(idx) : 0;
    }

addItem() реализует стратегию размещения элементов компоновщика по умолчанию. Она должна быть реализована. Она используется QLayout::add(), конструктором QLayout принимающим компоновщик в качестве родителя. Если Ваш компоновщик имеет продвинутый способ размещения, требующий параметров, вы должны предоставить дополнительные функции доступа типа рядов и колонок, охватывающие перегрузку QGridLayout::addItem(), addWidget() и addLayout().

    void CardLayout::addItem(QLayoutItem *item)
    {
        list.append(item);
    }

Компоновщик принимает ответственность за добавление элементов. С тех пор, как QLayoutItem не наследует QObject, мы должны удалять записи вручную. Функция QLayout::deleteAllItems() использует takeAt(), определенную выше, для удаления всех элементов в компоновщике.

    CardLayout::~CardLayout()
    {
        deleteAllItems();
    }

Функция setGeometry() фактически выполняет расположение компоновщика. Прямоугольник, переданный как аргумент, на включает margin(). Если нужно, используйте spacing() в качестве расстояния между элементами.

    void CardLayout::setGeometry(const QRect &amp;r)
    {
        QLayout::setGeometry(r);
 
        if (list.size() == 0)
            return;
 
        int w = r.width() - (list.count() - 1) * spacing();
        int h = r.height() - (list.count() - 1) * spacing();
        int i = 0;
        while (i < list.size()) {
            QLayoutItem *o = list.at(i);
            QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
            o->setGeometry(geom);
            ++i;
        }
    }

sizeHint() и minimumSize() обычно имеют очень похожие реализации. Размеры, возвращаемые обеими функциями, должны включать spacing(), но не должны включать margin().

    QSize CardLayout::sizeHint() const
    {
        QSize s(0,0);
        int n = list.count();
        if (n > 0)
            s = QSize(100,70); //начинаем с удобного размера по умолчанию
        int i = 0;
        while (i < n) {
            QLayoutItem *o = list.at(i);
            s = s.expandedTo(o->sizeHint());
            ++i;
        }
        return s + n*QSize(spacing(), spacing());
    }
 
    QSize CardLayout::minimumSize() const
    {
        QSize s(0,0);
        int n = list.count();
        int i = 0;
        while (i < n) {
            QLayoutItem *o = list.at(i);
            s = s.expandedTo(o->minimumSize());
            ++i;
        }
        return s + n*QSize(spacing(), spacing());
    }

Последние замечания

Данный компоновщик не обрабатывает зависимость высоты от ширины.

Мы игнорируем QLayoutItem::isEmpty(), поэтому компоновщик будет обращаться со скрытыми виджетами как с видимыми.

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

Вызовы QLayoutItem::sizeHint() и т.п. могут быть достаточно дорогими, поэтому лучше сохранить возвращенные ими значения в локальных переменных, если вы используете их снова в этой функции.

Вы не должны дважды вызывать QLayoutItem::setGeometry() для одного и того-же элемента в одной функции. Это может быть очень дорого в случае, если элемент имеет несколько дочерних виджетов, так как он должен каждый раз пересчитать компоновку полностью. Вместо этого рассчитайте геометрию, а потом установите ее. (Так поступают не только применительно к компоновщику, вы должны сделать то-же самое, если реализуете собственную resizeEvent().)


Copyright © 2007 Trolltech Trademarks
Qt 4.3.2