msiu_logo
Кафедра информационных систем и технологий
http://edu.msiu.ru

Основы Qt

А.Г. Верещагин

14 марта 2014

Введение

Qt — это кроссплатформенный (включая мобильные платформы) комплексный каркас для разработки приложений на C++. Qt значительно расширяет стандартную библиотеку языка, предоставляя инструменты для:

В данной статье рассматриваются лишь базовые возможности Qt. Обзор начинается с ядра библиотеки, на основе которого построена большая часть остальных компонентов. Далее рассматривается подход к созданию графического пользовательского интерфейса. В конце статьи рассматривается инструментарий библиотеки: метаобъектный компилятор moc, утилита генерации make-файлов qmake, интегрированная среда разработки Qt Creator и среда разработки графических интерфейсов Qt Designer.

Ядро Qt

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

Объектная модель

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

В основе объектной модели Qt лежит класс QObject. QObject является единым базовым классом для большинства классов Qt и многих пользовательских классов. Ключевой особенностью объектной модели Qt является мощный механизм прозрачного общения между объектами, называемый сигналы и слоты. Данный механизм доступен для всех наследников класса QObject.

Все объекты Qt организуются в иерархии владения, так что для любого не корневого объекта определён его объект-родитель, а объекты-родители имеют доступ к своим дочерним объектам. При удалении объекта-родителя дочерние объекты удаляются автоматически.

Каждому объекту может быть дано имя, по которому родительский объект может обращаться к дочернему. Система свойств позволяет хранить в объекте произвольное число дополнительных пользовательских свойств, управление которыми происходит во время исполнения.

Объекты Qt могут участвовать в цикле обработки событий. Система событий — это альтернативный механизм общения между объектами наряду с механизмом сигналов и слотов.

Информация о классе объекта может быть получена через систему метаобъектов. Все объекты одного типа разделяют единый метаобъект с описанием их класса, который предоставляет информацию об имени класса, его положении в иерархии наследования, реализуемых методах и свойствах. Данная информация генерируется автоматически с помощью компилятора метообъектов.

Для корректного функционирования системы объектов каждый наследник QObject должен содержать в теле класса макрос Q_OBJECT в закрытой секции. При множественном наследовании Qt допускает наличие лишь одного базового класса, производного от QObject.

C++ предоставляет оператор преобразования типов в рамках единой иерархии dynamic_cast. Преобразования в иерархии объектов Qt возможно с помощью нового оператора object_cast. В сравнении с dynamic_cast данный оператор обладает рядом преимуществ: он не использует механизм динамической идентификации типов данных (RTTI), используя вместо него более простой и быстрый механизм метообъектов Qt, и позволяет преобразования даже между границами динамических библиотек.

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

Система метаобъектов

Каждый Qt-объект имеет в своей основе метаобъект — экземпляр класса QMetaObject. Система метаобъектов позволяет функционировать механизму сигналов и слотов, динамической системе свойств и является источником информации о типах данных во время исполнения. Классы, производные от QObject являются источником информации для системы метаобъектов. Наличие макроса Q_OBJECT включает поддержку данной системы для объектов. Сбором информации для системы метаобъектов Qt занимается компилятора метообъектов.

Получить указатель на метаобъект для соответствующего производного от QObject класса можно с помощью функции-члена metaObject() или статической функции-члена staticMetaObject(). Функция-член метаобъекта className() возвращает C-строку с именем класса, который описывает метаобъект. По имени класса с помощью функции-члена inherits() класса QObject можно проверить, является ли объект наследником названного класса. Метаобъект базового Qt-класса можно получить с помощью функции-члена superClass() класса QMetaObject.

С помощью макросов Q_CLASSINFO() в Qt-классах можно описывать любую дополнительную информацию, например, имя автора и номер версии. Информация может быть получена по индексу с помощью метода метаобъекта classInfo, который возвращает объект типа QMetaClassInfo, хранящий в себе строку с ключом и строку со значением свойства класса.

С помощью Qt-метаобъектов возможно создание новых экземпляров класса, которому они соответствуют. Такая возможность может быть полезна в случае, когда имя класса, экземпляр которого необходимо создать, заранее неизвестно. Для создание нового экземпляра класса потомка QObject используется метод метообъекта newInstance, который принимает в качестве аргументов обёртки над аргументами соответствующего конструктора описываемого класса. При этом конструктор должен быть помечен макросом-спецификатором Q_INVOKABLE. Обёртки над аргументами указываются с помощью макроса Q_ARG(), который принимает тип аргумента и ссылку на величину аргумента, которая должна быть константной.

Наконец, система метаобъектов Qt позволяет вызывать по имени методы Qt-объектов. Эта особенность полезна, когда имя вызываемого метода заранее неизвестно. Вызвать метод можно с помощью статической функции-члена invokeMethod класса QMetaObject. Первым аргументом передаётся указатель на объект, у которого должен быть вызван метод. Второй аргумент — имя метода в виде C-строки. Если метод возвращает значение, третьим аргументом передаётся обёртка над переменной, куда возвращаемое значение должно быть возвращено. Обёртка получается с помощью макроса Q_RETURN_ARG() аналогично макросу Q_ARG(). Далее передаются обёрти над аргументами вызываемого метода, которые получаются с помощью макроса Q_ARG(). Метод invokeMethod возвращает true или false в зависимости от того, был ли вызван метод или нет. Имя метода должно быть именем сигнала или слота, или именем функции-члена, помеченной макросом-спецификатором Q_INVOKABLE.

Система метообъектов и объекты-обёртки позволяют Qt осуществлять контроль типов даже во время исполнения, что делает использование сигналов и слотов, контруирование объектов по известному метатипу и вызов методов по имени более безопасным.

Система метатипов

Не все типы данных разумно представлять в виде Qt-объектов. Производные от QObject классы обладают мощным набором преимуществ, поддержка которых требует определённых ресурсов, и некоторыми ограничениями. Это подходит не для всех классов. Поэтому в Qt существует облегчённая система метатипов как часть системы метаобъектов и альтернатива ей.

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

Объект, описывающий метатип, является экземпляром класса QMetaType и может быть получен по идентификатору зарегистрированного типа с помощью конструктора. Сам идентификатор можно получить по имени типа с помощью статической функции-члена type().

Система метатипов позволяет вызывать конструкторы по умолчанию и копирования зарегистрированного типа с помощью функции-члена create() (статической или нет). Функция опционально принимает в качестве аргумента указатель на копируемый объект. При указанном аргументе вызывается конструктор копирования, иначе — конструктор по умолчанию. Статическая версия функции в качестве первого обязательного аргумента принимает идентификатор типа. Вызвать деструктор можно с помощью аналогичной функции destroy().

Для получения метаобъекта необходим экземпляр описываемого класса во время исполнения или имя класса, известное на этапе компиляции. При использовании метатипов для создания экземпляров зарегистрированных типов данных можно использовать C-строку с именем класса. В этом заключается одно из преимуществ механизма метатипов, но наличие конструкторов по умолчанию и копирования, а так же необходимость регистрации во время исполнения — это достаточно сильные требования. Большая часть типов Qt, не являющихся Qt-объектами, уже зарегистрирована в системе метатипов.

Обобщённый тип QVariant

Система метатипов была разработка с целью поддержки обобщённого типа данных QVariant. Назначение обобщённого типа данных — хранение величины любого из поддерживаемых типов. Для указателей обобщённый тип данных реализовать достаточно просто, так как все указатели занимают фиксированный объем памяти. Ситуация гораздо сложнее, когда обобщённый тип данных должен хранить не указатель, а саму величину. Когда множество поддерживаемых типов известно заранее, можно использовать объединения C/C++. В Qt в качестве обобщённого типа данных для всех типов, зарегистрированных в системе метатипов, используется класс QVariant.

Конструктор по умолчанию класса QVariant конструирует объект, не содержащий никакой величины — «пустой» объект. Для каждого предопределённого типа Qt определён конструктор преобразования в QVariant. QVariant, хранящий пользовательский тип данных, зарегистрированный в системе метатипов, может быть получен с помощью конструктора, принимающего идентификатор типа и указатель на сохраняемое значение. QVariant поддерживает оператор копирующего присваивания, так что величина в одном и том же QVariant в разное время можно хранить различные объекты различных типов. Такое свойство называется динамической типизацией (dynamic typing).

Для того, чтобы у обобщённого типа данных Qt можно было вызывать методы, необходимо преобразование к конкретному типу данных с помощью шаблонной функции-члена value(), которой в качестве параметра передаётся конкретный тип. Функция возвращает копию объекта соответствующего типа данных. Если преобразование к запрашиваемому типу данных невозможно, вызывается конструктор по умолчанию данного типа. Проверить возможность преобразования можно с помощью шаблонной функции-члена canConvert(). Тип, возможность преобразования к которому проверяется, указывается в качестве параметра шаблона. Так же существует не шаблонная версия функции, принимающая идентификатор зарегистрированного типа в качестве аргумента. Для преобразования в предопределённые типы данных определены соответствующие функции.

Экземпляры класса QVariant поддерживают операции сравнения, если хранимая величина поддерживает данные операции. В случае, если сравниваются величины разных типов, осуществляется попытка сконвертировать правый операнд к типу левого.

Обобщённые типы данных позволяют работать с гетерогенными структурами данных и контейнерами неполиморфных типов. Как было сказано выше, для тех же целей могут быть использованы пустые указатели и объединения. QVariant обладает большими возможностями, и его использование является более безопасным, так как осуществляются дополнительные проверки возможности конвертирования в соответствующий тип.

Система свойств

Система свойств Qt позволяет сохранять в объектах индивидуальные свойства в дополнение к обычным данным-членам объекта или оборачивая их. Доступ к свойствам можно получить для любого Qt-объекта с помощью системы метаобъектов. Функция-член property() класса QObject позволяет получить у объекта значение свойства по его имени. Функция возвращает значение в виде обобщённого типа QVariant, так что Qt поддерживает только свойства типов, поддерживаемых системой метатипов. Установить значение свойства по его имени для Qt-объекта можно с помощью функции-члена setProperty(), принимающей имя свойства и его значение в виде QVariant в качестве аргументов. С помощью данной функции можно добавлять объектам новые свойства, называемые динамическими свойствами (dynamic properties), и удалять их, используя в качестве величины «пустой» QVariant().

Свойства, описанные при определении класса, называются статическими свойствами (static properties). Эти свойства доступны с момента создания объекта и являются отражением членов-данных объекта или его геттеров и/или сеттеров. Самая простая конструкция описания свойства в теле определения Qt-класса выглядит следующим образом: Q_PROPERTY(type name MEMBER memberName). Таким образом описывается свойство типа type с именем name, являющееся отражением члена memberName. Данную конструкцию можно дополнить строкой READ getFunction или WRITE setFunction для предоставления доступа на чтение через геттер getFunction или на записать через сеттер setFunction. Можно убрать MEMBER memberName и оставить только READ getFunction и, опционально, WRITE setFunction для ограничения доступа только через функци-члены. В таком случае свойство не обязательно должно быть отражением какого-то атрибута объекта и может генерироваться «на лету». Опционально можно добавить функцию сброса свойства в значение по умолчанию с помощью конструкции RESET resetFunction и зарегистрировать сигнал оповещения об изменении свойства с помощью конструкции NOTIFY notifySignal. Макрос Q_PROPERTY поддерживает и другие опциональные конструкции для более гибкого описания особенностей свойства.

Многие определённые в Qt объекты поддерживают различные свойства, которые позволяют управлять их поведением. При этом сам механизм свойств позволяет контролировать данные свойства единообразным образом. Системы свойств, метаобъектов и метатипов, сигналов и слотов используются средой Qt Designer при экспортировании спроектированного пользователем интерфейса в ui-файлы и генерации по ним классов, реализующих данный интерфейс, которые далее могут быть расширены и дополнены соответствующей логикой в Qt Creator.

Система событий

Система событий — это низкоуровневая система взаимодействия между объектами и внешней средой. События описываются экземплярами классов производных от класса QVariant. Qt-объекты обрабатывают поступающие события в методе event(). Данный метод может быть переопределён полностью или с использованием реализации из базового класса. Реализация по умолчанию использует для обработки событий объекты-фильтры. Функция event() вызывает функцию eventFilter() объекта-фильтра, передавая событие и объект, для которого оно было сгенерировано, в качестве аргументов. Фильтр может обработать событие, остановить его обработку, вернув true или вернуть событие объекту, вернув false. Для Qt объектов фильтры можно устанавливать и удалять динамически с помощью функций-членов installEventFilter() и removeEventFilter() класса QObject. Пока событие не будет остановлено, event() будет передавать его от фильтра к фильтру в порядке от более новых фильтров к более старым.

При разработке новых Qt объектов для поддержки событий следует предпочесть механизм фильтров переопределению функции event(), либо при переопределении оставлять вызов реализации из базового класса для сохранения поддержки механизма фильтров, который может быть использован пользователем. Между тем, предопределённые фильтры как правило не обрабатывают события самостоятельно, а делегируют обработку виртуальным методам-обработчикам, которые могут быть переопределены пользователем при наследовании. Так, при наследовании от предопределённых в Qt классов для переопределения обработчиков предопределённых событий следует переопределять функции-обработчики, используя фильтры и переопределение функции event() лишь тогда, когда переопределение функций-обработчиков является недостаточно гибким способом решения задачи. Например, каждый Qt-объект имеет функцию-обработчик customEvent(), которая автоматически вызывается при получении объектом событий пользовательского типа.

В Qt для реализации механизма диспетчеризации событий используется Qt-класс QCoreApplication или его наследники. Во время работы программы активен только один экземпляр данного класса. При вызове метода exec() объект приложения входит в бесконечный цикл обработки поступающих событий и занимается их доставкой. Программистам не следует напрямую вызывать функцию event() у Qt-объектов. Вместо этого следует использовать статические функции-члены sendEvent() и postEvent() класса QCoreApplication. События, отправленные через sendEvent() обрабатываются немедленно, тогда как обработка событий, отправленный с помощью postEvent() является отложенной до следующей итерации цикла обработчика. Экземпляры QCoreApplication и производных классов для обработки событий используют метод notify(). Именно этот метод вызывается для отправки событий другим объектам функциями sendEvent() и postEvent(). Реализация по умолчанию так же поддерживает механизм фильтров, с помощью которого можно гибко подстраивать, какие события кому и в каком виде будет доставлены и будут ли они вообще обработаны. Так же для некоторых типов событий реализация по умолчанию следит за тем, обработал ли объект событие, и если нет, событие может быть доставлено родителю объекта. Переопределение функции notify() позволяет получить практически полный контроль над циклом обработки событий и их диспетчеризацией, если это необходимо и иные методы не могут быть использованы.

Источником событий может быть не только внутренняя логика приложения, но и «внешний мир», генерирующий платформозависимые события в зависящем от платформы формате. Для обработки подобных событий на самом низком уровне можно так же определять фильтры — экземпляры классов, производных от QAbstractNativeEventFilter, — добавлять их и удалять в экземпляре объекта приложения с помощью методов installNativeEventFilter() и removeNativeEventFilter(). Обычно подобные фильтры преобразуют платформозависимые события в платформонезависимые события типа QVariant, включая их в обычный цикл обработки.

Сигналы и слоты

Пользователям редко приходится определять свои собственные типы событий, а существующие обрабатываются предопределённым образом, который редко имеет смысл менять. Обычно достаточно переопределить автоматически вызываемые обработчики соответствующих событий, да и то только в том случае, когда пользователь разрабатывает свой собственный производный Qt-класс, тесно интегрированный со всей остальной экосистемой приложения. Обработчики событий обычно фиксированы и редко меняют своё поведение. При использовании механизма событий отправитель должен знать, кому доставить событие, и он хочет, чтобы событие было обработано. В Qt существует альтернативный подход более высокого уровня, который носит название «сигналы и слоты». Когда один Qt-объект меняет своё состояние и хочет оповестить об этом окружение, он генерирует сигнал, который может быть доставлен в один или несколько слотов тех объектов, которые подключились к данному сигналу отправителя. При попадании сигнала в слот вызывается его обработчик — получатель реагирует на изменение. Идеологически механизм сигналов и слотов и механизм событий выполняют различные задачи и каждый имеет свою философию. Но иногда эти механизмы тесно соприкасаются. Так, объект «кнопка» может обрабатывать событие «нажатие» и при этом генерировать сигнал «нажата», которое может быть обработано в объекте, реализующем пользовательский интерфейс, если слот обработчика был заранее подключён к сигналу данной кнопки.

Сигналы, поддерживаемые объектом, объявляются в теле определения Qt-класса аналогично остальным функциям-членам. Объявления сигналов размещаются в специальной секции signals: подобно секциям public:, protected: и private:. Сигналы являются открытыми функциями, а их реализация генерируется компилятора метаобъектов. Данная реализация сводится к просмотру сущестсвующих подключений к сигналу и вызовы соответствующих слотов. Сигналы не должны возвращать каких-либо значений (возвращаемый тип void). Сигналы могут быть вызваны как обычные методы, но рекомендуется вызывать их только из методов класса, определяющего сигнал, или его производных классов (как защищённые методы). Так же рекомендуется предварять вызов сигнала словом emit, которое не имеет никакого функционального значения, но делает код нагляднее.

Слоты объявляются так же как и обычные функции. В отличие от сигналов, программист должен предоставить определения данных функций. Объявления слотов располагаются в секции public slots: тела определения Qt-класса. Все слоты, как и сигналы, всегда являются открытыми функциями. Слоты могут использоваться как обычные функции. Также слоты могут вызываться при генерации сигналов, если соответствующий слот подключен к соответствующему сигналу.

Подключение сигналов к слотам осуществляется с помощью статической функции-члена connect() класса QObject. В качестве первого аргумента функции используется указатель на объект-отправитель, который будет генерировать сигналы. Второй аргумент должен указывать на сигнал, для которого осуществляется подключение. В качестве данного аргумента может быть использован указатель на метод отправителя. Третий аргумент — указатель на объект-получатель, слот которого будет вызван при обработке сигнала. Четвёртый аргумент указывает на слот — это так же может быть указатель на метод, только теперь получателя. Механизм сигналов и слотов является типобезопасным. Сигнатуры соединяемых сигналов и слотов должны быть совместимыми. При использовании указателей на методы большая часть проверок осуществляется во время компиляции. Часть проверок может происходить во время исполнения. Сигнатура слота может содержать меньше аргументов, чем сигнатура сигнала — дополнительные аргументы будут проигнорированы. Сигналы можно подключать к другим сигналам. Такое подключение позволяет генерировать одни сигналы за счёт других.

Для разрыва подключения используется статическая функция-член disconnect(). Данной функции передаются аргументы, аналогичные вызову функции connect(). Использование значения 0 вместо указателя на сигнал означает отключение всех сигналов от заданного или любого слота. Использование значения 0 вместо указателя на получателя означает отключение заданного сигнала для всех получателей и всех их слотов. Использование значения 0 вместо указателя на слот означает отключение от всех слотов заданного или любого сигнала. Если третий аргумент 0 — четвёртый так же должен быть 0. Нет возможности отключить у всех получателей обработку сигнала только для конкретного слота. Это объясняется тем, что для ссылки на слоты различных получателей по сути должны быть использованы различные указатели на функции, и использовать какой-то один не представляется возможным.

Пользовательский интерфейс

Qt поддерживает несколько технологий для создания графического пользовательского интерфейса. Для разных случаев подходят различные технологии, так что их возможно сочетать по мере необходимости. Qt Creator — пример приложения, который смешивает все три подхода. Классические виджеты используются в качестве основы графического пользовательского интерфейса. Qt Quick, основанный на QML — JacaScript-подобном языке программирования, — используется для экрана приветствия, тогда как для предоставления справочной документации используется Qt WebKit. Рассмотрение Qt Quick и Qt WebKit выходит за рамки данной статьи, так что за более подробной информацией следует обратиться к сторонним источникам.

Виджеты

Qt Widgets (виджеты Qt) — это традиционные элементы графического пользовательского интерфейса, которые обычно используются в окружениях рабочего стола. Qt подстраивает внешний вид виджетов под используемое окружение, так что приложения Qt выглядят «как родные» под Windows, Linux и Mac OSX. Виджеты Qt прекрасно подходят для статичного пользовательского интерфейса без сложной анимации, что является стандартной ситуацией, например, для приложений офисного типа.

Виджеты Qt могут отображать данные и состояние приложения, принимать обрабатывать пользовательские события и являться контейнерами для других виджетов, которые должны быть сгруппированы вместе. Виджеты, не вложенные в другие виджеты, называются окнами (window). Виджеты Qt представляются классами, производными от QWidget. Сам QWidget является классом, производным от QObject, так что виджеты Qt прекрасно вписываются в объектную модель каркаса.

Виджеты и события

Класс QWidget предоставляет базовую реализацию функции event() для обработки событий приложения. Данная реализация вызывает предопределённые функции-обработчики для различных типов ситуаций:

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

Стили виджетов

Внешний вид виджетов может быть настроен с помощью таблиц стилей Qt (подобно каскадным таблицам стилей — CSS). Таблицы стилей могут быть установлены для всего приложения целиком или отдельных виджетов. Стиль, установленный для одного виджета, распространяется и на его потомков. Различные свойства, определённые на различных уровнях, могут комбинироваться и перекрываться. Именно благодаря механизму стилей одни и те же виджеты могут выглядеть «как родные» в различных операционных системах и окружениях рабочего стола. При разработке собственных виджетов, имеющих уникальный внешний вид, стоит задуматься о поддержке различных свойств таблиц стилей Qt.

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

Каждый виджет имеет своё собственную область размещения на экране, в которой и отображается. Определение положения и размера данной области — задача, вообще говоря, от виджета не зависящая. Решением данной задачи занимаются менеджеры размещения (layout menegers).

Qt включает в себя набор управляющих размещением классов, производных от QLayout. Экземпляры данных классов описывают, как должны располагаться дочерние виджеты внутри родительского, чтобы оптимальным образом занимать доступное пространство. Менеджеры размещения автоматически позиционируют и изменяют размеры дочерних виджетов, когда размер доступного для их размещения пространства меняется.

Все производные от QWidget классы могут использовать менеджеры размещения для управления размещением дочерних виджетов. Сначала создаётся родительский виджет. Потом дочерние виджеты (нет необходимости указывать выстраивать родственные отношения между родителем и детьми во время конструирования). Далее создаётся экземпляр менеджера размещения, дочерние виджеты помещаются в него как в контейнер. На последнем этапе менеджер размещения устанавливается в родительский виджет с помощью метода setLayout(). Некоторые менеджеры размещения так же могут быть вложены друг в друга; тогда вложенный менеджер трактуется обрамляющим менеджером как обычный виджет, хотя экземпляры QLayout не являются виджетами как таковые.

В Qt определены следующие основные менеджеры размещения:

Некоторые виджеты могут быть «чувствительны» относительно того, как может быть изменён их размер и пропорции. В свойство виджета sizeHint может быть установлен «рекомендуемый размер», относительно которого могут быть определены возможные границы допустимых для него размеров. Возможности по изменению размеров описываются в свойстве sizePolicy объектом типа QSizePolicy. Данный объект хранит информацию и принятых для горизонтального и вертикального размера политиках изменения размера, которые могут независимо принимать одно из следующих допустимых значений перечисления:

Объект sizePolicy так же описывает факторы растяжения (stretch factors), позволяя менеджеру размещения вычислять пропорции виджетов друг относительно друга в заданном измерении. Так, если один виджет имеет в вертикальном измерении фактор растяжения 1, а второй — 2, то менеджер размещения будет стремиться сделать вертикальный размер второго виджета в два раза больше, чем у первого.

С помощью метода setHeightForWidth() класса QSizePolicy возможно установить в истину флаг, отвечающий за то, должен ли виджет сохранять собственные пропорции в вертикальном и горизонтальном измерении, соответствующие его sizeHint.

Иногда бывает необходимо ограничить одновременно как минимальные, так и максимальные допустимые диапазоны размеров виджета. В этом случае для определения ограничения на минимальный размер можно использовать свойство minimumSizeHint, а для ограничения максимального размера — просто sizeHint. Менеджеры размещения не делают размер виджета меньше, чем определённый в свойстве minimumSizeHint, если оно было задано, и если только в свойстве sizePolicy не было задано игнорировать рекомендуемые размеры в определённом измерении.

Главное окно приложения

Главное окно приложения Qt — это виджет, производный от класса QMainWindow. Данный класс предоставляет всё самое необходимое для разработки типичного оконного приложения, включая само главное окно, меню, панели инструментов, панель информации и др.

Классы QMenu и QMenuBar

QMainWindow предоставляет область для размещения панели меню приложения в верхней части главного окна. Данная панель автоматически добавится на главное окно, если на ней будут размещены пункты меню. Так же, используя механизм менеджеров размещения, панель меню может быть добавлена в любую другую часть окна приложения.

Панель меню представляется классом QMenuBar, производным от QWidget. Доступ к панели меню по умолчанию можно получить с помощью метода menuBar() главного окна.

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

Доступные действия описываются с помощью класса QAction. Действия так же могут иметь своё название, иконку, подсказку, сочетание клавиш для вызова действия и бывают двух типов. Флаговые (checkable) действия имеют два состояния: включенное и выключенное. При переключнии между ними генерируется сигнал toggled (переключено). При необходимости флаговые действия можно объединять в группу, так что при включении одного действия включенное до это будет отключено. Обычные действия имеют всего одно состояние и могут генерировать сигнал triggered (активировано). Сигналы действий соединяют со слотами, которые должны реализовывать соответствующую ситуации реакцию на активацию или изменение состояния действия.

Класс QToolBar

Класс QToolBar реализует панель инструментов — альтернативный механизм запуска действий, позволяющий управлять приложением в обход меню. На панели инструментов размещаются различные действия (экземпляры QAction), возможно, продублированные в меню, для того, чтобы пользователь имел к ним более быстрый доступ. Действия на панели инструментов могут объединяться в секции. Если какие-то действия не помещаются на панель инструментов, к ним можно получить доступ через выпадающий список, предоставляемый панелью инструментов.

Панелей инструментов у главного окна может быть сколько угодно. По четырём сторонам окна доступны пространства для размещения этих панелей. В каждой из этих областей панели инструментов могут быть размещены слева или справа, сверху или снизу относительно других. Так же панели инструментов могут быть «откреплены» от главного окна, будучи перемещёнными в собственное окно. Все эти действия пользователь может осуществлять с помощью обычного перетаскивания мышью. Если какие-то из доступных действий необходимо запретить, это так же может быть сделано.

Класс QDockWidget

Кроме панели меню и панелей инструментов в главном окне приложения может быть размещено неограниченное количестве доков (docks) — перемещаемых виджетов, выполняющих роль контейнеров для других виджетов. Доки представлены классом QDockWidget. С помощью данного класса реализуется сложная логика перетаскивания, так, что доки не только могут быть размещены рядом по четырём сторонам главного окна, но и так же быть вложены друг в друга. Кроме этого доки могут быть «сложены в стопку», так что один док будет закрывать собой другой, но между ними возможно переключение с помощью вкладок. Наконец, как и панели инструментов, доки могут быть откреплены от главного окна и перемещены в собственные окна.

Инструментальная поддержка

В составе каркаса Qt поставляется ряд утилит и программ, делающих разработку Qt-приложений возможной и, по возможности, простой.

moc

Для поддержки объектной модели Qt, систем метаобъектов, сигналов и слотов файлы исходных кодов приложения, использующих Qt-объекты должны быть преобразованы компилятором метаобъектов (Meta-Object Compiler), который генерирует дополнительные файлы, необходимые для дальнейшей корректной сборки проекта. Из заголовочных файлов, содержащих определение Qt-классов, получаются файлы реализации с префиксом moc_, которые потом компилируются в отдельные объектные файлы, связывающиеся со всеми остальными. Если определение Qt-класса находится в файле реализации, то генерируется отдельный .moc-файл, который должен быть включен в файл реализации с помощью директивы препроцессора #include. Дополнительного объектного файла сгенерировано не будет.

qmake

Утилита qmake упрощает сборку проектов на различных платформах. Данная утилита автоматически генерирует Makefile на основе .pro-файла, описывающего проект, так что далее проект может быть собран стандартной утилитой make. Файлы проектов обычно устроены довольно просто, но могут содержать и более сложную информацию, если это необходимо.

Файл проекта обычно содержит перечисление заголовочных файлов, файлов реализации, файлов форм, ресурсов, модулей Qt, сторонних библиотек и конфигураций. Можно опционально указывать различные наборы для различных платформ. При использовании интегрированной среды разработки Qt Creator большая часть содержимого файла проекта генерируется автоматически.

Qt Creator

Qt Creator — это кроссплатформенная интегрированная среда разработки Qt-приложений, поддерживающая подсветку и проверку синтаксиса, автодополнение кода, управление проектами, автоматическую сборку проектов, запуск, отладку и профилирование, системы контроля версий и др. В Qt Creator входит визуальный редактор пользовательских графических интерфейсов Qt Designer (доступный так же в виде отдельного приложения) и инструмент просмотра онлайн-документации Qt Assistant. Так же Qt Creator поддерживает взаимодействие с Qt Linguist — инструментом локализации Qt-приложений.

Qt Designer и uic

Qt Designer предназначен для упрощения процесса создания графических пользовательских интерфейсов, позволяя вести работу в WYSIWYG-стиле («что ты видишь, то и получишь»). С помощью Qt Designer возможна «сборка» интерфейса из готовых компонентов виджетов и менеджеров размещения, настойка их свойств, редактирование меню приложения, панелей инструментов, доступных пользователю действий, связей между определёнными сигналами и слотами. Возможно описание своих собственных виджетов для их дальнейшего использования.

Спроектированный интерфейс представляет собой статичную древовидную структуру, которая может быть записана с помощью XML в т.н. ui-файлах. Если WYSIWYG-возможностей Qt Designer оказывается недостаточно, всегда можно вручную отредактировать ui-файлы для получения необходимого результата.

Компилятор пользовательского интерфейса uic (User Interface Compiler) используется для генерации заголовочных файлов, содержащих описание спроектированного интерфейса, из ui-файлов. Сгенерированный файл должен быть подключен в файл реализации соответствующего элемента интерфейса (окна или виджета), для того, чтобы спроектированные элементы были доступны внутри соответствующего класса. Данный класс должен быть унаследован не только от QWidget (или производных), но и от класса ui, который отвечает за построение интерфейса во время запуска приложения по заданному ui-файлу, содержащему всю необходимую информацию. Так же осуществляется автоматическое связывание некоторых сигналов и слотов. Так, существует соглашение, что если в классе определён слот on_NAME_SIGNAL, где NAME — это имя виджета, а SIGNAL — имя сигнала, то соответствующий сигнал соответствующего виджета будет подключен к данному слоту автоматически. Таким образом статичный пользовательский интерфейс может быть построен целиком с помощью Qt Designer, но его динамическая составляющая и реализация логики приложения (например, обработка событий и сигналов) должны быть реализованы уже непосредственно в исходных кодах.