Виджеты (Widgets) — это визуальные элементы, из которых состоит графический интерфейс пользователя.
Примеры виджетов:
QPushButton);QLabel);QLineEdit);QSpinBox);QScrollBar).В Qt есть около 50-ти готовых классов графических элементов доступных для использования. Родительским классом для всех виджетов является класс QWidget. От него наследуются все главные свойства визуальных элементов, которые мы тщательно рассмотрим. Исследование способов разработки программ с графическим интерфейсом начнём с примера.
Создадим пустой файл проекта. Запустим мастера проектов и выберем в разделе Projects (Проекты) пункт Other Project (Другой проект). Далее выберем тип проекта Empty Qt Project (Пустой проект Qt). К файлу проекта добавим содержимое:
TEMPLATE = app #Модули Qt, которые мы будем использовать QT += widgets #Добавляем модуль widgets для работы с виджетами (необходимо для Qt5). TARGET = widget#Название исполняемого файла SOURCES += \ main.cpp
Теперь создадим простую программу с окном, в котором мы будем выводить надпись. Установим размер окна и текст его заголовка, а также установим шрифт для надписи. Для этого создадим файл main.cpp со следующим содержанием:
#include <QApplication>
#include <QLabel>
int main ( int lArgc, char * lArgv [ ] )
{
//Создаём объект QApplication, который инициализирует и настраивает оконную программу,
//управляет её выполнением с помощью цикла обработки событий
QApplication lApplication ( lArgc, lArgv );
QLabel lLabel; //Создаём виджет QLabel — метка
lLabel.setText ( " I am widget! " ); //Задаём текст для метки
lLabel.setGeometry ( 200, 200, 300, 150 );
//Задаём размеры — позицию (x, y) ширину и высоту. Задаём выравнивание текста
lLabel.setAlignment (Qt::AlignHCenter | Qt::AlignVCenter );
//Класс QFont используют для настройки параметров шрифта.
//Выбираем семейство шрифтов Arial Black и размер 12.
QFont lBlackFont ( " Arial Black ", 12 );
lLabel.setFont ( lBlackFont ); //Задаём шрифт для метки
lLabel.show ( ); //Вызываем метод show() для показа метки на экране.
return lApplication.exec ( ); //Запускаем программу на выполнение exec() выполняет
//цикл обработки событий. Программа ожидает действия пользователя и выполняет их обработку.
}
Как видим, элементы, из которых состоят интерфейсы в Qt, имеют собственные позицию и размер — так называемую "геометрию" — и, таким образом, занимают соответствующую прямоугольный участок на экране (см. рис. 13.1). Также каждый из элементов имеет настройки, которые определяют его поведение и вид.
(рис 13.1) Первый оконный проект
Для создания структуры виджеты организовывают в иерархию по принципу "часть — целое". Каждый из виджетов может содержать другие виджеты. Такой визуальный элемент становится "родителем" (родительским виджетом) для элементов, которые он содержит. Отметим, что такие отношения не следует путать с наследованием в C++ — отношениями между классами в программе. Отношения между виджетами являются отношениями между объектами. Такие отношения порождают несколько последствий:
Виджеты, которые не имеют родителя (виджеты верхнего уровня), имеют вид отдельных окон в программе. Рассмотрим пример. Назовём новый проект ParentExample. Файл проекта будет содержать обычные для GUI-проекта настройки:
TEMPLATE = app TARGET = ParentExample QT += widgets
Для виджета, который мы будем использовать в качестве главного окна создадим новый класс. Для этого в категории Files and Classes (Файлы и классы) выберем раздел С++ и выберем С++ Class (см. рис. 13.2).
(рис 13.2) Мастер создания нового класса
Следующим шагом будет создание нескольких элементов на окне. Для этого откроем файл parentwidget.cpp и изменим код конструктора класса. Для отображения элементов достаточно создать их в конструкторе класса и задать ParentWidget как отца для них. Код parentwidget.cpp выглядит так:
#include " parentwidget.h "
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
ParentWidget::ParentWidget ( QWidget * parent ) :
QWidget ( parent )
{
//Создаём метку, указывая родительский виджет — this, то есть экземпляр класса ParentWidget.
QLabel * lLabel=new QLabel ( this );
//Позиция относительно левого верхнего угла родительского виджета.
lLabel ->setGeometry ( 50, 0, 100, 30 );
lLabel ->setText ( " TextLabel " ); //Текст на метке.
//Создаём кнопку, задаём "родителя", геометрию и текст
QPushButton * lPushButton = new QPushButton ( this );
lPushButton->setGeometry ( 50, 50, 100, 30 );
lPushButton->setText ( " PushButton " );
//Создаём поле ввода, задаём "родителя", геометрию и текст
QLineEdit * lLineEdit = new QLineEdit ( this );
lLineEdit ->setGeometry ( 50, 100, 100, 30 );
lLineEdit ->setText ( " LineEdit " );
lLineEdit ->selectAll ( ); //Выделяем текст в поле ввода (просто для примера)
//Наконец изменяем размер родительского виджета
setGeometry ( x ( ), y ( ), 300, 150 );
//и задаём текст заголовка окна
setWindowTitle ( " parent widgetExample " );
}
Поскольку родительским элементом является ParentWidget, то метка (QLabel), кнопка (QPushButton) и текстовое поле (QLineEdit) находятся в его пределах. Позицию дочерних виджетов задают относительно левого верхнего угла отца. В этом легко убедиться изменив размеры и позицию окна нашей программы. Обратите внимание на то, как мы создавали элементы пользовательского интерфейса в динамической памяти используя оператор new. Это гарантирует, что элементы не будут удалены после завершения работы конструктора ParentWidget.
Далее добавим в проект файл main.cpp. Наш класс наследует от класса QWidget — базового класса для всех визуальных элементов пользовательского интерфейса, а следовательно будет обладать всеми его особенностями. Создадим экземпляр нашего класса и вызовем метод show() для того, чтобы показать его (см. рис. 13.3).
#include <QApplication>
//Подключаем .h файл с определением нашего класса ParentWidget
#include " parentwidget.h "
int main ( int lArgc, char * lArgv [ ] )
{
QApplication lApplication ( lArgc, lArgv );
//Создаём и показываем окно программы
ParentWidget lParentWidget;
//Переместить окно в точку экрана с координатами 100, 200
lParentWidget.move ( 100, 200 );
lParentWidget.show ( );
return lApplication.exec ( );
}
(рис 13.3) Пример создания дочерних виджетов
Для того, чтобы оптимально разместить виджеты на форме, необходимо учесть ряд деталей:
Понятно, что такая логика является довольно сложной для реализации при создании пользовательских интерфейсов разной структуры. К счастью в арсенале Qt есть довольно мощный инструмент для упорядочивания виджетов на форме — компоновщик.
Компоновщик позволяет упорядочить размещение компонент, оставляя интерфейс гибким для внесения изменений, таких как изменение размера либо количества элементов на форме. Компоновщик также может обеспечивать адекватное изменение размера самого виджета в ответ на перемены в его наполнении.
Компоновщик не принадлежит к виджетам, не наследует от QWidget и является невидимым на форме. Он только управляет её размером и размещением её содержания.
Обычно используют три основных класса компоновщика:
QVBoxLayout);QHBoxLayout);QGridLayout);Для того, чтобы продемонстрировать работу с компоновками, используем предыдущий пример добавив ещё несколько элементов и сделав так, чтобы размещение элементов было пропорциональным и соответствовало изменениям размеров окна. Попытаемся создать такой интерфейс:
Создадим проект LayoutExample. Для этого воспользуемся мастером новых проектов. Выберем в списке Qt Widgets Application. Зададим название для проекта и настроим класс главного окна: зададим родительский класс (в выпадающем списке Base Class установим QWidget) и снимем флажок для автоматической генерации файла формы (Generate Form) (см. рис. 13.4).
(рис 13.4) Создание класса окна
Откроем файл mainwindow.cpp и изменим код конструктора окна:
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
MainWindow::MainWindow ( QWidget * parent ) : QWidget ( parent )
{
//Первая горизонтальная строка. Начальный текст в поле ввода
QLineEdit * lLineEdit = new QLineEdit ( " Text1 " );
//Задаём текст. — означает комбинацию клавиш для активации виджета
QLabel * lLabel = new QLabel ( " LineEdit 1 " );
//Задаём виджет на который будет переключаться фокус ввода при нажатии Alt+1
lLabel ->setBuddy ( lLineEdit );
//Размещаем поле ввода и метку в одной строке
QHBoxLayout *lHBoxLayout = new QHBoxLayout;
lHBoxLayout->addWidget ( lLabel );
lHBoxLayout->addWidget ( lLineEdit );
//Вторая горизонтальная строка
QLineEdit * lLineEdit 2 = new QLineEdit ( " Text2 " );
QLabel * lLabel 2 = new QLabel ( " LineEdit 2 " );
lLabel 2 ->setBuddy ( lLineEdit 2 );
QHBoxLayout *lHBoxLayout2 = new QHBoxLayout;
lHBoxLayout2->addWidget ( lLabel 2 );
lHBoxLayout2->addWidget ( lLineEdit 2 );
//Третий ряд виджетов с кнопками
QPushButton *lPushButtonOk = new QPushButton ( " ok " );
QPushButton * lPushButtonCancel=new QPushButton ( " Cancel " );
QHBoxLayout *lHBoxLayout3 = new QHBoxLayout;
//Добавим элемент-растяжку он займёт всё возможное свободное пространство
//и "прижмёт"кнопки к краю
lHBoxLayout3->addStretch ( );
lHBoxLayout3->addWidget ( lPushButtonOk );
lHBoxLayout3->addWidget ( lPushButtonCancel );
//Добавим компоновку вертикально в колонку
QVBoxLayout *lVBoxLayout = new QVBoxLayout;
lVBoxLayout->addLayout ( lHBoxLayout );
lVBoxLayout->addLayout ( lHBoxLayout2 );
lVBoxLayout->addLayout ( lHBoxLayout3 );
//Задаём компоновщик для окна
setLayout ( lVBoxLayout );
}
После запуска программы получим главное окно с размещёнными в компоновщиках виджетами (см. рис. 13.5).
(рис 13.5) Пример: компоновка виджетов
Рассмотрим работу с компоновщиками на ещё одном примере. На этот раз мы создадим простую программу-калькулятор и используем компоновщики QGridLayout.
Создадим новый проект так же, как для предыдущего примера. Добавим члены класса — указатели на цифровые кнопки (0-9), кнопку для сброса результата (С) и для суммирования\отображения результата (+/ =). Для создания визуальных элементов в окне — объявим отдельный частный метод createWidgets().
#include <QWidget>
//Предварительное объявление классов
class QPushButton;
class QLCDNumber;
class CalculatorMainWindow : public QWidget
{
Q_OBJECT
public :
explicit CalculatorMainWindow ( QWidget * parent = 0 );
˜CalculatorMainWindow ( );
private :
//Отдельный метод для создания интерфейса программы
void createwidgets ( );
private :
//Цифровые кнопки
QPushButton *pushButton;
QPushButton *pushButton_2;
QPushButton *pushButton_3;
QPushButton *pushButton_4;
QPushButton *pushButton_5;
QPushButton *pushButton_6;
QPushButton *pushButton_7;
QPushButton *pushButton_8;
QPushButton *pushButton_9;
QPushButton *pushButton_10;
//Кнопка вывода результата и суммирования
QPushButton *pushButtonP lus;
//Кнопка сброса результата
QPushButton *pushButtonC;
//Виджет — цифровой дисплей
QLCDNumber *lcdNumber;
};
В файл реализации включим необходимые .h-файлы:
#include <QPushButton> #include <QGridLayout> #include <QLCDNumber>
Определим метод createWidgets(), ответственный за создание интерфейса программы-калькулятора. В нём создадим элемент QLCDNumber для отображения следующего слагаемого и результата. Для упрощения примера запрограммируем только операцию сложения целых чисел. Поэтому калькулятор будет содержать только цифровые кнопки (0-9), кнопку добавления/вывода результата и кнопку сброса результата. Все элементы пользовательского интерфейса разместим в одной компоновке QGridLayout:
void CalculatorMainWindow::createwidgets ( )
{
QGridLayout *lCalcLayout = new QGridLayout;
setLayout ( lCalcLayout );
lcdNumber = new QLCDNumber;
pushButton = new QPushButton ( " 1 " );
pushButton_2 = new QPushButton ( " 2 " );
pushButton_3 = new QPushButton ( " 3 " );
pushButton_4 = new QPushButton ( " 4 " );
pushButton_5 = new QPushButton ( " 5 " );
pushButton_6 = new QPushButton ( " 6 " );
pushButton_7 = new QPushButton ( " 7 " );
pushButton_8 = new QPushButton ( " 8 " );
pushButton_9 = new QPushButton ( " 9 " );
pushButton_10 = new QPushButton ( " 0 " );
pushButtonC = new QPushButton ( " C " );
pushButtonP lus = new QPushButton ( " + / = " );
lCalcLayout->addWidget ( lcdNumber, 0, 0, 1, 4 );
lCalcLayout->addWidget ( pushButton, 1, 0 );
lCalcLayout->addWidget ( pushButton_2, 1, 1 );
lCalcLayout->addWidget ( pushButton_3, 1, 2 );
lCalcLayout->addWidget ( pushButton_4, 2, 0 );
lCalcLayout->addWidget ( pushButton_5, 2, 1 );
lCalcLayout->addWidget ( pushButton_6, 2, 2 );
lCalcLayout->addWidget ( pushButton_7, 3, 0 );
lCalcLayout->addWidget ( pushButton_8, 3, 1 );
lCalcLayout->addWidget ( pushButton_9, 3, 2 );
lCalcLayout->addWidget ( pushButton_10, 4, 0, 1, 3 );
lCalcLayout->addWidget ( pushButtonC, 1, 3 );
lCalcLayout->addWidget ( pushButtonP lus, 2, 3, 3, 1 );
}
Метод addWidget() класса QGridLayout принимает в качестве параметров указатель на виджет, а также строку и столбец для размещения виджета. Количество строк и столбцов задают заранее, оно может быть произвольным и определяется в процессе добавления виджетов к компоновщику. Перегруженный вариант метода addWidget() используем для добавления кнопок "0", "+/ =" и виджета QLCDNumber на форму. Он принимает два дополнительных параметра. Они определяют количество ячеек по горизонтали и по вертикали, которые будет занимать виджет в компоновщике. Обычно каждый элемент занимает одну ячейку в компоновщике. Последний параметр метода addWidget() указывает выравнивание элемента внутри ячейки (флажки Qt::Alligment).
В заключение добавим к конструктору вызов метода для создания элементов в окне.
CalculatorMainWindow::CalculatorMainWindow ( QWidget * parent ) : QWidget ( parent )
{
resize ( 300, 300 );
setWindowTitle ( " Simple Calculator " );
createwidgets ( );
}
В процессе размещения визуальные элементы и компоновки "договариваются" о размерах и пропорциях в окне. Компоновщики придают размещению структурированный вид: будут ли элементы размещены в ряд (вертикально или горизонтально) либо в сетке. В свою очередь каждый из виджетов предоставляет собственные политики размера: какое пространство будет занимать каждый визуальный элемент в ширину и высоту, минимальный и максимальный размер для каждого из них.
Политики размера задают вызовом метода setSizePolicy() класса QWidget. Метод принимает значение для горизонтальной и вертикальной политики изменения размера. Метод sizeHint() возвращает оптимальный размер (класс QSize), который был определён для виджета. Ниже приведены возможные значения настройки политик размера:
QSizePolicy::Fixed — sizeHint определяет размеры элемента. Размеры элемента фиксированы;QSizePolicy::Minimum — sizeHint определяет минимально возможные размеры;QSizePolicy::Maximum — sizeHint определяет максимально возможные размеры;QSizePolicy::Preferred — sizeHint определяет рекомендованные размеры для элемента;QSizePolicy::Expanding — так же как Preferred с тенденцией к увеличению размера;QSizePolicy::MinimumExpanding — так же как Minimum с тенденцией к увеличению размера;QSizePolicy::Ignored — sizeHint будет игнорирован, элемент должен занимать столько места на форме, сколько возможно.В предыдущем примере кнопки имеют фиксированный вертикальный размер. Для того, чтобы размеры кнопок менялись как горизонтально так и вертикально, добавим настройки политик размера метода createWidgets().
void CalculatorMainWindow::createwidgets ( )
{
.....
//Зададим политики размера для кнопок: горизонтальное и вертикальное изменение размера
pushButton->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_2->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_3->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_4->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_5->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_6->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_7->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_8->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_9->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButton_10->seSsizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButtonC->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
pushButtonPlus->setSizePolicy ( QsizePolicy::Preferred, QsizePolicy::Preferred );
}
Также размеры виджета можно жёстко ограничивать с помощью методов setMaximumSize() (контролирует максимальный размер элемента интерфейса) и setMinimumSize() (контролирует минимальный размер). В качестве параметра они принимают объект класса QSize, содержащий размеры элемента. Для удобства можно использовать также методы setMinimumWidth(), setMinimumHeight(), setMaximumWidth(), setMaximumHeight() для задания минимальной ширины и высоты, а также максимальной ширины и высоты. Зададим фиксированный вертикальный размер для визуального элемента lcdNumber:
void CalculatorMainWindow::createwidgets ( )
{
...
lcdNumber->setFixedHeight ( 5 0 );
}
Результат изменения политик размера показан на рис. 13.6.
(рис 13.6) Графический интерфейс программы-калькулятора.
Одной из фундаментальных возможностей в Qt является взаимодействие объектов с помощью сигнально-слотовых соединений. Для осознания преимуществ, которые предоставляет такая возможность, рассмотрим как устроено взаимодействие между объектами в программе.
Любая объектно-ориентированная программа состоит из объектов, которые взаимодействуют между собой. Каждый из объектов обладает состоянием, которое определяет совокупность данных, которые хранит объект в данный момент. В ответ на взаимодействие с объектом его состояние может измениться. Например, объект, реализующий сетевое соединение, может получить новые отправленные данные, а объект, реализующий кнопку на окне пользовательского интерфейса, может быть нажат пользователем. Таким образом объект изменил своё состояние — и он может уведомить об этом другой объект посылая ему сообщение об изменении. Практически это взаимодействие обычно реализуют с помощью вызова метода объекта, которому нужно отправить сообщение. Этот метод должен выполнять соответствующие действия в ответ на изменение. Каждый объект выполняет свою роль, а взаимодействие между ними происходит за счёт взаимного вызова методов (прямого или косвенного), каждый из которых должен выполнять собственную чёткую функцию. Таким разделением получают уменьшение сложности кода — за счёт чёткого распределения ответственности между объектами — а, следовательно, получают и гибкость, способность классов к повторному использованию, простоту сопровождения кода. Очень важно хорошее понимание этих ключевых идей — разделение ответственности между объектами в программе и организации взаимодействия между ними на основе чётко определённых интерфейсов (наборов методов), вытекающих из фундаментальных понятий ООП: абстракции и инкапсуляции.
Таким образом, для реализации такого взаимодействия программисту нужно вызвать метод другого объекта в ответ на изменение состояния. Объект, который сменил состояние, может для этого просто хранить указатель на другой объект, чей метод нужно вызвать. В ответ на изменение состояния он будет обращаться через указатель и вызывать метод. Однако, в таком случае теряется гибкость. Например, в случае с элементами управления в окне программы, придётся программировать каждый элемент отдельно — наследовать от класса элемента управления, добавлять указатель и переопределять метод для обработки действий пользователя с целью взаимодействия с другими объектами в программе. К тому же такая связь не может быть динамической и задаётся жёстко на этапе компиляции.
Конечно можно передавать указатель на метод, который будет вызываться в ответ (callback), но такой подход тоже имеет некоторые недостатки (меньшая безопасность, всегда работает как прямой вызов метода, нужны знания о деталях реализации). Именно поэтому в Qt используется концепция сигнально-слотовых соединений.
Сигнально-слотовые соединения являются простым, но в то же время важным средством кроссплатформенного инструментария разработки Qt. Один объект при изменении своего состояния может сообщить других с помощью сигнала. Визуальные компоненты тоже вырабатывают сигналы в ответ на действия пользователя (например, нажатия кнопки, установки флага, изменения положения слайдера, редактирования текста в поле ввода и т. п.). Другие объекты могут присоединиться к сигналу слотом — специальным методом, который реализует некоторую функциональность и вызывается каждый раз, когда был выпущен присоединённый к нему сигнал. Как отмечалось ранее, сигнально-слотовые соединения могут использоваться как для взаимодействия объектов в пределах одного потока, так и между потоками (при соответствующих условиях), также возможна организация отложенного выполнения операций.
Для задания соединения используют метод connect() класса QObject. Метод принимает пять параметров:
SIGNAL();SLOT(), или название другого сигнала, будет эмитироваться (выпускаться) в ответ;Qt::AutoConnection).В следующем примере мы продемонстрируем сигнально-слотовое соединения между кнопкой QPushButton и виджетом-окном QWidget. В ответ на нажатие кнопки (сигнал clicked()), вызывается метод-слот close(), который закрывает окно. Заметим, что слоты имеют спецификатор доступа (private/protected/public) и вызывают их как и обычные методы класса. Этим они не отличаются от других методов.
connect(lPushButton,SIGNAL(clicked()),lWindow, SLOT(close()));
Сигнально-слотовые соединения могут отличаться методом вызова слота, либо механизмом соединения. Тип соединения можно указать при его создании:
Qt::AutoConnection — тип соединения по умолчанию. При соединении объектов в пределах потока ведёт себя как Direct Connection, иначе — как Queued Connection;Qt::DirectConnection — слот вызывается немедленно после того, как был выпущен сигнал. По сути это напоминает обычний вызов слота как метода;Qt::QueuedConnection — слот выполняется, как только управления перейдёт к очереди обработки сообщений потока получателя;Qt::BlockingQueuedConnection — то же, что и Queued Connection, но поток, из которого был выпущен сигнал блокируется, пока выполнение слота не будет завершено. Этот тип соединения должен использоваться только когда взаимодействующие объекты находятся в разных потоках.Qt::UniqueConnection — этот тип соединения такой же как и Qt::AutoConnection, но соединение происходит только тогда, когда оно уникально (то есть такое, которое не дублирует уже существующие соединений).Также с помощью соединений между слотами и сигналами может происходить передача параметров. Например, визуальный элемент QCheckBox выпускает сигнал toggled() каждый раз при установке и снятии флажка. Сигнал toggled() передаёт один параметр — булевое значение: true — если флажок установлен, false — если нет. Мы сможем соединить его со слотом setChecked() кнопки, который также принимает булево значение и устанавливает кнопку в положение "включено" (true) или "выключено" (false). Заметьте: мы использовали метод setCheckable(), который устанавливает для кнопки режим переключения между двумя состояниями.
lPushButton->setCheckable ( true ); connect ( lCheckBox, SIGNAL( toggled ( bool ) ), lPushButton,SLOT( setChecked ( bool ) ) );
Один и тот же сигнал объекта может быть подключён к нескольким различным сигналам и слотам другого объекта и наоборот. При этом последовательность, в которой будут вызываться присоединённые сигналы и слоты будет соответствовать порядку в котором их соединили. Также надо помнить, что одно и то же соединение может быть выполнено несколько раз подряд. В таком случае при вызове сигнала слот сработает столько же раз, сколько раз было повторно выполнено соединение. Для того, чтобы избежать этого, необходимо передавать тип соединения Qt::UniqueConnection каждый раз в случаях, когда создание повторных соединений не требуется.
Возможность создания перекрёстного сигнально-слотового соединения продемонстрирована в следующем примере:
connect ( lCheckBox, SIGNAL( toggled ( bool ) ), lPushButton,SLOT( setChecked ( bool ) ) ); connect ( lPushButton, SIGNAL( toggled ( bool ) ), lCheckBox,SLOT( setChecked ( bool ) ) );
Здесь мы видим взаимодействие между обоими элементами управления. При установке\сбросе флажка кнопка будет устанавливаться во включённое состояние или сбрасываться и наоборот.
Для того, чтобы объект мог принимать участие в сигнально-слотовом взаимодействии, нужно удовлетворить несколько условий. Класс, экземпляром которого является объект, должен наследовать от класса QObject (в случае множественного наследования QObject должен быть первым в списке классов).
Также нужно добавить к описанию класса (перед описанием любых других членов класса в секции private) макрос Q_OBJECT, который будет обработан метаобъектным компилятором moc (программа, которая выполняет предварительную обработку текста программы при запуске qmake и генерирует дополнительный код для реализации возможностей, которые предоставляет Qt). Далее в описании класса можно указать собственно сигналы и слоты. Сигналы описываются в разделе signals, а слоты — в разделе slots, где перед названием раздела стоит спецификатор доступа (public, private либо protected).
Слоты являются обычными методами класса, которые имеют реализацию и могут принимать параметры и возвращать значения. Спецификатор доступа касается только использования слота как обычного метода но не сигнальнослотовых соединений (при получении сигнала слот будет вызван независимо от спецификатора). Также значения, которые возвращает слот, игнорируются при сигнально-слотовом соединении. Сигналы, в отличие от слотов, не имеют реализации. Их реализация обеспечивается метаобьектной системой Qt. Сигналы являются "защищёнными" (protected) методами — их можно посылать из классов, которые наследуют от класса, содержащего сигнал. Сигнально-слотовые соединения могут происходить как между объектами, так и внутри самого объекта, когда тот же объект является одновременно и отправителем сигнала и получателем.
Создание собственных слотов и использования их в программе продемонстрируем на примере нашего предыдущего проекта. Добавим секцию private slots и добавим описания слотов для обработки нажатия кнопки калькулятора.
private slots : void slotClear ( ); //Обработка нажатия кнопки сброса void slotButtonPressed ( int pNum); //Обработка цифровых кнопок void slotPlusEqual ( ); //Обработка кнопки сумирования/вывода результата
Для сохранения результата и слагаемого, добавим частный член класса:
private : int mSum; //Результат int mNextNumber; //Следующее слагаемое
Для упрощения примера, наш калькулятор будет выполнять только одну операцию — сложение чисел от 0 до 9 с предыдущим результатом и вывод результата при нажатии кнопки вывода результата и суммирования. Реализацию слотов добавим в файл реализации класса CalculatorMainWindow.
void CalculatorMainWindow::slotClear ( )
{
lcdNumber->display ( 0 );
mSum = 0;
mNextNumber = 0;
}
void CalculatorMainWindow::slotButtonPressed ( int pNum)
{
mNextNumber = pNum;
lcdNumber->display (pNum);
}
void CalculatorMainWindow::slotPlusEqual ( )
{
mSum += mNextNumber;
lcdNumber->display (mSum);
mNextNumber = 0;
}
Теперь выполним соединения для кнопок суммирования и сброса в конструкторе класса.
connect ( pushButtonC, SIGNAL( clicked ( ) ), this,SLOT( slotClear ( ) ),Qt : : UniqueConnection ); connect ( pushButtonPlus, SIGNAL( clicked ( ) ), this,SLOT( slotPlusEqual ( ) ),Qt : : UniqueConnection );
Для реализации обработки цифровых кнопок можно использовать следующие подходы:
Однако возможен и другой подход — использование класса QSignalMapper. QSignalMapper привязывает некоторое значение к каждому сигналу и позволяет избежать чрезмерного дополнительного создания слотов или специализированных классов. По сути, он выполняет роль посредника между объектами, которые посылают сигналы, и слотом, который принимает параметр, изменяющийся в зависимости от объекта, который его выполнил. Добавим описание нового поля класса, содержащий указатель на QSignalMapper:
//Предварительное объявление классов class QSignalMapper; ... private : QSignalMapper *mMapper;
В конструкторе класса создадим объект и свяжем каждую из кнопок с заданным для неё значением:
#include <QSignalMapper> ... mMapper = new QSignalMapper ( this ); connect ( pushButton, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt::UniqueConnection ); connect ( pushButton_2, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_3, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_4, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_5, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_6, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_7, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_8, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_9, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); connect ( pushButton_10, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : : UniqueConnection ); mMapper->setMapp ing ( pushButton, 1 ); mMapper->setMapp ing ( pushButton_2, 2 ); mMapper->setMapp ing ( pushButton_3, 3 ); mMapper->setMapp ing ( pushButton_4, 4 ); mMapper->setMapp ing ( pushButton_5, 5 ); mMapper->setMapp ing ( pushButton_6, 6 ); mMapper->setMapp ing ( pushButton_7, 7 ); mMapper->setMapp ing ( pushButton_8, 8 ); mMapper->setMapp ing ( pushButton_9, 9 ); mMapper->setMapp ing ( pushButton_10, 0 ); slotClear ( );
После этого соединим QSignalMapper сигнально-слотовым соединением с нашим слотом для обработки нажатий цифровых кнопок:
connect (mMapper, SIGNAL(mapped ( int ) ), this,SLOT( slotButtonPressed ( int ) ),Qt : : UniqueConnection );
Теперь программа-калькулятор готова к работе.
Заметим, что для создания соединения обычно используют метод connect(), но существует способ установить соединение автоматически. Обычно такие соединения можно использовать в случае, когда необходимо соединить элементы на форме созданной как Ui-файл с программным кодом, реализующий обработку действий пользователя. Автоматическое соединение задают следующим образом:
on_<имя объекта>_<имя сигнала> (параметры)
Соединение происходит с помощью вызова статического метода QMetaObject:: connectSlotsByName.
Несмотря на относительную простоту этот метод не всегда является удобным (учитывая некоторую неочевидность, а также способ именования слотов — особенно когда это касается многократного их использования).
Подытожим наши знания о сигналах, слотах и сигнально-слотовых соединениях:
Слоты:
private slots, protected slots, public slots);Сигналы:
signals);void;moc);emit (используется для различия вызова метода и эмитирования сигнала, но фактически не выполняет никакой специальной роли).Все виджеты в Qt наследуют от класса QWidget. Класс QWidget предоставляет базовую функциональность общую для всех виджетов. Среди свойств, которые наследуют от QWidget, — свойство enabled, которое позволяет разрешить или запретить взаимодействие пользователя с элементом управления (методы void setEnabled(bool) и bool isEnabled()). Когда свойство установлено (логическое значение false) — визуальный элемент деактивирован и пользователь больше не может с ним взаимодействовать. Обычно такие деактивированные элементы изменяют внешний вид, чтобы пользователь смог их отличить от активных. Свойство visible (методы void setVisible(bool) и bool isVisible()) определяет видимый виджет (значение true) или нет (значение false). Эти свойства влияют не только на сам визуальный элемент, но и на дочерние элементы.
Кроме этих общих свойств, каждый виджет обладает собственными уникальными особенностями, которые позволяют создавать удобные и практичные в использовании пользовательские интерфейсы.
Так, например, кнопки (Buttons), очень часто употребляемый элемент управления. В общем поведение для кнопок определяет абстрактный класс QAbstractButton. Эти элементы могут находиться в включённом или выключенном состояниях. Состояние можно определять с помощью свойства checked (метод isChecked()). Переключением можно управлять с помощью свойства checkable (bool isCheckable(), setChеcked(bool)). От класса QAbstractButton наследуют классы QCheckBox, QPushButton, QRadioButton, QToolButton.
QPushButton чаще используют соединяя его сигнал clicked(), который вызывается при нажатии кнопки, с другими слотами.QToolButton является кнопкой для быстрого доступа к действиям или настройкам, которую обычно используют внутри панели инструментов.QCheckBox — элемент-флажок. Может находиться в включённом или выключенном состоянии. Также может иметь третье промежуточное состояние, поддержку которого можно активировать с помощью метода setTristate().QRadioButton — кнопка-переключатель. Как и флажок может находиться в включённом или выключенном состоянии. Её используют в группе с другими переключателями (см. класс QButtonGroup) для обозначения нескольких взаимоисключающих вариантов выбора, где только один переключатель включён, а все остальные выключены.К виджетам-контейнерам (Containers) относят QFrame, QGroupBox, QTabWidget, QToolBox. QFrame — наиболее общий элемент. Это базовый класс для виджетов, которые имеют обрамление. От него наследуют такие классы визуальных элементов как QLabel, QLCDNumber, QSplitter, QToolBox, QStackedWidget, QAbstractScrollArea. Может использоваться самостоятельно для отображения различных рамок.
QGroupBox используют для выделения группы виджетов рамкой с надписью. Есть возможность задать клавиатурную комбинацию, чтобы перевести фокус ввода на виджеты в группе. Не создаёт компоновку для группы виджетов автоматически.QTabWidget — виджет для отображения виджетов внутри отдельных страниц. Предоставляет панель вкладок и показывает виджет для текущей страницы внутри себя. Для работы необходимо создать виджет и добавить его как страницу, а также задать имя страницы.QToolBox используют для создания вертикальной колонки виджетов со вкладками. Каждый виджет имеет отдельную вкладку. Текущий видимый виджет соответствует текущей открытой вкладке.Также следует выделить виджеты-виды (Views), к которым относят QListView, QListWidget, QTableView, QTableWidget, QTreeView, QTreeWidget. Они добавляют возможность выводить информацию в виде списков, таблиц и деревьев. QListView, QTableView, QTreeView используют модель, как источник данных (см. абстрактный класс QAbstractItemModel). QListWidget, QTableWidget и QTreeWidget используют как самостоятельный виджет с данными, данные добавляют поэлементно (см. QListWidgetItem, QTableWidgetItem, QTreeWidgetItem).
К элементам вывода информации (Display widgets) относят QLabel, QLCDNumber и QProgressBar. QProgressBar позволяет вывести текущий прогресс в виде заполненной линии. QLCDNumber выводит целые и числа с плавающей запятой в стиле семисегментного дисплея. QLabel используют для вывода различной текстовой информации. Этот виджет также поддерживает разметку HTML4, которую можно использовать для оформления текста.
Наиболее многочисленная группа — элементы ввода (Input widgets). К ним относят QComboBox, QDateEdit, QDial, QDoubleSpinBox, QFontComboBox, QLineEdit, QScrollBar, QSlider, QSpinBox, QTextEdit, QTimeEdit.
QComboBox — выпадающий список, используемый для выбора элемента из списка альтернатив.QDateTimeEdit — поле ввода даты и времени. Позволяет вводить и показывать время в заданном формате. Вид этого элемента наследуют также QTimeEdit и QDateEdit.QDial — изменяет числовое значение по тому же принципу, что и регуляторы на панели приборов. Наследует от абстрактного класса QAbstractSlider.QLineEdit — поле ввода. Даёт возможность не только вводить текст, но и проверять допустимость ввода (см. класс QValidator). Имеет режим для ввода пароля. Также возможно задать маску для ввода значений.QScrollBar — элемент управления скроллингом. Наследует от абстрактного класса QAbstractSlider. Каждому положению указателя соответствует значение в заданных пределах. Часто используют для прокрутки содержимого других виджетов.QSlider — элемент, который использует перетаскивания мышкой для ввода значений. Каждому положению указателя соответствует значение, в заданных пределах. Наследует от абстрактного класса QAbstractSlider.QScrollBar реагировал на перемещение QSlider).QGridLayout сеткой 3х3. Также разместите в окне надпись QLabel, которая будет показывать текст "Player1" или "Player2" в зависимости от того, ходит первый игрок (крестики) или второй игрок (нолики). При нажатии на кнопку она должна менять текст в зависимости от игрока на символ "X" или "O". Используйте для этого класс QSignalMapper. Также добавьте кнопку "Clear", которая будет очищать поле (устанавливать в качестве текста для всех кнопок пустую строку). После каждого нажатия кнопки "Clear" порядок хода меняется.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.