Журнал LinuxFormat - перейти на главную

LXF78:Qt/KDE

Материал из Linuxformat
Перейти к: навигация, поиск

Часть 1. Добро пожаловать в мир графических интерфейсов Linux! Андрей Боровский начинает серию учебников для желающих научиться разрабатывать собственные KDE-приложения.

Содержание


Нет ничего удивительного в том, что многие серьезные приложения для Linux до сих пор обладают только текстовым интерфейсом. Текстовый терминал имеет свои преимущества и даже свою эстетику. Тем не менее, всякий программист, изучающий разработку приложений для Linux, рано или поздно сталкивается с необходимостью освоить программирование графических интерфейсов. Средств разработки графических интерфейсов для приложений Linux существует много, но бесспорными лидерами являются два – Qt/KDE и GTK. Именно с помощью этих средств построены две самые популярные графические среды Linux – KDE и GNOME. Наше внимание будет сосредоточено на разработке приложений с помощью простой объектно-ориентированной системы Qt/KDE.

Система Qt/KDE, как следует из ее названия, состоит из двух слоев –базовой библиотеки классов C++ Qt toolkit, разработанной норвежской компанией Trolltech и «надстройки» KDE. К достоинствам Qt toolkit следует отнести тщательно проработанную, простую для понимания структуру классов и великолепную документацию. Среда KDE, в свою очередь, развивает традиции, заложенные разработчиками Qt toolkit. Изучение разработки приложений для Qt/KDE мы начнем именно с Qt toolkit.

Наша первая программа

Что же представляет собой Qt с точки зрения программиста? Прежде всего – это библиотека, насчитывающая более четырехсот классов C++. Классы Qt реализуют различные элементы графического интерфейса пользователя, интерфейсы взаимодействия с базами данных и некоторые часто используемые структуры данных. Помимо этого Qt включает визуальный редактор пользовательского интерфейса Qt Designer, набор инструментов для интернационализации приложений Qt Linguist и браузер справочной документации Qt Assistant. Qt Assistant составляет основу справочной системы самой Qt и может использоваться разработчиками для реализации интерактивной справки в их собственных приложениях. Qt – кросс-платформенная библиотека, она реализована на платформах Windows, Mac OS X, X11 (Linux, BSD, Solaris, HP-UX, IRIX, AIX и другие). На платформах Windows и X11 библиотека напрямую обращается к низкоуровневым API (GDI для Windows и X API для X11), благодаря чему обеспечивается максимально возможное для быстродействие.

Хотя на момент написания статьи компания Trolltech уже выпустила Qt 4, все примеры статьи написаны с использованием Qt 3.3.5, так как именно на этой версии основана новейшая на сегодняшний день версия KDE – 3.5.1. Хотя простейшие примеры из этой статьи можно скомпилировать на всех версиях Qt, для компиляции более сложных примеров из последующих статей цикла может понадобиться версия 3.3.5 или выше. Кроме пакетов основной библиотеки в системе должны быть установлены пакеты разработчика (qt-devel, kde-devel или подобные). К версии компилятора никаких особых требований нет. В системе также должны быть установлены инструменты сборки и конфигурации исходных текстов: make, autoconf и automake. Для удобства работы с Qt следует установить переменные окружения. В переменную PATH добавим путь к утилитам Qt, например:

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

Для того, чтобы утилиты и make-файлы Qt выполнялись успешно, необходимо создать переменную QTDIR:

export QTDIR=/usr/lib/qt3

Структура библиотеки Qt довольно проста и напоминает структуру библиотек MFC и VCL. Родоначальником большинства классов Qt является QObject, а все визуальные элементы управления происходят от одного из его потомков – класса QWidget. Классы объявляются в заголовочных файлах Qt, имена которых соответствуют именам классов. Например, класс QPushButton объявлен в файле QPushButton.h. Cправочная система Qt, помимо прочего, предоставляет подробную информацию обо всех классах библиотеки. Доступ к справочной системе можно получить двумя способами: открыть копию индекса справочной системы на локальном жестком диске (это можно сделать с помощью приложения Qt Help Assistant, показанном на рис.1, а можно – с помощью web-браузера) или найти нужный раздел справки на специальном сайте doc.trolltech.com.

Для того, чтобы познакомиться с классами Qt на практике, напишем простую программу:

 #include <Qapplication.h>
 #include <QLabel.h>
 int main(int argc, char *argv[])
 {
  QApplication MyApp(argc, argv);
  QLabel Label(NULL);
  Label.setFrameStyle(QFrame::Panel | QFrame::Sunken);
  Label.setText(«Hello, brave new Qt world!»);
  MyApp.setMainWidget(&Label);
  Label.show();
  return MyApp.exec();
 }

Эта программа, своего рода «Hello world!» для Qt, включает в себя все основные элементы приложения. Рассмотрим текст программы по порядку. В файле QApplication.h объявлен класс QApplication, который играет центральную роль в работе приложения Qt. Файл QLabel.h декларирует класс QLabel, соответствующий элементу интерфейса «панель со статическим текстом» (также известному как «метка»).

Как и в любой Linux-программе, написанной на C++, точкой входа в Qt-программе является функция main. В этой функции мы, прежде всего, создаем объект класса QApplication. У класса Qapplication несколько конструкторов, мы выбираем из них конструктор с двумя параметрами, аналогичными параметрам функции main. Получив переменные argc и argv, конструктор извлекает из них ключи, которые относятся к конфигурации приложения X Window (дисплей для вывода, геометрия окна), если, конечно, таковые имеются. Таким образом, используя объект класса QApplication, мы «бесплатно» получаем приложение, которое ведет себя в соответствии со стандартами X Window! Далее мы создаем объект класса QLabel, который будет главным (и единственным) визуальным элементом нашего приложения. В заголовке выбранного нами конструктора класса QLabel определен один параметр типа QWidget*. В этом параметре конструктору передается указатель на класс, содержащий родительский визуальный элемент (родительский в иерархии визуальных элементов окна, а не в иерархии классов). Аналогичный конструктор имеют и другие визуальные элементы Qt: кнопки, поля ввода и т.д. У главного визуального элемента приложения родителя нет, поэтому мы передаем NULL конструктору класса. Метод setFrameStyle, унаследованный классом QLabel от его предка – класса QFrame, позволяет указать некоторые параметры внешнего вида панели. Флаги, передаваемые методу setFrameStyle, являются элементами класса QFrame. Метод setText задает текст, который отображается элементом QLabel. Далее, с помощью метода setMainWidget, мы указываем объекту MyApp, что Label является главным визуальным элементом приложения. Метод show делает объект Label видимым. Последнее, что мы делаем в нашей программе – запускаем цикл обработки сообщений с помощью метода exec объекта MyApp. Цикл будет выполняться до тех пор, пока приложение не получит команду завершить работу.

Теперь можно приступить к сборке приложения. Сохраняем исходный текст в отдельной директории в файле с именем app1.cpp. Наша следующая задача, – создать проект приложения. Для генерации проекта используется утилита qmake. Переходим в директорию, в которой сохранен файл app1.cpp и даем команду

qmake -project

В результате в директории должен появиться файл проекта app1.pro. Теперь, чтобы получить make-файл проекта, достаточно скомандовать

qmake

После этого собираем наше приложение командой make. Запустив на исполнение двоичный файл app1, мы увидим простое окно с текстовой панелью.

Img 78 95 1.jpg

Обработка событий

Как и многие другие графические системы, Qt toolkit базируется на событийно-управляемой архитектуре. Когда в графической системе, например X Window, происходит нечто, связанное с одним из окон приложения (щелчок мышью, нажатие на клавишу, сокрытие окном другого приложения – то есть необходимость перерисовки), этому окну посылается сообщение. Qt toolkit преобразует сообщение графической системы в событие Qt, создает объект класса события и вызывает метод-обработчик события, которому в качестве параметра передается созданный объект. Обработчик события, связанного с окном, – это виртуальный метод объекта, отвечающего за данное окно. Методы-обработчики, как правило, принимают один параметр, имеющий тип «класс события». Все классы событий Qt являются потомками класса Qevent. Класс QWidget включает в себя обработчики практически всех событий, которые может обрабатывать визуальный элемент, однако большинство этих методов – заглушки, не выполняющие никаких действий. В классах-потомках QWidget переопределяются методы-обработчики событий, актуальных для соответствующих классов. Для того, чтобы задать собственный обработчик события для какого-либо класса Qt, необходимо создать потомок этого класса и переопределить в нем соответствующий метод базового класса.

Рассмотрим описанный механизм на примере создания обработчика события mousePressEvent для потомка класса QLabel. Событие mousePressEvent генерируется в результате нажатия кнопки мыши, если в этот момент указатель мыши находится в области окна. Событие mousePressEvent обрабатывается методом mousePressEvent, объявленным в базовом классе QWidget, где этот метод является заглушкой. Метод mousePressEvent не переопределяется классах QFrame и QLabel, поскольку соответствующие визуальные элементы обычно не реагируют на нажатие кнопки мыши. В следующем примере мы создаем производный класс от QLabel, в котором переопределяем метод mousePressEvent.

 class QMyLabel: public QLabel
 {
             public:
             QMyLabel( QWidget * parent, const char * name = 0, WFlags f = 0)
                         :QLabel(parent, name, f)
             {
             }
                         protected:
                         void mousePressEvent ( QMouseEvent * e )
             {
                         QString S;
                         S = QString(«x= %1, y= %).arg(e->x()).arg(e->y());
                         this->setText(S);
             }
 };

У обработчика mousePressEvent один параметр типа QMouseEvent*. QMouseEvent – это класс, соответствующий событиям, связанным с мышью. Объект этого класса содержит информацию о событии – координаты указателя мыши в момент события, а также состояние кнопок мыши и специальных клавиш. Используя информацию объекта e, обработчик формирует строку, содержащую координаты указателя мыши в момент возникновения события и выводит эту строку на панель с помощью метода setText., используя перегруженный метод arg класса QString (строк). Для подстановки значений в строку S применяется метод arg класса QString – привычный C-программистам snprintf в Qt используется редко. Теперь заменим в исходной программе класс QLabel классом QMyLabel:

 int main(int argc, char *argv[])
 {
              QApplication MyApp(argc, argv);
              QMyLabel Label(NULL);
              Label.setFrameStyle(QFrame::Panel | QFrame::Sunken);
              Label.resize(200, 100);
              Label.setFont( QFont( «Courier», 14, QFont::Bold ) );
              MyApp.setMainWidget(&Label);
              Label.show();
  return MyApp.exec();
 }

Мы получили программу, отображающую в своем окне координаты щелчка мыши:

Img 78 96 1.jpg

Как узнать, какие события может обрабатывать тот или иной класс Qt? Для ответа на этот вопрос можно составить список методов-обработчиков событий для данного класса и его предков с помощью справочной системы Qt. Методы-обработчики можно отличить от других методов по их именам, которые оканчиваются на «Event».

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

В приложениях, построенных на основе иерархии объектов, часто бывает необходимо, чтобы в ответ на событие, связанное с одним из объектов, вызывался метод другого объекта. Допустим, у нас есть объект Button, представляющий кнопку. Нажатие кнопки должно вызвать изменения в другом объекте, например, удалять текст, выводимый объектом Label класса QLabel. Для решения этой задачи можно конечно, переопределить событие mousePressEvent, в потомке класса QpushButton (кнопка), как мы это делали раньше, но такой подход оказывается весьма неудобным: нам придется создать для нашей кнопки Button новый класс-потомок QPushButton, учитывающий логику взаимодействия с объектом Label. Может случить так, что для каждой кнопки в нашей программе нам понадобится создать собственный класс C++. В общем случае, если мы хотим, чтобы класс A реагировал на события, связанные с классом B, нам придется создавать производные классы от A и от B. Излишне говорить, что описанный метод весьма громоздок и неудобен. Все интегрированные системы разработки на C++ пытаются так или иначе упростить решение этой проблемы. Одним из альтернативных решений является метод обратных вызовов (callbacks). При использовании метода обратных вызовов объекту источнику события (например, Button), передается адрес метода объекта обработчика (в нашем примере – адрес метода clear объекта Label). Конечно, обратные вызовы можно реализовать в явном виде средствами стандартного C++, но такое решение будет почти столь же трудоемким, как и описанное выше. Qt library упрощает взаимодействие между объектами при помощи основанного на методе обратного вызова механизма сигналов и слотов. Объект Qt генерирует (в терминологии Qt – «эмитирует») сигнал при изменении своего состояния. Если данный сигнал объекта связан со слотом другого (или того же самого) объекта, выполняется метод, соответствующий слоту. Связь сигнал-слот можно рассматривать как однонаправленную линию коммуникации между двумя объектами.

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

Для того, чтобы проверить описанные концепции на практике, напишем еще одну программу:

 #include <QApplication.h>
 #include <qvbox.h>
 #include <QLabel.h>
 #include <qlineedit.h>
 #include <QPushButton.h>
 int main(int argc, char *argv[])
 {
              QApplication MyApp(argc, argv);
              QVBox MainBox;
              MainBox.resize( 150, 110 );
              QLabel Label(&MainBox);
              QLineEdit Edit(&MainBox);
              QPushButton Button(«Clear», &MainBox);
              Label.setFrameStyle(QFrame::Panel | QFrame::Raised);
              QObject::connect(&Edit, SIGNAL(textChanged(const QString&)), &Label, SLOT(setText(const QString& )));
              QObject::connect(&Button, SIGNAL(clicked()), &Label, SLOT(clear()));
              QObject::connect(&Button, SIGNAL(clicked()), &Edit, SLOT(clear()));
              MyApp.setMainWidget(&MainBox);
              MainBox.show();
  return MyApp.exec();
 }

Эта программа содержит много новых элементов, поэтому рассмотрим их по порядку. Класс QVBox представляет собой панель, способную упорядочивать расположение дочерних визуальных элементов по вертикали. В этом смысле Qt (и Gtk) значительно отличается от инструментариев, доступных Windows-программистам. В VCL, MFC и прочих применяется модель абсолютного позиционирования, когда положение элемента управления на форме, а также его размеры задаются координатами в пикселях. При изменении размеров окна программа сама должна пересчитать координаты его дочерних элементов. В мире Unix еще со времен Tk преобладает другой подход – менеджеры компоновки (layout manager). Разработчик просто указывает, как нужно расположить элементы на форме (вертикально, горизонтально, как в таблице и т.п.), а компоновщик берет на себя всю работу по вычислению координат и автоматически обновляет их при изменении размеров окна. В случае с QVBox, дочерние элементы располагаются друг под другом в порядке их объявления. Объект MainBox класса QVBox является главным визуальным элементом нашего приложения. Метод resize изменяет размеры главного элемента. В конструкторе QLabel мы указываем MainBox в качестве родительского элемента. Класс QLineEdit – это строка ввода, класс QPushButton – кнопка. Первый аргумент конструктора QPushButton – текст кнопки, второй аргумент – ссылка на родительский визуальный элемент. Таким образом, в окне программы будут расположены три элемента – текстовая панель, строка ввода и кнопка. Высота кнопки и строки ввода фиксирована, а текстовая панель займет всю оставшуюся область окна.

Далее в программе следуют три конструкции связывания сигналов и слотов. Связывание выполняет статический метод connect класса QObject. У метода connect 4 аргумента. Первый аргумент – указатель на объект-источник сигнала, второй аргумент – макрос SIGNAL, которому передается описание сигнала, третий аргумент – указатель на объект-владелец слота и последний аргумент – макрос SLOT, которому передается описание слота.

Конструкция

QObject::connect(&Edit, SIGNAL(textChanged(const QString&)), &Label,  SLOT(setText(const QString& )));

означает, что сигнал textChanged объекта Edit связывается со слотом setText объекта Label. Сигнал textChanged эмитируется объектом класса QLineEdit всякий раз, когда меняется содержимое строки ввода, а слот setText класса QLabel аналогичен уже знакомому нам одноименному методу (далее мы увидем, что слоты представляют собой не что иное как методы классов C++, так что слот можно не только связывать с сигналом, но и вызывать напрямую, как обычный метод объекта). У сигнала textChanged и слота setText одинаковый набор параметров (один параметр типа «ссылка на объект QString»), и эти параметры мы ука- зываем в описании сигнала и слота. Указывать параметры необходимо для того, чтобы слот setText получал строку, передаваемую сигналом textChanged. Мы можем не указывать параметры или вообще связать сигнал и слот с разными списками параметров, но в этом случае слот не получит данных, а будет использовать значения своих параметров, заданные по умолчанию. Возможность связывать сигналы и слоты с разными списками параметров очень удобна, но она может стать источником ошибок, которые трудно будет обнаружить. Связывание сигнала textChanged и слота setText приведет к тому, что любое изменение в строке ввода будет тут же отображаться на текстовой панели.

Далее мы связываем сигнал clicked объекта Button со слотом clear объекта Label. Сигнал clicked эмитируется объектами класса QPushButton в ответ на щелчок по кнопке. Слот clear удаляет текст с панели. Связывание этих сигнала и слота приводит к тому, что нажатие кнопки Button будет удалять текст с панели Label. Следующая строка программы выполняет связывание того же сигнала Button.clicked с слотом clear объекта Edit, удаляющим текст из строки ввода. Использование сигнала clicked настолько удобнее по сравнению с обработкой события clicked, что разработчики Qt даже не вели в состав класса QPushButton такое событие. Мы назначаем объект MainBox главным визуальным элементом приложения. Мы должны сделать MainBox видимым с помощью метода show, но для дочерних объектов этого делать не нужно, поскольку MainBox сам позаботится о них.

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

qmake –project
qmake
make

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

Img 78 97 1.jpg

Соединение сигнал-слот можно разорвать с помощью статического метода QObject::disconnect. Список параметров у этого метода такой же, как и у метода connect.

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


...Qt и OpenSource

Лежащий в основе KDE инструментарий Qt toolkit распространяется на условиях двойного (сейчас даже можно сказать – тройного) лицензирования. Разработчики Open Source могут распространять свои приложения на условиях открытой Q Public License (QPL) или GPL. Лицензия QPL, одобренная организацией Open Source Initiative позволяет распространять Qt toolkit, а также программное обеспечение, созданное с его помощью, в форме исходных текстов и двоичных модулей на бесплатной основе. Модификации исходного кода должны распространяться отдельно в виде патчей на тех же условиях. Разработчики коммерческих продуктов должны приобрести коммерческую лицензию, которая позволяет не открывать исходные тексты, а также предоставляет в распоряжение разработчика дополнительные возможности коммерческих расширений Qt.

...История открытого лицензирования Qt

Компания Trolltech далеко не сразу согласилась на открытое лицензирования своего продукта. Использовавшаяся в начале лицензия FreeQt не позволяла свободно распространять модификации исходных текстов Qt toolkit. Когда основанная на Qt графическая оболочка KDE начала завоевывать популярность на платформе Linux, у многих сторонников Open Source сложилось убеждение, что лицензия FreeQt ставит под угрозу дальнейшую судьбу KDE как открытого проекта. Участники движения Open Source опасались, что в определенный момент ведущая графическая среда для Linux может превратиться в закрытый коммерческий продукт. Оппозиция сообщества Open Source привела к постепенному увеличению открытости главного продукта компании Trolltech. В результате компромисса была принята новая лицензия Q Public License. Специальная договоренность гарантировала, что условия QPL не станут более жесткими даже в том случае, если компания разработчик перейдет в третьи руки. Организация KDE Free Qt Foundation должна была обеспечить переход Qt toolkit под действе лицензии BSD License в случае, если в течении 12 месяцев не будет выпущено открытой версии Qt. Тем не менее, разработчики, не очень доверявшие изменениям лицензионной политики, основали проект Harmony, призванный создать полностью открытую библиотеку, эмулирующую Qt. Проект Harmony был закрыт после того, как Trolltech выпустила Qt Open Source Edition на условиях GPL.

...Полезные ссылки

Если вы заинтересовались Qt и хотите узнать больше об этом инструментарии, советуем вам обратить внимание на книгу Жасмин Бланшетт (Jasmin Blanchette) и Марка Сомеерфильда (Mark Summerfield) «C++ GUI Programming with Qt 3». Эта книга написана сотрудниками компании Trolltech и выходит в серии «Bruce Perens Open Source Series». Книга переведена на русский язык и выпущена издательством «Кудиц-Образ». Если вам не удалось найти ее в ближайшем книжном магазине, вы можете загрузить бесплатную английскую PDF-версию с сайта phptr.com/perens. Перевод на русский язык доступен для чтения онлайн: http://www.linuxcenter.ru/lib/books/qt3/. Ну и не забывайте о восхитительной справочной документации Qt!

Персональные инструменты
купить
подписаться
Яндекс.Метрика