Пользовательский интерфейс по-норвежски, или пишем на QT. Путь и имя файла в Qt

Добрый день, уважаемые читатели! Совсем недавно я завершил разработку одного своего приложения на Qt, и мне захотелось создать профессиональную программу установки, чтобы всё было «как у взрослых». Как оказалось, сделать это непросто, учитывая, что на официальном сайте инструментария информации по развёртыванию почти нет. В данной статье рассмотрены некоторые этапы подготовки программ на Qt версии 5.2 или выше для распространения на компьютеры других пользователей. Итак, вот план руководства:
  1. Подготовка проекта Qt к развёртыванию
  2. Компоновка дистрибутива программы
  3. Подписание кода и создание установщика
Не будем терять времени и приступим к работе.

1. Подготовка проекта Qt к развёртыванию

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

HelloWorld.pro

QT += core gui widgets TARGET = HelloWorld TEMPLATE = app SOURCES += main.cpp


main.cpp

#include #include int main(int argc, char *argv) { QApplication a(argc, argv); QLabel label("Hello, world!"); label.setAlignment(Qt::AlignCenter); label.resize(200, 50); label.show(); return a.exec(); }


Программы выглядят особенно качественно и профессионально, если они несут с собой метаданные о разработчике, версии программного продукта, авторских правах, языке и многом другом. Для примера, обратимся к свойствам файла Photoshop.exe всем известной системы Adobe Photoshop. На рисунке ниже показано окно свойств данного файла:

Добавить подобную информацию можно с помощью файла ресурсов . Файл ресурсов имеет расширение .rc и несёт в себе текстовый код, описывающий используемые в приложении ресурсы. Подобные скрипты используются в проектах Visual Studio, основанных на WinAPI, и содержат различные дескрипторы иконок, строк, идентификаторов и прочего. В проектах Qt всё это имеет мало смысла, однако включение общей информации о программе всё же необходимо. Ниже приведены исходный код файла ресурсов и содержимое файла проекта, который также потребуется изменить:

resources.rc

IDI_ICON1 ICON "icon.ico" #include #define VER_FILEVERSION 1,0,0,0 #define VER_FILEVERSION_STR "1.0.0.0\0" #define VER_PRODUCTVERSION 1,0,0 #define VER_PRODUCTVERSION_STR "1.0.0\0" #define VER_FILEDESCRIPTION_STR "HelloWorld" #define VER_INTERNALNAME_STR "Sensor" #define VER_LEGALCOPYRIGHT_STR "Copyright (C) 2015, MyCompany" #define VER_ORIGINALFILENAME_STR "HelloWorld.exe" #define VER_PRODUCTNAME_STR "Hello World" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION PRODUCTVERSION VER_PRODUCTVERSION BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "FileDescription", VER_FILEDESCRIPTION_STR VALUE "FileVersion", VER_FILEVERSION_STR VALUE "InternalName", VER_INTERNALNAME_STR VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR VALUE "ProductName", VER_PRODUCTNAME_STR VALUE "ProductVersion", VER_PRODUCTVERSION_STR END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END


HelloWorld.pro

QT += core gui widgets TARGET = HelloWorld TEMPLATE = app SOURCES += main.cpp RC_FILE = resources.rc


В данном примере важно не забыть добавить файлы resources.rc и icon.ico в папку с исходными файлами проекта. На рисунке ниже показано окно свойств программы после сборки:

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

HelloWorld.pro

QT += core gui widgets TARGET = HelloWorld TEMPLATE = app SOURCES += main.cpp RC_FILE = resources.rc win32 { CONFIG += embed_manifest_exe QMAKE_LFLAGS_WINDOWS += /MANIFESTUAC:"level="requireAdministrator"" }


Следует отметить, что все указанные выше инструкции будут гарантированно работать только при использовании комплекта сборки Visual Studio. Подробную информацию о файлах ресурсов можно найти на портале MSDN в разделе .

2. Компоновка дистрибутива программы

Создание дистрибутива приложения с учётом всех его файлов, которые должны устанавливаться на компьютерах пользователей, вероятно, является самым сложным этапом развёртывания. Требуется тщательно проанализировать исполняемый файл программы на наличие зависимостей, позаботиться о файлах переводов, не забыть про ресурсы приложения. Решить часть этих проблем поможет утилита windeployqt.exe , которая поставляется вместе с комплектом сборки. Данный инструмент работает в командной строке и поддерживает некоторые параметры конфигурации. На рисунке ниже показано окно командной строки с запущенной утилитой:

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

Параметр Описание
-?, -h, --help Вывод справки
-v, --version Вывод информации о версии
--dir <каталог > Использовать указанный каталог вместо каталога файлов
--libdir <путь > Каталог, в который будут скопированы библиотеки
--debug Использовать отладочные версии файлов
--release Использовать файлы для выпуска
--release-with-debug-info Использовать файлы для выпуска с отладочной информацией
--force Заменить уже существующие файлы
--dry-run Провести работу с целью проверки
--no-plugins Пропустить копирование плагинов
--no-libraries Пропустить копирование библиотек
--qmldir <каталог > Сканировать импорт QML, начиная с указанного каталога
--no-quick-import Пропустить Qt Quick
--no-translations Пропустить копирование файлов перевода
--no-system-d3d-compiler Пропустить копирование компилятора Direct3D
--compiler-runtime Копировать зависимости компилятора
--no-compiler-runtime Пропустить зависимости компилятора
--webkit2 Копировать файлы WebKit2
--no-webkit2 Пропустить WebKit2
--json Печатать вывод в формате JSON
--angle Копировать файлы ANGLE
--no-angle Пропустить ANGLE
--list <режим > Печатать только имена копируемых файлов. Режимы: source , target , relative , mapping
--verbose <уровень > Уровень отладки
-<имя библиотеки > Добавить указанную библиотеку
--no-<имя библиотеки > Не добавлять указанную библиотеку

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

Следует отметить, что перед использованием windeployqt.exe необходимо добавить путь к этому файлу в переменную среды PATH, в противном случае данный инструмент работать не будет.

3. Подписание кода и создание установщика

После компоновки дистрибутива программы попробуем запустить приложение от имени администратора. На рисунке ниже показано сообщение системной службы User Account Control (UAC) с предупреждением о запуске приложения неизвестного издателя:

Данное предупреждение отпугивает пользователей и создаёт приложению плохую репутацию. Чтобы исправить ситуацию, нужно подписать файлы программы с помощью специального сертификата. Разработчики, подписывая свои программы, как бы дают дополнительные гарантии надёжности приложений с точки зрения информационной безопасности. Разработчики программного обеспечения с открытым исходным кодом могут получить сертификаты для своих проектов бесплатно, к примеру, на сайте Certum . Для использования сертификата понадобиться специальная программа, которая подпишет файлы приложения. Для этого можно использовать удобный инструмент DigiCert Certificate Utility . На рисунке ниже показано окно данной программы со списком сертификатов для подписания кода:

После использования данной утилиты стоить снова попробовать запустить приложение от имени администратора. Ниже показано сообщение UAC с отображением информации об издателе программы:

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

Сложно дать какие-либо рекомендации по поводу выбора той или иной системы. Разработчики ПО с открытым исходным кодом, вероятно, выберут бесплатные инструменты, в то же время платные продукты часто используются коммерческими компаниями.

Заключение

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

Программирование с Qt

Часть 1. Введение. Инструменты разработчика и объектная модель

Серия контента:

1. Введение

Существуют версии Qt для unix-подобных операционных систем с X Window System (например, X.Org (EN), Mac OS X и ОС Windows). Также Qt Software портирует свой продукт на мобильные платформы: Embedded Linux (EN), S60 (EN) и Windows CE. Qt предоставляет большие возможности кросс-платформенной разработки самых разных программ, не обязательно с графическим интерфейсом. На нем, в частности, основана популярная среда рабочего стола KDE (EN).

Инструментарий разбит на модули , каждый из которых размещается в отдельной библиотеке. Базовые классы находятся в QtCore , компоненты графических интерфейсов – в QtGui , классы для работы с сетью – в QtNetwork и т.д. Таким образом, можно собирать программы даже для платформ, где нет X11 или другой совместимой графической подсистемы.

2. Установка Qt

Нам потребуется установить среду разработки Qt. Программное обеспечение распространяется на условиях свободной лицензии GPL 3.0 или LGPL 2.1. Его можно получить по адресу http://www.qtsoftware.com/downloads (EN).

2.1. Базовые библиотеки и инструменты

В репозиториях популярных дистрибутивов GNU/Linux уже есть готовые пакеты со средой разработки Qt (например, в Debian, Fedora, Gentoo, Mandriva, Ubuntu). Тем не менее, пользователь может собрать и установить инструментарий из исходных текстов.

Для систем, использующих X11, необходимо загрузить файл qt-x11-opensource-src-4.x.y.tar.gz , где 4.x.y – последняя доступная версия из стабильных. Мы будем устанавливать версию 4.5.0.

В директории с файлом qt-x11-opensource-src-4.5.0.tar.gz выполните следующие команды:

tar xvfz qt-x11-opensource-src-4.5.0.tar.gz cd qt-x11-opensource-src-4.5.0

Прежде чем собирать Qt, запустите скрипт configure . Полный набор его опций выдается по команде./configure -help , но обычно можно использовать типовые настройки.

Параметр -prefix задает каталог для установки (по умолчанию используется /usr/local/Trolltech/Qt-4.5.0). Также имеются ключи для инсталляции различных компонентов (исполняемых файлов, библиотек, документации, и т.д.) в разные директории.

При запуске скрипт требует подтвердить согласие пользователя с условиями лицензии GPL / LGPL. После выполнения

./configure

можно запустить сборку и установку при помощи команд:

make & make install

Имейте в виду, что компиляция занимает много времени, а для установки Qt могут потребоваться права суперпользователя (файлы записываются в /usr/local/).

Если в дальнейшем вам понадобится в той же директории заново сконфигурировать и пересобрать Qt, удалите все следы предыдущей конфигурации при помощи make confclean , прежде чем снова запускать./configure .

Путь к исполняемым файлам Qt нужно добавить в переменную окружения PATH. В оболочках bash, ksh, zsh и sh это можно сделать, дописав в файл ~/.profile следующие строки:

PATH=/usr/local/Trolltech/Qt-4.5.0/bin:$PATH export PATH

В csh и tcsh нужно дописать в ~/.login строку:

setenv PATH /usr/local/Trolltech/Qt-4.5.0/bin:$PATH

Если вы используете другую оболочку, то обратитесь к соответствующим разделам документации.

Кроме того, необходимо добавить строку /usr/local/Trolltech/Qt-4.5.0/lib в переменную LD_LIBRARY_PATH , если компилятор не поддерживает RPATH. Мы используем GNU/Linux и GCC (EN), поэтому пропускаем этот шаг.

Затем с помощью утилиты qtdemo запустите демонстрационные приложения для проверки работоспособности установленного инструментария.

2.2. SDK

Недавно появилась кросс-платформенная среда разработки Qt Creator. На сайте Qt Software можно найти полный SDK, включающий IDE (помимо библиотек и основных средств разработчика). Загрузите бинарный файл qt-sdk-linux-x86-opensource-xxx.bin и запустите мастер установки:

chmod +x ./qt-sdk-linux-x86-opensource-2009.01.bin ./qt-sdk-linux-x86-opensource-2009.01.bin

Если не собираетесь устанавливать SDK в домашнюю директорию, то запускайте инсталлятор с правами суперпользователя.


3. Инструменты разработчика

В состав Qt включены инструменты разработчика с графическим или консольным интерфейсом. В их числе:

  • assistant – графическое средство для просмотра гипертекстовой документации по инструментарию и библиотекам Qt.
  • designer – графическое средство для создания и сборки пользовательских интерфейсов на основе компонентов Qt.
  • qmake – кросс-платформенный генератор Makefile.
  • moc – компилятор метаобъектов (обработчик расширений Qt для C++).
  • uic – компилятор пользовательских интерфейсов из файлов.ui, созданных в Qt Designer.
  • rcc – компилятор ресурсов из файлов.qrc.
  • qtconfig – графическое средство установки пользовательских настроек для приложений Qt.
  • qtdemo – запуск примеров и демонстрационных программ.
  • qt3to4 – средство переноса проектов с Qt 3 на Qt 4.
  • linguist – средство для локализации приложений.
  • pixeltool – экранная лупа.


3.1. qmake

Утилита qmake используется для автоматического генерирования Makefile на различных платформах.

В целом qmake ориентируется на Qt. Если вас интересуют кросс-платформенные системы сборки более широкого назначения, то можете обратиться к CMake, которая также поддерживает Qt.

Новичкам стоит остановиться на qmake.

Полную документацию по этой утилите вы можете найти в Qt Assistant. Также с Qt поставляются страницы руководства, в том числе qmake(1) (наберите в командной строке man qmake). Здесь мы приведем основные указания, которые помогут вам собирать код примеров статьи, а также свои простые проекты.

В качестве примера создадим директорию myproject и добавим туда файлы hello.h, hello.cpp и main.cpp . В hello.h опишем прототип функции hello():

Листинг 1.1. Объявления функций программы «Hello, World!»
// hello.h void hello();

Реализацию hello() поместим в hello.cpp:

Листинг 1.2. Реализации функций программы «Hello, World!»
// hello.cpp #include #include "hello.h" void hello() { qDebug() << "Hello, World!"; }

Здесь qDebug() используется для вывода отладочной информации. Ее можно убрать, объявив при компиляции символ QT_NO_DEBUG_OUTPUT . Также имеется функция qWarning() , выдающая предупреждения, и qFatal() , завершающая работу приложения после вывода сообщения о критической ошибке в STDERR (то же самое, но без завершения работы, делает qCritical()).

В заголовочном файле содержатся объявления, добавляющие для qDebug(), qWarning() и qCritical() более удобный синтаксис оператора << . При этом между аргументами (как в случае qDebug() << a << b << c;) автоматически расставляются пробелы, поддерживается вывод многих типов C++ и Qt, а в конце автоматически добавляется перевод строки.

Код основного приложения (здесь мы следуем соглашению, по которому main() помещается в файл main.cpp):

Листинг 1.3. Функция main() программы «Hello, World!»
// main.cpp #include "hello.h" int main() { hello(); return 0; }

Чтобы создать файл проекта, запустите

После этого должен появиться файл myproject.pro примерно такого содержания:

#################################### # Automatically generated by qmake #################################### TEMPLATE = app TARGET = DEPENDPATH += . INCLUDEPATH += . # Input HEADERS += hello.h SOURCES += hello.cpp main.cpp

Оператор = используется для присвоения значений переменным, += добавляет новую опцию к переменной, -= удаляет указанную опцию.

TEMPLATE = app обозначает, что мы собираем приложение; для библиотеки используется TEMPLATE = lib .

TARGET – имя целевого файла (укажите TARGET = foobar , чтобы получить исполняемый файл foobar).

DEPENDPATH – директории для поиска при разрешении зависимостей.

INCLUDEPATH – директории с заголовочными файлами.

После запуска

на основе myproject.pro в GNU/Linux будет создан обычный Makefile:

####### Compile hello.o: hello.cpp hello.h $(CXX) -c $(CXXFLAGS) $ (INCPATH) -o hello.o hello.cpp main.o: main.cpp hello.h $(CXX) -c $(CXXFLAGS) $ (INCPATH) -o main.o main.cpp ####### Install install: FORCE uninstall: FORCE FORCE:

Опции qmake влияют на содержимое Makefile. Например, qmake -Wall добавит к флагам компилятора -Wall – вывод всех предупреждений.

По команде make мы получим исполняемый файл myproject , который выводит на экран строку «Hello, World!».

Эта схема может показаться слишком сложной, но в реальных проектах qmake берет на себя большую часть работы по сборке (например, запускает компилятор метаобъектов).

3.2. Qt Creator

Описанных выше инструментов достаточно для разработки приложений. Вы можете использовать любимый текстовый редактор, например GNU Emacs или Vim. С Qt работают также традиционные IDE, такие как KDevelop.

Однако не так давно Qt Software выпустила свою кросс-платформенную IDE Qt Creator. В неё встроены все инструменты разработчика, имеется редактор с подсветкой и дополнением кода, отладчик (графический интерфейс для gdb), а также реализована поддержка Perforce, SVN и Git.

При работе в Qt Creator используется несколько режимов, которым соответствуют вкладки на панели слева. Для быстрого переключения между режимами можно использовать комбинации клавиш Ctrl+1, Ctrl+2 , и т.д. Основному режиму редактирования соответствует Ctrl+2 .


Для навигации в редакторе применяется комбинация клавиш Ctrl+K . После ее нажатия нужно указать один из префиксов:

Таблица 1. Префиксы для навигации в Qt Creator

После префикса нажмите пробел и введите соответствующую информацию. Например, для перехода на строку 93 текущего файла нужно напечатать " l 93 " (то же самое можно сделать при помощи Ctrl+L), для перехода к документации по теме qobject_cast – "? qobject_cast" и т.д.

В нижней части окна при этом отображается поле с автоматическим дополнением.

Рисунок 5. Поле для навигации в Qt Creator

Таблица 2. Комбинации клавиш для редактора Qt Creator

Ctrl+[ Перейти к началу блока
Ctrl+] Перейти к концу блока
Ctrl+U Выделить блок
Ctrl+Shift+U Снять выделение блока
Ctrl+I Выровнять блок
Ctrl+< Свернуть блок
Ctrl+> Развернуть блок
Ctrl+/ Закомментировать блок
Ctrl+Shift+ Переместить строку вверх
Ctrl+Shift+↓ Переместить строку вниз
hift+Del SУдалить строку

Во встроенном редакторе реализовано «умное» дополнение кода, вызываемое комбинацией клавиш Ctrl+<Пробел> . База символов составляется на основе заголовочных файлов проекта из INCLUDEPATH .

Для чтения документации в IDE предусмотрен отдельный режим справки . Чтобы получить контекстную помощь по классу или методу, просто передвиньте текстовый курсор к имени и нажмите F1 . Также полезна клавиша F2 , перемещающая к определению в заголовочных файлах.

Чтобы переключиться из режима справки или отладки в основной режим редактирования, нажмите Esc . В режиме редактирования Esc переводит фокус из дополнительных окон (например, вывода компиляции или контекстной справки) на редактор. Если нажать Esc еще раз, то дополнительные окна закрываются.

Как и qmake , Qt Creator использует файлы в формате.pro , поэтому в IDE легко импортируются старые проекты, созданные вручную. Также доступен мастер, при помощи которого можно создать заготовку нового проекта.

Сейчас Qt Creator активно разрабатывается, но если вам нужна классическая IDE для Qt, работающая на различных платформах, то это лучший вариант.

4. Стиль Qt

В Qt используется CamelCasing : имена классов выглядят как MyClassName , а имена методов – как myMethodName .

При этом имена всех классов Qt начинаются с Q , например QObject, QList или QFont .

Большинству классов соответствуют заголовочные файлы с тем же именем (без расширения.h), т.е. нужно использовать:

#include #include #include

Поэтому в дальнейшем мы не будем отдельно оговаривать, где объявлен тот или иной класс.

Методы для получения и установки свойств (getter и setter ) именуются следующим образом: свойство fooBar можно получить при помощи метода fooBar() и установить при помощи setFooBar() .

T fooBar() const; void setFooBar (T val);

При разработке собственных приложений на Qt стоит придерживаться этого стиля.

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

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

  • древовидные иерархии объектов;
  • аналог dynamic_cast для библиотеки, не использующий RTTI;
  • взаимодействие объектов через сигналы и слоты ;
  • свойства объектов.

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

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

Инструментарий спроектирован так, что для QObject и всех его потомков конструктор копирования и оператор присваивания недоступны – они объявлены в разделе private через макрос Q_DISABLE_COPY() :

class FooBar: public QObject { private: Q_DISABLE_COPY(FooBar) };

Будьте внимательны и не используйте конструкцию

Foo bar = Foo (baz);

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


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

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

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

В сложных ситуациях Qt использует свой компилятор метаобъектов moc , преобразующий код с расширениями в стандартный код C++. Для обозначения того, что класс использует метаобъектные возможности (и, соответственно, должен обрабатываться moc), в разделе private нужно указать макрос Q_OBJECT .

Если вы встречаете странные ошибки компиляции, сообщающие, что у класса не определен конструктор, либо у него нет таблицы виртуальных функций (vtbl), скорее всего вы забыли код, генерируемый moc. Обычно это происходит, если не указан макрос Q_OBJECT .

Во избежание ошибок Q_OBJECT лучше использовать во всех классах, наследуемых от QObject (косвенно либо напрямую).

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

В числе прочих, метаобъектный код добавляет метод

virtual const QMetaObject* QObject::metaObject() const;

который возвращает указатель на метаобъект объекта.

На системе метаобъектов основаны сигналы, слоты и свойства.

При наследовании от QObject помните об ограничениях, налагаемых moc:

  1. При множественном наследовании потомком QObject должен быть первый и только первый наследуемый класс: class MyClass: public QObject, public Foo, public Bar { // ... };
  2. Виртуальное наследование с QObject не поддерживается.

5.2. qobject_cast

Для динамического приведения QObject используется функция

T qobject_cast (QObject *object);

Она работает как стандартная операция dynamic_cast в C++, но не требует поддержки со стороны системы динамической идентификации типов (RTTI).

Пусть у нас имеется класс MyClass1 , наследующий от QObject и MyClass2 , наследующий от MyClass1:

#include class MyClass1: public QObject { Q_OBJECT public: MyClass1(); // ... }; class MyClass2: public MyClass1 { Q_OBJECT public: MyClass2(); // ... };

Динамическое приведение иллюстрирует следующий код:

QObject *a = new MyClass2; MyClass1 *b = qobject_cast(a); MyClass2 *c = qobject_cast(b);

Эти операции сработают корректно на стадии выполнения.

Как и в случае с dynamic_cast , результат приведения можно проверить:

if (b = qobject_cast(a)) { // ... }

Система метаобъектов позволяет также проверить, наследует ли a класс MyClass1:

if (a->inherits("MyClass1")) { b = static_cast(a); // ... }

Однако предпочтителен предыдущий вариант с qobject_cast .

5.3. Деревья объектов

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

Пусть у нас имеются классы ClassA и ClassB:

Листинг 2.1. Объявление MyClass для программы, демонстрирующей порядок создания и удаления объектов
// myclass.h #include class MyClass: public QObject { public: MyClass (char id, QObject *parent = 0); ~MyClass(); private: char id_; };
Листинг 2.2. Определение методов MyClass для программы, демонстрирующей порядок создания и удаления объектов
// myclass.cpp #include #include #include "myclass.h" MyClass::MyClass (char id, QObject *parent) : QObject(parent), id_(id) { qDebug() << "+" >> id_; } MyClass::~MyClass() { qDebug() << "-" >> id_; }

Здесь родительский объект устанавливается в конструкторе QObject:

QObject::QObject (QObject *parent = 0);

Его можно установить в последующем при помощи метода setParent() и получить при помощи parent() :

void QObject::setParent (QObject *parent);
QObject* QObject::parent() const;

Если создать в стеке по одному из объектов A и B, то сначала будет создан A, потом B. В соответствии со стандартом C++, удаление происходит в обратном порядке – сначала B, затем A:

Листинг 2.3. Создание экземпляров MyClass в стеке
// main.cpp #include "myclass.h" int main() { MyClass a ("A"); MyClass b ("B"); return 0; }

Если создать B в куче и назначить его дочерним объектом для A, то вместе с A автоматически удалится B:

Листинг 2.4. Создание экземпляра A класса MyClass в стеке, а экземпляра B – в куче, как дочернего для A
// main.cpp #include "myclass.h" int main() { MyClass a ("A"); MyClass *b = new MyClass ("B", &a); return 0; }

Аналогично для более сложных деревьев:

Рисунок 8. Пример многоуровневого дерева объектов
Листинг 2.5. Многоуровневое дерево объектов с корнем в стеке
// main.cpp #include "myclass.h" int main() { MyClass a ("A"); MyClass *b = new MyClass ("B", &a); MyClass *c = new MyClass ("C", &a); MyClass *d = new MyClass ("D", c); MyClass *e = new MyClass ("E", c); return 0; }

После удаления A удалится всё дерево.

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

Если объект и его дочерние объекты созданы в стеке, то подобный порядок удаления может привести к ошибкам.

int main() { MyClass b ("B"); MyClass a ("A"); b.setParent(&a); // ... return 0; }

Здесь при выходе из области действия сначала будет удален объект A, так как он был создан последним. При этом Qt удалит и его дочерний объект B. Но потом будет сделана попытка удаления B, что приведет к ошибке.

В другом случае проблем не будет, потому что при вызове деструктора QObject объект удаляет себя из списка дочерних объектов родительского объекта:

int main() { MyClass a ("A"); MyClass b ("B", &a); // ... return 0; }

Вообще говоря, дочерние объекты должны размещаться в куче.

У каждого QObject есть свойство objectName , для доступа к которому используются методы

QString objectName() const; void setObjectName (const QString& name);

По умолчанию objectName – пустая строка. Через это свойство объектам в дереве можно присвоить имена для последующего поиска.

const QList& children() const;

– возвращает список дочерних объектов.

T findChild (const QString& name = QString()) const;

– возвращает дочерний объект с именем name , который можно привести к типу T, либо 0, если такой объект не найден. Без аргумента name возвращает все дочерние объекты. Поиск осуществляется рекурсивно.

QList QObject::findChildren (const QString& name = QString()) const;

– возвращает все дочерние объекты с именем name , которые можно привести к типу T , либо пустой список, если таких объектов не найдено. Без аргумента name возвращает все дочерние объекты. Поиск осуществляется рекурсивно.

QList QObject::findChildren (const QRegExp& regExp) const;

– аналогично, но с поиском по регулярному выражению regExp .

void dumpObjectTree();

– выводит отладочную информацию о дереве объектов с данным корнем.

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

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

В Qt вводится концепция сигналов и слотов.

Сигнал отправляется при вызове соответствующего ему метода. Программисту при этом нужно только указать прототип метода в разделе signals .

Слот является методом, исполняемым при получении сигнала. Слоты могут объявляться в разделе pulic slots, protected slots или private slots . При этом уровень защиты влияет лишь на возможность вызова слотов в качестве обычных методов, но не на возможность подключения сигналов к слотам.

Модель сигналов и слотов отличается от модели событий и обработчиков тем, что слот может подключаться к любому числу сигналов, а сигнал может подключаться к любому числу слотов. При отправке сигнала будут вызваны все подключенные к нему слоты (порядок вызовов не определен).

5.4.1. Объявление сигналов и слотов, отправка сигналов

В качестве типичного примера слота рассмотрим метод получения свойства (getter ). Методу установки свойства (setter ) при этом будет соответствовать сигнал.

Листинг 3.1. Класс MyClass со слотом void setValue (int x) и сигналом void valueChanged (int x)
// myclass.h #include class MyClass: public QObject { Q_OBJECT public: MyClass(int x, QObject *parent = 0); int value() const; public slots: void setValue (int x); signals: void valueChanged (int x); private: int x_; };

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

Листинг 3.2. Реализация методов класса MyClass со слотом void setValue (int x) и сигналом void valueChanged (int x)
// myclass.cpp #include #include "myclass.h" MyClass::MyClass (int x, QObject *parent) : QObject(parent) { setValue (x); } int MyClass::value() const { return x_; } void MyClass::setValue (int x) { if (x_ == x) return; x_ = x; emit valueChanged (x); }

Ключевое слово emit отвечает за отправку сигнала.

Для сигнала задается только прототип, причем сигнал не может возвращать значение (т.е., указывается void). За реализацию отвечает компилятор метаобъектов, он же преобразует расширенный синтаксис с ключевыми словами signals, slots, emit в стандартный код C++.

На самом деле, ключевые слова можно заменить на макросы Q_SIGNALS, Q_SLOTS и Q_EMIT . Это полезно, если вы используете сторонние библиотеки, в которых уже используются слова signals, slots или emit .

Обработка ключевых слов отключается флагом no_keywords . В файл проекта qmake (.pro) добавьте

CONFIG += no_keywords

Вы можете посмотреть на результат работы компилятора метаобъектов в файле moc_slots.cpp , который генерируется на основе slots.h и компилируется вместе с остальными.cpp .

5.4.2. Подключение сигнала к слоту

Листинг 3.3. Подключение сигнала void MyClass::valueChanged (int x) к слоту void MyClass::setValue (int x)
// main.cpp #include #include #include "myclass.h" int main() { MyClass a(1); MyClass b(2); QObject::connect (&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int))); a.setValue (3); qDebug() << "a:" << a.value(); // 3 qDebug() << "b:" << b.value(); // 3 return 0; }

Здесь при помощи QObject::connect сигнал объекта a соединяется со слотом объекта b (передаются указатели на QObject). Макросы SIGNAL и SLOT формируют строковые сигнатуры методов. Их аргументы должны содержать прототипы без указания имен переменных, т.е. SIGNAL(valueChanged(int x)) – недопустимый вариант.

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

Другой вариант вызова QObject::connect:

b.connect (&a, SIGNAL(valueChanged(int)), SLOT(setValue(int)));

Таким образом, здесь вызов MyClass::setValue для a задействует MyClass::setValue для b .

Обратите внимание на строку if (x_ == x) return; . Она нужна, чтобы избежать проблем при циклических соединениях. Например, следующий код сработает:

Листинг 3.4. Циклическое соединение сигналов void MyClass::valueChanged (int x) со слотами void MyClass::setValue (int x)
// main.cpp #include #include #include "slots.h" int main() { MyClass a(0); MyClass b(1); MyClass c(2); QObject::connect (&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int))); QObject::connect (&b, SIGNAL(valueChanged(int)), &c, SLOT(setValue(int))); QObject::connect (&c, SIGNAL(valueChanged(int)), &a, SLOT(setValue(int))); a.setValue (3); qDebug() << "a:" << a.value(); // 3 qDebug() << "b:" << b.value(); // 3 qDebug() << "c:" << c.value(); // 3 return 0; }

QObject::connect возвращает true , если соединение успешно установлено, и false в противном случае – например, когда сигнал или слот не обнаружен, либо их сигнатуры несовместимы.

Если добавить при помощи QObject::connect одинаковые соединения, то слот будет вызываться несколько раз.

5.4.3. Отключение

Для отключения сигнала от слота используется QObject::disconnect:

QObject::disconnect (&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));

При успешном отключении возвращается true .

Если вместо сигнала (SIGNAL(...)) указать 0, то данный получатель сигнала (b) и слот отключаются от любого сигнала:

QObject::disconnect (&a, 0, &b, SLOT(setValue(int)));

Если 0 указать вместо получателя сигнала (b) и слота (SLOT(...)) , то отключено будет всё, что подключено к данному сигналу:

QObject::disconnect (&a, SIGNAL(valueChanged(int)), 0, 0);

Если 0 указать вместо слота (SLOT(...)) , то отключено будет всё, что подключено к данному получателю сигнала (b):

QObject::disconnect (&a, SIGNAL(valueChanged(int)), &b, 0);

Получаем следующие варианты вызова QObject::disconnect:

// Отключить всё от сигналов, отправляемых объектом a: QObject::disconnect (&a, 0, 0, 0); // То же самое, но в виде метода a: a.disconnect(); // Отключить всё от сигнала SIGNAL(...), отправляемого объектом a: QObject::disconnect (&a, SIGNAL(...), 0, 0); // То же самое,но в виде метода a: a.disconnect (SIGNAL(...)); // Отключить данного получателя сигналов b: QObject::disconnect (&a, 0, &b, 0); // То же самое,но в виде метода a: a.disconnect (&b);

При удалении одного из объектов соединения Qt автоматически удаляет само соединение.

5.4.4. Ограничения

У компилятора метаобъектов имеется ряд ограничений, которые распространяются и на работу с сигналами и слотами.

  1. moc не обрабатывает шаблоны и макросы, поэтому шаблоны классов не могут определять сигналы и слоты, а при объявлении сигналов и слотов нельзя использовать макросы (в том числе при указании параметров).

    Макросы нельзя использовать в любых участках кода, которые должны обрабатываться moc . В частности, через них нельзя указать базовый класс.

    Однако некоторые возможности препроцессора использовать можно. Доступны простые условные конструкции (с директивами #if, #ifdef, #ifndef, #else, #elif, #endif , а также специальным оператором defined). Для создания объявлений у moc имеется опция командной строки -D . Утилита qmake передает moc все объявления, перечисленные в параметре проекта DEFINES .

    Например, moc правильно обработает

    #if 0 // Игнорируемый блок #endif
    Блок #ifdef FOO // ... #endif

    также будет обработан, только если вызвать moc -DFOO , либо если до него имеется строка #define FOO .

  2. Типы должны быть указаны полностью, так как QObject::connect() сравнивает их буквально. В частности, если внутри класса Foo определяется перечисление Bar , то в аргументах сигнала нужно указывать Foo::Bar:

    class Foo: public QObject { Q_OBJECT enum Bar { a, b, c }; signals: void somethingHappened (Foo::Bar x); };
  3. В качестве параметров сигналов и слотов нельзя использовать указатели на функции. Например,

    int (*fun)(int)

    не является допустимым аргументом. Можно использовать typedef:

    typedef int (*fun)(int);

    Обычно вместо указателей лучше применять наследование и виртуальные функции.

  4. Вложенные классы не могут содержать сигналы и слоты.
  5. Сигналы и слоты, возвращающие ссылки, обрабатываются таким образом, как если бы они возвращали void .
  6. В разделах signals и slots могут объявляться только сигналы и слоты.

5.5. Свойства

Класс, наследующий QObject , может содержать объявление свойства при помощи макроса Q_PROPERTY() :

Q_PROPERTY(type name

READ getFunction

Обязательные параметры макроса:

  • type – тип свойства;
  • name – имя свойства;
  • getFunction – const-метод для считывания значения; возвращаемый тип должен быть type, type* либо type& .

Не обязательные параметры макроса:

  • setFunction – метод для установки значения свойства, должен возвращать void и принимать только один аргумент типа type, type* , либо type& ;
  • resetFunction – метод для установки значения свойства по умолчанию, зависящего от контекста, должен не иметь аргументов и возвращать void ;

Методы могут быть виртуальными либо унаследованными от базового класса. При множественном наследовании они должны принадлежать первому классу в списке.

Для не обязательных атрибутов DESIGNABLE, SCRIPTABLE, STORED, USER допускается указание булевых значений:

  • DESIGNABLE – показывать ли свойство в Qt Designer и подобных графических программах. По умолчанию true , также можно указать булев метод.
  • SCRIPTABLE – должно ли свойство быть видимым скриптовому движку. По умолчанию true , также можно указать булев метод.
  • STORED – должно ли свойство сохраняться при сохранении состояния объекта либо оно вычисляется через другие свойства. По умолчанию true .
  • USER - редактируется ли свойство пользователем. Обычно у классов, соответствующих элементам управления, бывает одно такое свойство. По умолчанию false .

Например, QWidget объявляет, в числе прочих, следующие свойства:

Q_PROPERTY (QSize minimumSize READ minimumSize WRITE setMinimumSize) Q_PROPERTY(int minimumWidth READ minimumWidth WRITE setMinimumWidth STORED false DESIGNABLE false) Q_PROPERTY(int minimumHeight READ minimumHeight WRITE setMinimumHeight STORED false DESIGNABLE false)

Свойство minimumSize имеет тип QSize и может быть получено при помощи QSize minimumSize() const и установлено при помощи void setMinimumSize (const QSize&). minimumWidth и minimumHeight вычисляются через minimumSize , поэтому для них указано STORED false .

Пример свойства с атрибутом USER – text в QLineEdit:

Q_PROPERTY(QString text READ text WRITE setText USER true)

Для считывания и записи свойства используются методы:

QVariant QObject::property (const char * name) const; bool QObject::setProperty (const char * name, const QVariant& value);

property() возвращает значение свойства либо неправильный вариант QVariant , если такого свойства нет.

setProperty() возвращает true , если у объекта есть указанное свойство с типом, совместимым с переданным значением. В противном случае возвращается false .

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

QList QObject::dynamicPropertyNames() const;

Рассмотрим пример использования свойств. Пусть класс MyClass имеет строковое свойство text (типа QString):

Листинг 4.1. Объявление класса MyClass со свойством text
// myclass.h #include #include class MyClass: public QObject { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText) public: MyClass(QString text, QObject *parent = 0); QString text() const; void setText(const QString& text); private: QString text_; };
Листинг 4.2. Определение методов класса MyClass со свойством text
// myclass.cpp #include #include #include "myclass.h" MyClass::MyClass(QString text, QObject *parent) : QObject(parent) { setText(text); } QString MyClass::text() const { return text_; } void MyClass::setText(const QString& text) { text_ = text; }

Работа со свойством:

Листинг 4.3. Работа со свойством text объекта MyClass
// main.cpp #include #include #include #include #include #include #include "myclass.h" int main() { MyClass str("foo"); qDebug() << "text:" << str.text(); // Через метод: str.setText("bar"); qDebug() << "text:" << str.text(); // Через setProperty() / property(): str.setProperty("text", QVariant("baz")); QVariant prop = str.property("text"); qDebug() << "text:" << prop.toString(); // Добавление динамического свойства: str.setProperty("foo", QVariant("bob")); str.setProperty("bar", QVariant("slack")); QList d_props = str.dynamicPropertyNames(); QListIterator iter (d_props); // (Контейнеры и итераторы мы еще рассмотрим отдельно) while (iter.hasNext()) { const char* d_prop_name = iter.next().data(); QVariant d_prop = str.property(d_prop_name); qDebug() << "" << d_prop_name << ":" << d_prop.toString(); } return 0; }

Программа должна вывести на экран следующее:

text: "foo" text: "bar" text: "baz" foo: "bob" bar: "slack"

Разумеется, безопаснее и быстрее вызывать методы конкретного класса для считывания и записи свойств. property() и setProperty() нужны в том случае, когда о классе ничего не известно кроме имен и типов свойств.

Если для класса не известен даже перечень свойств и методов, можно использовать метаобъект.

5.6. Работа с метаобъектами

Метаобъект возвращается методом

QObject::metaObject()

С его помощью можно динамически получить информацию о классе, как, например, в Java Reflecion API (EN).

5.6.1. Основная информация

Имя класса возвращает

const char * QMetaObject::className() const;

Указатель на метаобъект базового класса –

const QMetaObject* superClass() const;

5.6.2. Методы

Через систему метаобъектов доступны только те методы и конструкторы, перед объявлениями которых указан макрос Q_INVOKABLE:

class MyClass: public QObject { Q_OBJECT public: Q_INVOKABLE MyClass(); // виден системе метаобъектов Q_INVOKABLE void foo(); // виден void foo(); // не виден };

Для доступа к методам (в том числе сигналам и слотам) используйте

int QMetaObject::methodCount() const; int QMetaObject::methodOffset() const; QMetaMethod QMetaObject::method (int index) const;

Методы и свойства класса проиндексированы. Доступ к методу по индексу осуществляется через QMetaObject::method() .

Общее число методов, с учетом наследованных, возвращает QMetaObject::methodCount() . Смещение методов класса возвращается QMetaObject::methodOffset() , оно показывает, с какого индекса начинаются методы данного класса. Смещение увеличивается при наследовании и показывает число методов базовых классов.

Пример прохода по методам:

const QMetaObject* m_obj = obj.metaObject(); for (int i = m_obj->methodOffset(); i < m_obj->methodCount(); i++) { qDebug() << m_obj->method(i).signature(); }

Если бы мы начали с индекса 0, то получили бы методы всех базовых классов, в том числе QObject:

destroyed(QObject*) destroyed() deleteLater() _q_reregisterTimers(void*) ...

Методы, начинающиеся с _q_ , используются внутри Qt и не являются частью API.

Конструкторы указываются отдельно:

QMetaMethod QMetaObject::constructor (int index) const; int QMetaObject::constructorCount() const;

Например, получим перечень конструкторов QObject:

Листинг 5. Вывод конструкторов QObject через систему метаобъектов
#include #include #include #include int main() { QObject obj; const QMetaObject* m_obj = obj.metaObject(); for (int i = 0; i < m_obj->constructorCount(); i++) { qDebug() << m_obj->constructor(i).signature(); } return 0; }

Результат:

QObject(QObject*) QObject()

Индекс метода, сигнала, слота или конструктора можно получить по его сигнатуре:

int QMetaObject::indexOfConstructor (const char * constructor) const; int QMetaObject::indexOfMethod (const char * method) const; int QMetaObject::indexOfSignal (const char * signal) const; int QMetaObject::indexOfSlot (const char * slot) const;

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

static QByteArray QMetaObject::normalizedSignature (const char * method);

Например,

QMetaObject::normalizedSignature ("int * foo(const QString &, QObject *)")

возвращает " int*foo(QString,QObject*) ".

Аналогично работает

static QByteArray QMetaObject::normalizedType (const char * type);

Это текстовое приведение к каноническому виду, используемое, в частности, при проверке совместимости сигнала и слота.

5.6.3. Свойства

Аналогично можно работать со свойствами.

int QMetaObject::propertyCount() const; int QMetaObject::propertyOffset() const; QMetaProperty QMetaObject::property (int index) const; const QMetaObject* m_obj = obj.metaObject(); for (int i = m_obj->propertyOffset(); i < m_obj->>propertyCount(); i++) { qDebug() << m_obj->property(i).name(); }

(Если просмотреть все свойства, включая наследованные, то вы увидите, по меньшей мере, objectName из QObject .)

Индекс свойства можно получить по его имени:

int QMetaObject::indexOfProperty (const char * name) const;

5.6.4. Перечисления

Перечисления регистрируются в классе при помощи макроса Q_ENUMS() .

Перечисление, значения которого можно комбинировать при помощи побитового ИЛИ, называется флагом и должно регистрироваться при помощи Q_FLAGS() .

QMetaEnum QMetaObject: :enumerator (int index) const; int QMetaObject::enumeratorCount() const; int QMetaObject::enumeratorOffset() const;
Листинг 6.1. Класс MyClass с перечислением Type и флагом Mode
class MyClass { Q_OBJECT Q_ENUMS(Type) Q_FLAGS(Mode) public: enum Type { A, B, C }; enum Mode { Read = 0x1, Write = 0x2, Execute = 0x4 }; // ... };

Флаги используются следующим образом:

int mode = MyClass::Read | MyClass::Write; // ... if (mode & MyClass::Write) // Установлен ли флаг Write? { // ... }

Динамическая работа с перечислениями:

Листинг 6.2. Вывод перечислений и флагов MyClass через систему метаобъектов
MyClass obj; const QMetaObject* m_obj = obj.metaObject(); for (int i = m_obj->enumeratorOffset() ; i < m_obj->enumeratorCount(); i++) { QMetaEnum me = m_obj->enumerator(i); if (me.isValid()) // Есть имя { if (me.isFlag()) // Флаг { qDebug() << "" << me.scope() << "::" << me.name(); } else { qDebug() << me.scope() << "::" << me.name(); } } }

Результат работы:

MyClass:: Type MyClass:: Mode

Индекс перечисления можно получить по его имени:

int QMetaObject::indexOfEnumerator (const char * name) const;

5.6.5. CLASSINFO

При помощи макроса Q_CLASSINFO() к метаобъекту можно добавлять пары имя–значение. Например,

Листинг 7.1. Класс MyClass с CLASSINFO
class MyClass { Q_OBJECT Q_CLASSINFO("author", "Bob Dobbs") Q_CLASSINFO("version", "0.23") // ... };

Эти пары наследуются, и их можно получить из метаобъекта по той же схеме:

QMetaClassInfo QMetaObject:: classInfo (int index) const; int QMetaObject::classInfoCount() const; int QMetaObject::classInfoOffset() const;

Для примера выше:

Листинг 7.2. Вывод CLASSINFO класса MyClass
MyClass obj; const QMetaObject* m_obj = obj.metaObject(); for (int i = m_obj->classInfoOffset(); i < m_obj->classInfoCount(); i++) { QMetaClassInfo mci = m_obj->classInfo(i); qDebug() << mci.name() << ":" << mci.value(); }

Результат:

Индекс CLASSINFO можно получить по его имени:

int QMetaObject::indexOfClassInfo (const char * name) const;

5.6.6. Вызов конструкторов и методов

Передача аргументов осуществляется через объекты QGenericArgument и QGenericReturnArgument . Они создаются макросами Q_ARG и Q_RETURN_ARG .

// константная ссылка для передачи значения: Q_ARG (T, const T& value) // ссылка для возврата значения: Q_RETURN_ARG (T, T& value)

Пример использования:

Q_ARG(QString, "foo") Q_ARG(int, 23) Q_RETURN_ARG(QString, str)

Для создания нового экземпляра класса используется метод метаобъекта newInstance() , которому можно передать до 10 аргументов.

QObject* QMetaObject::newInstance (QGenericArgument val0 = QGenericArgument(0), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) const;

В случае ошибки возвращается 0.

Для вызова метода используется invokeMethod() :

static bool QMetaObject::invokeMethod (QObject* obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument(0), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument());
  • obj – указатель на объект;
  • member – имя метода;
  • type – тип вызова:
    • Qt::DirectConnection – незамедлительно,
    • Qt::QueuedConnection – при начале выполнения QCoreApplication::exec() ,
    • Qt::AutoConnection – синхронно, если объект находится в том же потоке, и асинхронно в противном случае;
  • ret – возвращаемое значение;

При асинхронном вызове значение не может быть вычислено.

Имеются перегруженные версии invokeMethod() . Если вы не укажете тип вызова, то будет использоваться Qt::AutoConnection . Если вы не укажете возвращаемое значение, то оно будет проигнорировано.

Те же возможности предоставляет класс QMetaMethod:

bool QMetaMethod::invoke (QObject* object, Qt::ConnectionType connectionType, QGenericReturnArgument returnValue, QGenericArgument val0 = QGenericArgument(0), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) const;

Точно так же, тип соединения и/или возвращаемое значение можно не указывать.

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

Рассмотрим следующий класс:

Листинг 8.1. Класс MyClass с конструктором и методами, доступными системе метаобъектов
class MyClass: public QObject { Q_OBJECT public: Q_INVOKABLE MyClass (QString text, QObject *parent = 0); Q_INVOKABLE QString text() const; Q_INVOKABLE void setText (const QString& text); private: QString text_; };

Обращение к конструктору и методам:

Листинг 8.2. Вызов конструкторов и методов класса MyClass через систему метаобъектов
MyClass foo ("foo"); const QMetaObject* m_foo = foo.metaObject(); // Создать новый экземпляр: MyClass *bar = qobject_cast (m_foo->newInstance(Q_ARG(QString,"bar"))); if (!bar) { qCritical() << "Can"t invoke constructor!"; } else { bar->setParent(&foo); qDebug() << bar->text(); // "bar" } // Вызвать метод: if (!QMetaObject::invokeMethod (&foo, "setText", Q_ARG(QString,"baz"))) qCritical() << "Can"t invoke method!"; QString val; // Вызвать метод и получить возвращенное значение: if (!QMetaObject::invokeMethod (&foo, "text", Q_RETURN_ARG(QString, val))) qCritical() << "Can"t invoke method!"; qDebug() << val; // "baz"

text() и setText() вызываются таким образом лишь в качестве простого примера работы с QMetaObject::invokeMethod() . Как вы уже знаете, эти два метода должны быть связаны со свойством.

Также обратите внимание, что text() возвращает QString , но не const QString& . Иначе бы система метаобъектов считала, что text() возвращает void .

Заключение

Для эффективной работы с классами на стадии выполнения Qt использует специальную объектную модель, в которой при помощи наследования от QObject и генерирования кода компилятором метаобъектов реализованы:

  • иерархии объектов;
  • специальный аналог dynamic_cast , не зависящий от RTTI;
  • система сигналов и слотов;
  • система свойств объектов;
  • динамическая работа с классами.

В следующей статье мы рассмотрим типы, варианты, ссылки и разделение данных.

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

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

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

С учетом всех плюсов и минусов последний вариант в большинстве случаев является предпочтительным. О нем мы и поговорим.

Реклама

Создание файла с описанием ресурсов

Первым делом создайте файл с описанием тех ресурсов, которые собираетесь использовать. Он имеет следующий вид (назовем его res.qrc):

logo.png

В приведенном примере мы определили один префикс: /images . Его можно считать логическим каталогом ресурсов. Таких префиксов может быть сколько угодно. Например, если в вашем приложении есть звуковые эффекты, то вы можете добавить префикс /sounds . Для создания более глубокой иерархии используйте префиксы вида /some/long/prefix .

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

Имеет смысл явным образом распределять ресурсы по подкаталогам в файловой системе проекта. Например, изображение logo.png поместите в images/ . Тогда запись приобретает вид:

images/logo.png

В этом случае логический путь к файлу logo.png вновь имеет вид: /images/logo.png .

Для краткости можно использовать псевдонимы следующим образом:

long/relative/path/to/logo.png

Файл доступен по логическому пути /myprefix/logo.png .

Затем нужно привязать заполненный qrc -файл к проекту. Для этого добавьте в ваш pro -файл строку вида:

RESOURCES += res.qrc

В примере выше qrc -файл расположен на одном уровне с pro -файлом. Если вы применяете более сложную схему размещения файлов, то воспользуйтесь относительным путем.

Обратите внимание, что в QtCreator предусмотрен довольно удобный GUI-интерфейс для работы с файлами ресурсов. Чтобы создать новый qrc -файл, щелкните в контекстном меню для нужного проекта на пункт Add New... . В появившемся диалоговом окне перейдите в группу Qt и выберите Qt Resource file . После успешного создания файла ресурсов в панели проекта вы увидите новую группу Resources , появившуюся рядом с Headers и Sources . Открыв qrc -файл вы попадете в редактор ресурсов, который вполне интуитивно позволяет выполнить те же самые действия, которые мы выполняли вручную.

Реклама

Использование ресурсов в приложении

Итак, qrc -файл готов и подключен к проекту. Осталось только воспользоваться преимуществами от его использования. И сделать это совсем не сложно:

#include #include int main(int argc, char* argv) { QApplication a(argc, argv); QLabel lbl; QPixmap pix(":/images/logo.png"); lbl.setPixmap(pix); lbl.resize(pix.size()); lbl.show(); return a.exec(); }

Ключевым здесь является способ обращения к изображению (или любому другому файлу) из ресурсов. Путь строится следующим образом: сначала ставится двоеточие: , затем префикс /images , который мы сами выбрали, и наконец путь к файлу (или псевдоним) logo.png . В остальном все то же самое, что и при работе с обычными файлами, предназначенными только для чтения.

Заключение

Использовать файлы ресурсов в Qt-проектах очень просто, поэтому если вам понадобилось добавить изображения, музыку или что-то еще в свое приложение, то теперь вы знаете, что нужно делать.

Задача этого класса состоит в предоставлении информации о свойствах файла, например: имя, размер, время последнего изменения, права доступа и т. д. Объект класса QFileInfo создается передачей в его конструктор пути к файлу, но можно передавать и объекты класса QFile .

Файл или каталог?

Иногда необходимо убедиться, что исследуемый объект является каталогом, а не файлом и наоборот. Для этой цели существуют методы isFile () и isDir ().

В том случае, если объект является файлом, метод isFile () возвращает значение булевого типа true , иначе — false . Если объект является директорией, то метод isDir () возвращает true , иначе — false . Кроме этих методов, класс QFileInfo содержит метод isSymLink (), возвращающий true , если объект является символьной ссылкой (symbolic link или shortcut в ОС Windows).

Путь и имя файла в Qt

Чтобы получить путь к файлу, нужно воспользоваться методом absoluteFilePath (). Для получения относительного пути к файлу следует использовать метод filePath (). Для получения имени файла нужно вызвать метод fileName (), который возвращает имя файла вместе с его расширением. Если нужно только имя файла, то следует вызвать метод baseName (). Для получения расширения используется метод completeSuffix ().

Информация о дате и времени файла в Qt

Иногда нужно узнать время создания файла, время его последнего изменения или чтения. Для этого класс QFileInfo предоставляет методы created (), lastModified () и lastRead () соответственно. Эти методы возвращают объекты класса QDateTime , которые можно преобразовать в строку методом toString (). Например:

//Дата и время создания файла fileInfo.created().toString(); //Дата и время последнего изменения файла fileInfo.lastModified().toString(); //Дата и время последнего чтения файла fileInfo.lastRead().toString();

Получение атрибутов файла в Qt

Атрибуты файла дают информацию о том, какие операции можно проводить с файлом.

Для их получения в классе QFileInfо существуют следующие методы:

isReadable () —возвращает true , если из указанного файла можно читать информацию;

isWriteable () —возвращает true , если в указанный файл можно записывать информацию;

isHidden () — возвращает true , если указанный файл является скрытым;

isExecutable () —возвращает true , если указанный файл можно исполнять. В ОС UNIX это определяется не на основании расширения файла, как привыкли считать программисты в DOS и ОС Windows, а посредством свойств самого файла.

Определение размера файла в Qt

Метод size () класса QFileInf о возвращает размер файла в байтах. Размер файлов редко отображается в байтах, чаще используются специальные буквенные обозначения, сообщающие об его размере. Например, для килобайта — это буква К, для мегабайта — М, для гигабайта — G, а для терабайта — Т. Следующая функция позволяет сопровождать буквенными обозначениями размеры, лежащие даже в терабайтном диапазоне (вполне возможно, что через несколько лет это будет обычный размер файла).

В данной статье, я поведу речь о разработке пользовательских интерфейсов с помощью библиотеки QT, также мы с вами сделаем краткий экскурс в историю компании Trolltech, и рассмотрим особенности разработки на QT под Mac OS X на небольшом примере.

С чего все начиналось…

Норвежская компания Trolltech, была зарегистрирована в 1994 году, двумя норвежскими парнями – Хаавардом Нортом и Эйриком Чамбе-Энгом. Поначалу компания называлась Quasar Technologies, но позже была переименована в Trolltech.

По долгу службы, Хаавард занимался разработкой объектно-ориентированой системы для вывода информации с аппарата УЗИ. Будучи друзьями еще с университета, ребята всерьез задумались о своем собственном стартапе. Они мечтали разработать наилучшую С++ библиотеку для разработки графических интерфейсов под операционный системы класса Unix.

В 1991 году работа было положено начало эры QT. В 1992 году Эйрик придумал одну из фундаментальных парадигм QT – систему сигнал/слот, Хаавард тут же реализовал эту идею. Так в QT появился собственный препроцессор С++ кода – moc. И уже к концу 1993 года была готово ядро рендеринга, а в следующем году вышла первая версия QT!

Разработать - разработали, а вот продать свой продукт оказалось проблематично. Выхода не было, нужно было искать клиентов, потому как негоже могучим норвежским викингам сидеть на шее своих жен. С помощью старых знакомых, удалось заключить контракт с норвежской компанией Metis, на разработку графического интерфейса на QT.

Дела пошли в гору, и в 1996 Европейское Космическое агентство также стало клиентами Trolltech. Сама QT обрела поддержку Windows, и достигла версии 1.0! Trolltech начали расти, наняли еще двух разработчиков. Старт был позади, парни крепко стояли на ногах, а впереди виднелись неплохие перспективы.

В 1997 году Матиас Эттрих, будущий отец KDE, делает решение создать графическую оболочку под Linux на QT. С релизом первой версии KDE, стало понятно, что QT становится стандартом де-факто разработки графических интерфейсов под Linux.

В 1999 году выходит QT 2, в 2001 появляется QT 3. В июне 2005 года происходит историческое событие – выходит QT 4, значительно улучшенная версия фреймворка. Также под Windows появляется GPL версия библиотеки, QT становится стандартом для кросс-платформенной разработки. К 4 версии QT стал полноценным фреймворком. В нем есть все, что можно пожелать: средства ввода-вывода, контейнеры и алгоритмы, поддержка RPC с помощью D-Bus и многое другое.

Много воды утекло с тех пор… Trolltech достигла размера 250 человек, и открыла офисы по всему миру. В 2006 году компания провела IPO. В январе 2008 года Trolltech объявили, что Nokia сделала им предложение, от которого они не смогли отказаться. Сумма сделки составила 104 миллиона евро, а Trolltech стала собственностью Nokia. Зачем же Nokia понадобились тролли спросите вы. Все очень просто! QT - отнюдь не единственный продукт Trolltech. В 2004 году увидел свет продукт QTopia. QTopia – не что иное, как платформа для мобильных устройств, на основе Embeded Linux и QT. Видимо Nokia усматривает в QTopia замену текущей платформе Maemo, а в будущем возможно даже Symbian.

Отличительная черта Trolltech – их бизнес-модель. QT доступна под двумя лицензиями: GPL и проприетарной. Таким образом, либо вы покупаете лицензию QT и делаете вклад в копилку Trollltech и развития QT рублем, либо пишите открываете исходные коды вашего приложения, делая вклад в развития Open Source и QT в частности.

Краткий обзор возможностей QT

Итак. Для разработки приложений с пользовательским интерфейсом в Mac OS X доступно несколько фреймворков:
  • Cocoa. Данный фреймворк – наследие могучих Next Computer, которым не преминуло воспользоваться Apple.Написан данный фреймворк на Objective C и в настоящий момент именно он является единственной стратегической целью Apple.
  • Carbon. Apple позаботилась также и о разработчиках С++, по этому создала Carbon. Однако времена Carbon – это эпоха Mac OS Classic, в Mac OS X он превращается в бесполезный рудимент, который рано или поздно, возможно даже в Snow Leopard, будет упразднен. Carbon существовал для того, чтобы облегчить портирование сторонних приложений из Mac OS 9 в Mac OS X. В частности, если вы хотите, чтобы ваше приложение было 64 битным и умело адресовать до 64 Гбайт виртуальной памяти, то Carbon – это не для вас. Adobe уже успела обжечься на этом.
  • Фреймворки третьих лиц. Их существует также огромное множество, к примеру WxWidgets, GTK, TCL, однако в этой статье речь пойдет о QT:)
Сейчас QT является довольно мощным инструментом, который здорово расширяет возможности С++. Вдогонку о лицензиях: коммерческая версия QT включает драйвера для RDBS типа DB2 и т.д.

Стоит упомянуть, что интересы QT и Mac OS X пересеклись на движке KHTML. KHTML – движок рендеринга HTML страниц из проекта KDE, который в свое время избрала Apple, как основу для своего проекта WebKit, который посже породил Safari, Google Chrome и другие более мелкие браузеры:) Хотя вся QT часть была из проекта выброшена. К слову о WebKit. В свое время Apple рассматривала также Gecko, как основного кандидата на роль HTML движка. Однако из-за преобладания в Gecko технологии RPC – XPCOM, был избран KHTML.

QT приложения имеют нативный Aqua look, но несколько устаревший. Это связано с тем, что QT использует Carbon для прорисовки виджетов, а современный интерфейс Mac OS X написан в большинстве своем на Cocoa. Тем не менее Trolltech постоянно пытается улучшить внешний вид мак версии и в последних версиях QT появились возможности по созданию нативных для Mac OS X ToolBar меню, а также дополнительных функций по взаимодействию с Dock. Также в QT есть поддержка Growl.

Почему именно QT? Если вам нужно написать небольшое приложение с пользовательским интерфейсом и вы не знаете Objective C, то QT – ваш выбор, потому как учить Carbon не имеет ни особого смысла, ни видимых перспектив. Если же вы имеете приложение на С++, и вам его нужно портировать на Mac OS X, то в пору выбрать QT, потому как С++ имеет определенные ограничения при сопряжении с Objective C кодом. Тем более, обновленная версия QT, которая использует Cocoa для отрисовки виджетов – не за горами, уже доступны первые снапшоты.

От слов к делу

Для начала нам нужно скачать GPL версию QT, это можно сделать с сайта Trolltech http://trolltech.com/developer/downloads/qt/mac

Данные исходные коды нам нужно собрать, причем с возможностью создания Universal Binary (позволю себе напомнить, что Вы должны иметь установленный Apple XCode). Распакуйте QT в удобную для Вас директорию, запустите терминал и выполните следующую строку в каталоге QT:

./configure -universal -sdk /Developer/SDKs/MacOSX10.4u.sdk -fast –static

Данная команда отконфигурирует QT для статической сборки в Universal Binary статические библиотеки. Поскольку UB приложения, собранные статически, занимают довольно много места, рекомендую перед сборкой переименовать папку examples:
mv -R examples examples_

Теперь соберем библиотеку посредством команды make. В зависимости от мощности вашего процессора, сборка может занять от 20 минут до нескольких часов. После завершения сборки, выполняем в терминале следующие команды:
make install
PATH=$PATH:/usr/local/Trolltech/QT-4.4.1/
export PATH

Учтите, что путь к установленной QT меняется от версии к версии. Теперь все готово для дальнейшей разработки.

HelloQT

Итак, создадим простейшее UB приложение с одной кнопкой и текстовой меткой. Для этого в нашей домашней директории создадим папку HelloQT. Запустите QT Designer, он выглядит примерно так:

Создайте Dialog форму и переместите на нее виджеты QPushButton и QLabel. Присвойте им object name helloBt и helloLbl соответственно. Саму форму назовите helloDlg и сохраните результаты наших манипуляций в директорию проекта с именем HelloDlg. Конечный результат будет выглядеть так:

Теперь нам предстоит написать программный код. Создайте файлы HelloDlg.cpp, HelloDlg.h, main.cpp и HelloQT. pro (файл QT проекта) в директории проекта.

В файле HelloQT. pro напишите данные строки:

SOURCES += HelloDlg.cpp main.cpp
HEADERS += HelloDlg.h
FORMS += HelloDlg.ui
CONFIG += x86 ppc

QT использует собственный генератор make файлов qmake. Директивы и формат qmake можно более подробно изучить с помощью QT Asistant. Обратите внимание на последнюю строку, здесь вы явно указываете, что вас интересует именно Universal Binary приложение.

Trolltech пытается улучшить интеграцию QT и наиболее популярных интегрированных сред разработки, но дела обстоят пока не так хорошо как хотелось бы. У вас есть два пути: вы можете компилировать приложение прямо из командной строки, а можете создать XCode проект, чтобы продолжить написание проекта прямо из под этой IDE. Я предпочитаю второй вариант, время от времени генерируя новый файл. Выполните из терминала следующие строки:

qmake -spec macx-xcode HelloQT.pro

Теперь осталась самая важная часть. Измените содержимое файла main.cpp на следующие строки:
#include

#include «HelloDlg.h»

int main(int argc, char *argv)
{
QApplication appl(argc, argv);
HelloDlg mainDlg;
mainDlg.show();
return appl.exec();
}


Файл HelloDlg. h должен выглядеть так:
#ifndef _HELLODLG_H_
#define _HELLODLG_H_

#include

#include «ui_HelloDlg.h»

class HelloDlg: public QDialog, Ui::helloDlg
{
Q_OBJECT

public :
HelloDlg();
~HelloDlg();

private slots:
void onHelloBtClicked(bool );
};

#endif


Ну и наконец HelloDlg.cpp:
#include «HelloDlg.h»

HelloDlg::HelloDlg(): QDialog(NULL)
{
Ui::helloDlg::setupUi(this );

Connect(helloBt, SIGNAL(clicked(bool )), this , SLOT(onHelloBtClicked(bool )));
}

HelloDlg::~HelloDlg()
{

void HelloDlg::onHelloBtClicked(bool )
{
helloLbl->setText(QString::fromUtf8(«Hello QT» ));
}


Остановимся на этом моменте и сделаем теоретический экскурс, чтобы немного пояснить данные строки новичкам в QT. Для реакции на события, т. е. например нажатия кнопки, QT использует свою собственную прадигму сигнал/слот. Поскольку язык С++ не имеет достаточно возможностей, Trolltech решили использовать собственный препроцессор moc, о котором я упоминал раньше. Макрос Q_OBJECT указывает на то, что данный класс содержит слоты и требует специальной обработки препроцессором QT (этот процесс называется moc" ing), каждый такой класс должен прямо или косвенно наследоваться от класса QObject. Директива private slots указывает на то, что данные методы класса являются закрытыми слотами, т. е. так называемыми обработчиками событий. Следует добавить, что к одному сигналу можно подключить множество слотов, точно также как и подключить на каждый слот какое-угодно количество сигналов.

Класс Ui:: helloDlg, находящийся в пространстве имен Ui, это класс который создан специальным препроцессором из файла формы HelloDlg.ui (этот процесс называется ui" ing). UI файл – файл формата xml, который описывает форму, подготовленную в QT Designer и ее элементы.

Вот и все, компилируем приложение. Либо из XCode нажатием на cmd + B, либо из терминала командой:


Результаты можно увидеть на скриншотах:

Если вам эта тема интересна, задавайте вопросы в комментариях:)