Программирование на C++ с использованием библиотеки Qt4

         

Перерасчёт геометрии в методе 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 };

(5) Объявили класс MyDialog, в качестве базового использовали стандартный класс диалога библиотеки Qt. (9) Конструктор диалога. (15-30) Указатели на все визуальные элементы необходимо сделать членами класса, чтобы иметь к ним доступ из метода resizeEvent.



Перерасчёт геометрии в методе 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 }


(7-8) Кодек и установка кодировки. (10) Текстовая метка. Второй параметр конструктора (указатель на родительский элемент) можно опустить. (12) Однострочное поле ввода. То же самое замечание относительно параметра this. (14-20) Поле ввода с раскрывающимся списком. Текущим сделан второй, считая с нуля, te последний элемент списка. Вызов(19) разрешает вводить в поле любые строковые значения, независимо от того, имеются ли они в списке выбора. В (20) указано, что новые значения, введённые пользователем, будут автоматически дописываться в конец списка. (22-23) Целочисленное поле ввода с кнопками инкремента/декремента. (25-26) Независимый переключатель с двумя состояниями. (28-32)Поле для ввода вещественного значения с кнопками инкремента/декремета. В (29) установлено максимальное допустимое значение, в (30) -- количество десятичных цифр в дробной части, а в (31) -- величина приращения при нажатии кнопки. (34-36) Поле для ввода даты и времени. Заданы год, месяц, число, часы, минуты, секунды и миллисекунды. (38) Рамка с надписью вокруг группы элементов. (40,42,44) Три зависимых переключателя (радиокнопки) с текстовыми метками.

(46) Установлен второй переключатель, остальные автоматически выключены. (48,50) Две кнопки. (52) Заданы минимально возможные размеры окна диалога. Если вместо setMinimumSize использовать метод setFixedSize, то пользователь не сможет изменить ни ширину, ни высоту окна. (55) Виртуальный метод, автоматически вызываемый при изменении размеров окна. (56,57) Вычисление приращения ширины и высоты окна относительно их минимально разрешённых значений. (59) Положение и размеры текстовой метки фиксированы. (60) Положение однострочного поля ввода не изменяется, а ширина увеличивается на то же значение, что и ширина всего окна. В результате правый край элемента зафиксирован относительно правой границы окна. (62-63) Ширина поля ввода со списком выбора увеличивается в два раза быстрее, чем ширина поля для ввода целого значения. (65) Позиция и размеры независимого переключателя фиксированы. (66, 68, 70) Ширина полей для ввода вещественного значения и для ввода даты/времени, а также ширина рамки вокруг группы элементов увеличивается на то же значение, что и ширина всего окна. (71-73) Ширина текстовых меток зависимых переключателей растёт одинаково, на 1/3 от прироста ширины всего окна. (75-76) Кнопки в нижней части диалога увеличиваются по ширине, а расстояние от них до нижней границы окна остаётся неизменным.




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

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


Менеджеры размещения (файл 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, первый параметр которого задаёт политику изменения ширины элемента, а второй -- его высоты. Каждый параметр может принимать одно из значений:

QSizePolicy::Fixed -- размер элемента в данном направлении не изменяется; QSizePolicy::Minimum -- "идеальным" размером элемента считается его минимальный размер. Элемент может растягиваться, но не может сжиматься; QSizePolicy::Maximum -- "идеальным" размером элемента считается его максимальный размер. Элемент может сжиматься, но не может растягиваться; QSizePolicy::Preferred -- элемент "старается" поддерживать некоторый предпочтительный для него размер, но при необходимости может растянуться или сжаться; QSizePolicy::Expanding -- элемент "старается" принять максимально возможный доступный ему размер, но при необходимости может и сжиматься.



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


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



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



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


С помощью метода 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