Назад Содержание Вперед

Размещение элементов в окне

"Ручное" размещение

С помощью метода setGeometry(int x, int y, int w, int h) или setGeometry(const QRect&) можно задать положение и размер любого визуального элемента в пикселах. Для установки размеров без изменения положения может использоваться метод resize(int w, int h) или resize(const QSize&). Наоборот, для перемещения элемента в нужную позицию с сохранением прежних размеров служит метод move(int x, int y) или move(const QPoint&).

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

Очевидный (но не лучший) способ решить хотя бы некоторые из перечисленных проблем заключается в том, чтобы в виртуальном методе resizeEvent, автоматически выполняемом при изменении размеров окна, пересчитывать размеры и положение всех элементов. Пример программирования такого диалога приведён в листингах 8 и 9, а внешний вид окна -- на рис.


Рис. Перерасчёт геометрии в методе resizeEvent

Листинг 8. Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.h)

    1 // Перерасчёт геометрии всех элементов при изменении размеров окна
    2 
    3 #include <QtGui>
    4 
    5 class MyDialog : public QDialog {
    6     Q_OBJECT
    7 
    8 public:
    9     MyDialog();
   10 
   11 protected:
   12     virtual void resizeEvent(QResizeEvent *event);
   13 
   14 private:
   15     QLabel *lb;          // Текстовая метка.
   16     QLineEdit *le;       // Строковое поле ввода.
   17     QComboBox *cb;       // Поле ввода с раскрывающимся списком.
   18     QSpinBox *sb;        // Целочисленное поле ввода с кнопками
   19                          // инкремента/декремента.
   20     QCheckBox *chb;      // Независимый переключатель
   21                          // с двумя состояниями.
   22     QDoubleSpinBox *dsb; // Поле ввода вещественного значения
   23                          // с кнопками инкремента/декремента.
   24     QDateTimeEdit *dte;  // Поле ввода даты и времени.
   25     QGroupBox  *gb;      // Рамка с надписью вокруг группы элементов.
   26     QRadioButton *rb1;   // Три
   27     QRadioButton *rb2;   // зависимых
   28     QRadioButton *rb3;   // переключателя.
   29     QPushButton *btn1;   // Кнопка "Сохранить".
   30     QPushButton *btn2;   // Кнопка "Отменить".
   31 };
Листинг 9. Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.cpp)
    1 // Перерасчёт геометрии всех элементов при изменении размеров окна
    2 
    3 #include "03.h"
    4 
    5 MyDialog::MyDialog() {
    6 
    7     QTextCodec *codec = QTextCodec::codecForName("CP1251");
    8     QTextCodec::setCodecForTr(codec);
    9 
   10     lb = new QLabel(tr("Метка:"), this);
   11 
   12     le = new QLineEdit(tr("Строка"), this);
   13 
   14     cb = new QComboBox(this);
   15     cb->addItem(tr("Первый"));
   16     cb->addItem(tr("Второй"));
   17     cb->addItem(tr("Третий"));
   18     cb->setCurrentIndex(2);
   19     cb->setEditable(true);
   20     cb->setInsertPolicy(QComboBox::InsertAtBottom);
   21 
   22     sb = new QSpinBox(this);
   23     sb->setValue(5);
   24 
   25     chb = new QCheckBox(tr("Пометка"), this);
   26     chb->setCheckState(Qt::Checked);
   27 
   28     dsb = new QDoubleSpinBox(this);
   29     dsb->setMaximum(200.0);
   30     dsb->setDecimals(2);
   31     dsb->setSingleStep(0.25);
   32     dsb->setValue(100.25);
   33 
   34     dte = new QDateTimeEdit(
   35         QDateTime(QDate(2007, 2, 5), QTime(13, 35, 55, 10)),
   36         this);
   37 
   38     gb = new QGroupBox(tr("Выбрать одно из трёх:"), this);
   39 
   40     rb1 = new QRadioButton(tr("Один"), gb);
   41 
   42     rb2 = new QRadioButton(tr("Два"), gb);
   43 
   44     rb3 = new QRadioButton(tr("Три"), gb);
   45 
   46     rb2->setChecked(true);
   47 
   48     btn1 = new QPushButton(tr("Сохранить"), this);
   49 
   50     btn2 = new QPushButton(tr("Отменить"), this);
   51 
   52     setMinimumSize(160, 205);
   53 }
   54 
   55 void MyDialog::resizeEvent(QResizeEvent* /* event */ ) {
   56     int dw = width() - minimumWidth();
   57     int dh = height() - minimumHeight();
   58 
   59     lb->setGeometry(5, 6, 45, 24);
   60     le->setGeometry(55, 6, 100+dw, 24);
   61 
   62     cb->setGeometry(5, 36, 80+dw*2/3, 24);
   63     sb->setGeometry(95+dw*2/3, 36, 60+dw/3, 24);
   64 
   65     chb->setGeometry(5, 65, 70, 24);
   66     dsb->setGeometry(95, 65, 60+dw, 24);
   67 
   68     dte->setGeometry(5, 95, 150+dw, 24);
   69 
   70     gb->setGeometry(5, 120, 150+dw, 40);
   71     rb1->setGeometry(5, 15, 45+dw/3, 24);
   72     rb2->setGeometry(55+dw/3, 15, 45+dw/3, 24);
   73     rb3->setGeometry(105+dw*2/3, 15, 45+dw/3, 24);
   74 
   75     btn1->setGeometry(5+dw/4, 170+dh, 70+dw/4, 29);
   76     btn2->setGeometry(85+dw/2, 170+dh, 70+dw/4, 29);
   77 }
   78 
   79 int main(int argc, char *argv[]) {
   80 
   81     QApplication app(argc, argv);
   82 
   83     MyDialog *dlg = new MyDialog();
   84     dlg->show();
   85 
   86     return app.exec();
   87 }


Рис. Внешний вид окна и элементов управления в системе Windows при запуске программы с параметром
-style=Windows, WindowsXP, Motif, CDE, Plastique и Cleanlooks

На рис. показано, как изменяется внешний вид элементов диалога, если при запуске программы, текст которой приведён в листингах 8 и 9, указать в командной строке параметр -style=ИмяСтиля. Обратите внимание, что при одних и тех же размерах окна, устанавливаемых сразу после запуска программы, в режиме WindowsXP текстовая метка "Один" оказалась обрезанной на последней букве, а для стиля Motif высота всех полей ввода и ширина кнопок оказалась на грани критической: текстовые надписи еле "влазят" в отведённые для них границы из-за увеличенной толщины декоративных элементов. В системе Linux, где размер шрифта по умолчанию выбирается обычно больше, чем в Windows (из-за традиционно худшего качества отображения шрифтов), это различие компоновки элементов диалога для различных стилей ещё более заметно.

Менеджеры размещения

В Qt имеются классы QHBoxLayout, QVBoxLayout и QGridLayout, которые специально предназначены для управления положением и размерами элементов в окне. Первый позволяет располагать элементы друг за другом по горизонтали, второй -- по вертикали, а третий размещает виджеты в ячейках воображаемой таблицы, причём каждый элемент может занимать несколько смежных ячеек по вертикали и/или горизонтали.

В листинге 10 показаны строки, которые потребуется дописать к тексту конструктора диалога (см. листинг 9), если мы захотим использовать менеджеры размещения. Здесь все элементы располагаются в сетке QGridLayout, а для зависимых переключателей и кнопок создаются отдельные менеджеры QHBoxLayout. Разумеется, метод resizeEvent теперь не нужен.

Листинг 10. Менеджеры размещения (файл examples-qt/04/04.cpp)

          // Создаём менеджер размещения:
   52     QGridLayout *mainlay = new QGridLayout();
          
          // Размер полей вокруг сетки элементов и
          // интервалы между ячейками сетки:
   53     mainlay->setMargin(2);
   54     mainlay->setSpacing(3);
   55 
          // Размещаем элементы:
   56     mainlay->addWidget(lb, 0, 0);
   57     mainlay->addWidget(le, 0, 1, 1, 2);
   58 
   59     mainlay->addWidget(cb, 1, 0, 1, 2);
   60     mainlay->addWidget(sb, 1, 2);
   61 
   62     mainlay->addWidget(chb, 2, 0);
   63     mainlay->addWidget(dsb, 2, 1, 1, 2);
   64 
   65     mainlay->addWidget(dte, 3, 0, 1, 3);
   66 
          // Менеджер размещения для радиокнопок:
   67     QHBoxLayout *hbl = new QHBoxLayout();
   68     hbl->addWidget(rb1, 1);
   69     hbl->addWidget(rb2, 1);
   70     hbl->addWidget(rb3, 1);
   71     gb->setLayout(hbl);
   72     mainlay->addWidget(gb, 4, 0, 1, 3);
   73 
          // Менеджер размещения для кнопок диалога:
   74     QHBoxLayout *btns = new QHBoxLayout();
   75     btns->addStretch(1);
   76     btns->addWidget(btn1, 2);
   77     btns->addWidget(btn2, 2);
   78     btns->addStretch(1);
   79     mainlay->addLayout(btns, 6, 0, 1, 3);
   80 
          // Растяжимость колонок и строк:
   81     mainlay->setColumnStretch(1, 1);
   82     mainlay->setColumnStretch(2, 1);
   83     mainlay->setRowStretch(5, 1);
    
   84     setLayout(mainlay);
   85 } 

Для вставки любого виджета в нужную ячейку QGridLayout предназначен метод addWidget, при вызове которого указываются номер строки и столбца (нумерация начинается с нуля), а также, если требуется, количество соседних строк и столбцов, которые будет занимать элемент. Растяжимостью строк и столбцов сетки управляют методы setRowStretch и setColumnStretch, первый параметр которых указывает номер строки (столбца), а второй -- коэффициент растяжения. Чем он больше, тем сильнее будет растягиваться/сжиматься данная строка по вертикали (или столбец по горизонтали) по сравнению с остальными строками (столбцами) при изменении размеров всего окна.

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

Если нас не удовлетворяет то, как ведут себя какие-либо элементы диалога при изменении размеров окна, то можно попытаться вызвать для них метод setSizePolicy, первый параметр которого задаёт политику изменения ширины элемента, а второй -- его высоты. Каждый параметр может принимать одно из значений:

Назад Содержание Вперед