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

LXF80:Qt/KDE

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

Qt Designer: красивый интерфейс одной мышью

ЧАСТЬ 3

Компьютер был придуман для того, чтобы избавить человечество от рутины, так почему вы должны корпеть над кодом, создавая бесконечные кнопки, списки и поля ввода? Андрей Боровский знает более легкий путь.


В прошлой статье мы конструировали графические интерфейсы вручную. Преимуществами этого подхода являются свобода обращения с визуальными компонентами и достижение глубокого понимания механизмов работы Qt. Недостаток у ручного проектирования только один – высокая трудоемкость. Процесс проектирования графического интерфейса можно упростить и ускорить с помощью среды визуального проектирования Qt Designer. Следует сразу подчеркнуть, что Qt Designer не является полноценной IDE, такой как Borland C++Builder или MS Visual C++. Qt Designer не управляет процессами компиляции или отладки, а только генерирует «заготовки» исходных текстов приложения (заголовочные файлы C++ и специальные файлы с расширением ui). В качестве примера проектирования с помощью Qt Designer мы напишем простую программу-просмотрщик графических файлов.

(thumbnail)
Рис. 1. Окно формы в режиме редактирования.

Запустите Qt Designer командой designer или через главное меню оболочки KDE вашего дистрибутива. В главном окне выберите команду File|New. В открывшемся окне укажите тип интерфейса. Для нашей задачи подойдет интерфейс Dialog (напомню, что окно QDialog является основой интерфейса простых приложений Qt). В результате будет создана новая форма – класс Form1 (естественно, потомок класса Qdialog). Внешний вид Qt Designer в режиме редактирования формы напоминает другие визуальные редакторы (например, Delphi или Kylix). Для размещения визуального элемента в окне нужно «перетащить» мышью соответствующую иконку с панели компонентов на форму. На специальной панели можно установить значения свойств объектов, представляющих элементы интерфейса. С помощью этой панели можно, например, задать надписи на кнопках и в заголовках окон или установить политику изменения размеров. Окно создаваемой нами программы (рис. 1) содержит три визуальных элемента: кнопку Открыть (объект pushButton1 класса QpushButton), с помощью которой загружается файл, элемент Pixmap Label (pixmapLabel1) для вывода изображения и элемент Text Label (textLabel1) для вывода имени файла. Кнопка и текстовая метка объединены элементом Layout (менеджером компоновки, с которыми мы познакомились в прошлый раз). В режиме редактирования формы область действия менеджера компоновки обводится красной каймой. Для того, чтобы связать одним менеджером компоновки несколько элементовинтерфейса, необходимо выделить их (щелкая по ним мышью и удерживая нажатой клавишу Shift), а затем в контекстном меню выбрать одну из команд Lay out. Под строкой с кнопкой и меткой расположен визуальный элемент Pixmap Label (выделен на рисунке). Чтобы скомпоновать Pixmap Label и верхнюю (уже скомпонованную) строку элементов управления, мы щелкаем правой кнопкой мыши по свободному месту в окне формы и в контекстном меню выбираем команду Lay Out Vertically (будучи применена к форме, эта команда связывает все расположенные на ней группы элементов единым менеджером компоновки). В нашем случае с формой будет связан вертикальный менеджер компоновки, управляющий расположением верхней строки элементов управления и элементом Pixmap Label. В примере из предыдущей статьи мы добавляли специальную «распорку» с помощью метода addStretch() менеджера компоновки. Требовалось это для того, чтобы не допустить «расползания» элементов интерфейса. В нашем примере в распорке нет необходимости, так как элемент Pixmap Label занимает все свободное пространство окна. В тех ситуациях, когда распорка нужна, к нашим услугам визуальный элемент Spacer (объект класса QSpacerItem). В режиме редактирования формы этот элемент обозначается пружинкой. Логика работы интерфейса нашей программы очень проста: в ответ на щелчок по кнопке Открыть открывается окно выбора файла, после чего выбранное изображение выводится в окно Pixmap Label.

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

Начнем с переменных. В нашей программе нужно реализовать упомянутое выше диалоговое окно выбора файла. Мы воспользуемся стандартным окном, реализованным в классе QFileDialog, но для этого нам надо, во-первых, включить в проект заголовочный файл qfiledialog.h, а, во-вторых, объявить переменную типа QFileDialog. Чтобы включить в проект заголовочный файл qfiledialog.h, в окне Object Explorer переходим на вкладку Members, щелкаем правой кнопкой мыши по названию раздела Includes (in Declaration) (включения в заголовочный файл) и вводим строку qfiledialog.h. Точно так же введем имя еще одного заголовочного файла, который понадобится нашей программе, – qpixmap.h. В раздел Includes (in Implementation) (включения в файл реализации) добавим заголовочный файл qstring.h, он тоже нам пригодится. В том же окне Object Explorer мы объявляем новую переменную-член класса Form1. Для этого на вкладке Objects выбираем Form1, переходим на вкладку Members, щелкаем правой кнопкой мыши по имени подраздела Private в разделе Class Variables (переменные класса) и в открывшемся контекстном меню выбираем (единственную в этот момент) команду New. В строке редактора переменных вводим

QFileDialog * OpenFileDialog;

Все сделанные изменения отображаются в окне Object Explorer (рис. 2), (по умолчанию это окно «припарковано» в окне Qt Designer). Переменную OpenFileDialog нужно инициализировать. Обычно инициализация выполняется в конструкторе, однако Qt Designer не позволяет редактировать конструктор Form1 напрямую. Вместо этого мы должны определить специальную функцию-метод init(), которую будет вызывать конструктор класса. Именно в метод init() следует помещать весь вводимый вручную код инициализации. Заготовку метода init() мы создадим в окне Object Explorer в разделе Functions/private, точно так же, как мы объявляли переменную OpenFileDialog (то есть выберем команду New из контекстного меню и в открывшемся окне редактора введем строку "init()"). После этого должно открыться окно редактора исходных текстов, в котором следует вести код функции init():

void Form1::init()

{
   OpenFileDialog = new QFileDialog(0);
   OpenFileDialog->setCaption(trUtf8("Открыть"));

}

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

У метода init есть метод-напарник destroy, который вызывается деструктором объекта формы. Определять метод destroy в принципе не обязательно, но желательно с точки зрения правил хорошего тона:

void Form1::destroy()

{

    delete OpenFileDialog;

}

Теперь определим метод loadFile(), который и будет открывать графический файл для просмотра. Новый метод определяется точно также, как метод init(), только в строке ввода редактора методов нужно ввести «loadFile(const QString fn)».

void Form1:: loadFile(const QString fn)

{

    textLabel1->setText(trUtf8("Файл: ") + fn);
    QPixmap pm;
    if (pm.load(fn))
    pixmapLabel1->setPixmap(pm);
    else
    pixmapLabel1->setText(trUtf8("Неизвестный формат"));

}

Параметр fn служит для передачи имени файла, который нужно открыть. Для загрузки графических файлов мы используем объект класса QPixmap. Этот класс поддерживает работу с множеством популярных и не очень популярных графических форматов (конкретный список форматов зависит от условий компиляции Qt). Метод load объекта QPixmap пытается загрузить файл и возвращает значение TRUE, если файл загружен успешно, и FALSE в случае неудачи, вызванной, например, попыткой загрузить изображение в неподдерживаемом формате.

Далее мы отображаем изображение с помощью метода setPixmap объекта pixmapLabel1 или выводим сообщение об ошибке с помощью метода setText того же объекта. Возможно, это покажется неожиданным, но объект pixmapLabel1 (элемент Pixmap Label с панели Tool Box) принадлежит тому же классу QLabel, что и текстовая метка.

(thumbnail)
Рис. 2. Окно Object Explorer.

Для реализации логики работы нашего интерфейса необходимо определить в классе Form1 слот, который бы обрабатывал событие clicked() объекта pushButton1. Qt Designer предоставляет нам несколько средств для создания и редактирования слотов. Панель редактирования свойств позволяет создавать обработчики сигналов, посылаемых объектами. Если в этой панели выделить объект pushButton1, то на вкладке Signal Handlers появится список сигналов, эмитируемых этим объектом. Среди прочего мы находим сигнал clicked. Щелчок мышью по имени сигнала создает обработчик – новый метод класса Form1 pushButton1_clicked().

Далее открывается знакомое окно редактора, в котором можно ввести исходный текст обработчика сигнала, что мы и делаем:

void Form1::pushButton1_clicked()

{

    if(OpenFileDialog->exec() == QDialog::Accepted )
    loadFile(OpenFileDialog->selectedFile());

}

Метод exec() класса OpenFileDialog выводит на экран диалоговое окно и приостанавливает исполнение основного потока программы до тех пор, пока это окно не будет закрыто. Возвращаемое методом значение указывает, была ли нажата кнопка OK (QDialog:: Accepted). Имя выбранного пользователем файла можно получить с помощью метода selectedFile(). Новый слот автоматически связывается с соответствующим ему сигналом, однако сигнал и слот можно связывать и вручную, например, если один и тот же слот должен быть связан с несколькими сигналами. Для установки дополнительных связей сигнал–слот можно использовать специальное окно View and Edit Connections, которое открывается по команде меню Edit Connections.... Это же окно можно вызывать и более простым способом: нажать клавишу F3 (курсор приобретет вид перекрестья), навести курсор на нужный элемент формы и щелкнутьлевой кнопкой мыши. При этом открывается окно View and Edit Connections, в котором для выбранного элемента уже заготовлена строка сигнал-слот.

На этом разработка программы закончена. Сохраним результаты работы в отдельной директории. У нас появилось два файла – form1.ui и form1.ui.h. Первый из этих файлов представляет собой описание интерфейса создаваемой программы. В документации к Qt Designer можно найти подробное описание формата ui-файлов. Помимо самого Qt Designer, эти файлы можно также открывать в специальной программе просмотра. Файл form1.ui.h содержит весь код, который мы ввели вручную, то есть слот pushButton1_clicked(), а также методы init(), destroy() и loadFile().

Наша следующая задача заключается в том, чтобы сгенерировать заголовочный файл и файл реализации класса Form1, используя описание класса из файла form1.ui. Генерацию файлов осуществляет утилита командной строки uic. Для того, чтобы получить заголовочный файл form1.h даем в окне терминала команду:

uic form1.ui –o form1.h

По умолчанию программа uic выводит результаты своей работы в стандартный поток вывода, то есть, на терминал, но с помощью ключа –o мы указываем программе, что вместо терминала данные нужно записывать в файл с заданным именем. Для генерации файла form1.cpp, ссылающегося на form1.h, командуем:

uic –impl form1.h form1.ui –o form1.cpp

Команды, использующие uic, приводятся скорее в целях общего развития, поскольку нет никакой необходимости вызывать uic вручную. Утилита qmake в любом случае добавит в создаваемый make-файл вызов uic для генерации файлов form1.h и form1.cpp. Поскольку файлы form1.cpp и form1.h генерируются автоматически (в процессе сборки программы!), не следует пытаться вносить в них какие-либо изменения. Для ручного редактирования доступен только файл form1.ui.h, который включается в form1.cpp директивой #include. Как обычно, нам осталось написать только функцию main:

#include <qapplication.h>
#include "form1.h"

int main(int argc, char *argv[])

{

    QApplication app(argc, argv);
    Form1 fm(0);
    app.setMainWidget(&fm);
    fm.show();
    return app.exec();

}

Далее мы создаем файл проекта и make-файл с помощью традиционных вызовов утилиты qmake. На платформе Linux наша программа справится с отображением файлов BMP, JPEG (рис. 3), GIF, PNG и многими другими.

(thumbnail)
Рис. 3. Наша программа в работе.

Программа из нашего примера создает только одно окно, но ничто не мешает нам создать множество разных окон (например, нестандартных диалогов) и связать их в один проект. При запуске Qt Designer в окне New/Open можно выбрать элемент Project, и тогда файл проекта программы (файл с расширением .pro) будет сгенерирован самим Qt Designer.

Размещение кода, открывающего графический файл, в отдельной функции позволяет нам безболезненно наращивать интерфейс программы. Допустим, мы хотим снабдить нашу программу возможностью открывать файлы с помощью Drag and Drop. Сделать это нам будет несложно. Добавим в метод init() строку:

setAcceptDrops(TRUE);

Метод setAcceptDrops() позволяет превратить окно приложения в приемник перетаскиваемых объектов. Далее нам нужно перекрыть два метода-обработчика событий класса Form1. В разделе protected создаем методы

dragEnterEvent( QDragEnterEvent * event )

и

dropEvent( QDropEvent * event )

Событие DragEnterEvent происходит, когда перетаскиваемый объект оказывается в области окна программы. Обработчик этого события должен сообщить системе, может ли программа принять перетаскиваемый объект. Наша программа принимает все объекты, содержащие данные в формате URL (универсальные идентификаторы ресурсов).

Создавая обработчики событий DragEnterEvent и DropEvent следует помнить, что объекты Drag and Drop могут содержать несколько блоков данных в разных форматах. Код нашего обработчика состоит из одной строки:

void Form1::dragEnterEvent( QDragEnterEvent * event )

{

    event->accept(QUriDrag::canDecode(event));

}

Класс QDragEnterEvent содержит все необходимые сведения о событии, включая указатель на область данных перетаскиваемого объекта. Класс QUriDrag специально предназначенный для обработки перетаскиваемых объектов, содержащих URI, объявлен в файле qdragobject.h (а не quridrag.h, как можно было бы ожидать).

Помимо класса QUriDrag в том же файле объявлены классы для работы с перетаскиваемым текстом и изображениями. Статический метод QUriDrag::canDecode() возвращает TRUE, если объект содержит данные в формате URI и FALSE в противном случае. Это значение передается методу объекта-события accept, который сообщает системе, готова ли программа принять перетаскиваемый объект. Если теперь пользователь отпустит левую кнопку мыши, программе будет передано сообщение DropEvent, которое мы тоже должны обработать:

void Form1::dropEvent( QDropEvent * event )

{

    QStrList S;
    if (QUriDrag::decode(event, S))
    openFile(QUriDrag::uriToLocalFile(S.getFirst()));

}

Статический метод decode класса QUriDrag преобразует набор URI в набор строк с именами файлов (в общем случае пользователь может перетаскивать сразу несколько файлов) и заполняет ими объект класса QStrList. Наше приложение максимально упрощено, а потому перетаскивание файлов будет работать не всегда. Помимо файлового менеджера KDE, источником файлов для перетаскивания в программу в ее текущем виде может служить Nautilus, а вот перетащить имена файлов из Firefox и Mozilla не удастся. Дело тут вовсе не в несовместимости Drag and Drop – и Qt, и Mozilla на платформе X11 поддерживают XDND (стандартный протокол Drag and Drop в X Window System). Однако для передачи ссылок на файлы разные программы используют разные форматы MIME. Mozilla использует «фирменные» типы MIME text/x-moz-url и _NETSCAPE_URL, Konqueror – text/uri-list. Поскольку за устрашающими названиями скрывается обычный текстовый формат, написать перекодировщик совсем не сложно.

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

Взаимодействие с оконной системой и подключение сторонних библиотек

Интерфейсы библиотеки Qt предоставляют практически всё, что только может понадобиться разработчику графического приложения, а потому у него, как правило, нет необходимости напрямую обращаться к функциям оконной системы. Еслиже такая необходимость возникает, следует помнить, что код, обращающийся к оконной системе напрямую, практически всегда теряет переносимость. Рассмотрим два фрагмента кода. Первый генерирует снимок формы и сохраняет его в файле scr.png. Он должен выполняться одинаково на всех платформах:

#include <qpixmap.h>

#include <qnamespace.h>

...

QPixmap pm = QPixmap::grabWindow(this->winId());
pm.save("scr.png", "PNG");

Метод winId() возвращает идентификатор окна визуального элемента. В Windows это переменная типа Handle, в X Window – переменная типа Window. Второй фрагмент вызывает функцию X Window напрямую. Для этого вызова требуется не только идентификатор окна, но и идентификатор X-дисплея:

#include <X11/Xlib.h>

...

Window w = this->winId()
Display * d = this->x11Display();
XClearWindow(d, w);

...

Метод X11Display определен в классе QPaintDevice, который является предком классов QWidget, QPixmap, QPicture и Qprinter. Как вы догадываетесь, код, использующий этот метод, не будет работать в среде Windows GDI.

Сколь бы всеобъемлющей ни была система классов Qt, не ею единой живы наши приложения. В реальном мире нам неизбежно придется добавлять в программы ссылки на другие библиотеки. С подключением к проекту сторонних заголовочных файлов проблем у нас не возникает, а как быть со ссылками на сами двоичные файлы библиотек? Можно, конечно, отредактировать вручную make-файл, сгенерированный командой qmake, однако в процессе отладки проекта этот файл перезаписывается многократно. Более простой и естественный способ заключается в использовании ключей самой qmake. Например, если нам нужно связать наше приложение с библиотеками OpenGL libGL и libGLU, мы даем такую команду:

qmake –after LIBS="-lGL -lGLU"

В автоматически сгенерированном make-файле мы увидим строку наподобие

LIBS = $(SUBLIBS) -L/usr/lib/ -L$(QtDIR)/lib/ -L/usr/X11R6/lib/ -lGL-
lGLU -lQt-mt -lXext -lX11 -lm

То есть в переменную LIBS, содержащую список подключаемых библиотек, были добавлены ключи –lGL и –lGLU. Ключ --after указывает, что переменная LIBS должна быть обработана после завершения синтаксического разбора файлов (например, файла pro) нашей программы. Если бы мы не указывали ключ --after, а скомандовали, например:

qmake –makefile LIBS="-lGL -lGLU"

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

qmake –project LIBS="-lGL -lGLU"

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

Отладка

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

К наиболее простым и часто используемым техникам отладки следует отнести расстановку в программе функций, выводящих отладочные сведения о значении определенных переменных, о выполнении определенных условий или просто о том, что данная точка программы была выполнена. Отладочные сообщения делятся в Qt по тремкатегориям важности: обычные сообщения (debug), к которым относятся, например, сообщения о прохождении определенного блока программы, предупреждающие сообщения (warning), каковыми являются сообщения о некритичных ошибках и критические сообщения (fatal), которые помимо вывода информации об ошибке завершают работу программы. Соответственно трем типам сообщений, в Qt определено три глобальных функции: qDebug(), qWarning() и qFatal(). Формат вызова всех трех функций подобен формату вызова printf(3). По умолчанию сообщения, создаваемые с помощью этих трех функций, выводятся в стандартный поток ошибок, однако вывод можно перенаправить с помощью функции qInstallMsgHandler(). В качестве примера добавим функции отладки к многострадальному приложению просмотра изображений. Прежде всего, внесем изменения в тот модуль нашей программы, в котором определена функция main:

#include <qapplication.h>
#include <qmessagebox.h>
#include <qstring.h>
#include "form1.h"

void debugMsg(QtMsgType type, const char *msg)

{

    switch (type) {
       case QtDebugMsg:
QMessageBox::information(0, QWidget::trUtf8("Информация"),
QString(msg));
         break;

       case QtWarningMsg:
QMessageBox::warning(0, QWidget::trUtf8("Предупреждение"),
QString(msg));
         break;

       case QtFatalMsg:
QMessageBox::critical(0, QWidget::trUtf8("Критическая ошибка"),
QString(msg));
         abort();

}

}

int main(int argc, char *argv[])

{

qInstallMsgHandler(debugMsg);
QApplication app(argc, argv);
Form1 fm(0);
app.setMainWidget(&fm);
fm.show();
return app.exec();
}

В новом варианте программы мы, первым делом, определяем функцию debugMsg(). Эта функция осуществляет вывод отладочных сообщений. По умолчанию сообщения выводятся на консоль. Наш же вариант функции создает для каждого отладочного сообщения диалоговое окно QMessageBox, оформленное в соответствии с типом сообщения. Тип (QtDebugMsg, QtWarningMsg, QtFatalMsg) передается в параметре type, а текст сообщения – в параметре msg. В функции main мы вызываем функцию qInstallMsgHandler(), которая и устанавливает debugMsg в качестве стандартного обработчика сообщений об ошибках. Отметим еще раз, что функция debugMsg вызывается средой Qt в случае возникновения ошибки Qt (именно Qt, а не системной) и вызывать ее напрямую не следует. Теперь добавим отладочное сообщение в тело метода

(thumbnail)
Рис. 4. Окно Object Explorer.
void Form1::dragEnterEvent( QDragEnterEvent * event )

{

if(QUriDrag::canDecode(event))
event->accept(TRUE);
else
qWarning(event->format());

}

Теперь, если над окном приложения перетаскивается объект в неподдерживаемом формате, на экран будет выведено красивое сообщение (рис. 4).

Действительно, если ошибки в программах неизбежны, то почему бы нe сделать их красивыми? Среди других элементов Qt, предназначенных для отладки, отметим еще два макроса:

Q_ASSERT() и Q_CHECK_PTR(). Первый макрос выводит на консоль предупреждающее сообщение, если переданный ему аргумент типа bool имеет значение FALSE. Макрос упрощает Q_CHECK_PTR() проверку указателя, возвращаемого new и malloc(3).

Мы рассмотрели все основные аспекты программирования приложений Qt. Пора переходить к программированию KDE, ради которого все и затевалось.

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