|
||
![]() |
||
В этой главе будут рассмотрены некоторые из платформо-зависимых особенностей библиотеки Qt. Начнем с описания доступа к системному API: Win32 -- для Windows, Core Graphics -- для Mac OS X и Xlib -- для X11. Затем перейдем к рассмотрению расширения ActiveQt и покажем, как добавить поддержку ActiveX в свои приложения, для платформы Windows. В последнем разделе главы опишем, как можно добиться взаимодействия приложения с менеджером сессии на платформе X11.
Помимо расширений, представленных здесь, в состав версии Qt Enterprise Edition включено расширение Qt/Motif, которое облегчает миграцию приложений от Motif и Xt -- к Qt. Аналогичное расширение, для Tcl/Tk приложений предоставляет froglogic, и конвертер ресурсов Microsoft Windows -- Klaralvdalens Datakonsult. Для разработчиков устройств, Trolltech предоставляет среду исполнения приложений -- Qtopia. За дополнительной информацией обращайтесь по адресам:
Qt предоставляет достаточно мощный API, которого вполне достаточно для большинства приложений на любых платформах. Но в некоторых случаях невозможно обойтись без обращения к средствам, предоставляемым самой операционной системой. В этом разделе мы покажем, как можно организовать взаимодействие приложений с API операционной системы для выполнения специфических задач.
На всех, поддерживаемых библиотекой Qt, платформах, класс QWidget реализует функцию winId(), которая возвращает числовой идентификатор окна (HWND -- в терминах Windows). Коме того, QWidget предоставляет статическую функцию find(), которая возвращает QWidget по заданному идентификатору окна. Этот идентификатор может быть передан функциям API операционной системы для достижения эффектов, которые зависят от платформы. Например, следующий код использует winId(), чтобы добиться эффекта полупрозрачности, при отображении визуального компонента QLabel в Mac OS X, используя для этого функции графического ядра "Core Graphics". [9]
#include <qapplication.h> #include <qlabel.h> #include <qt_mac.h> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel *label = new QLabel("Hello Qt!", 0); app.setMainWidget(label); CGSWindowRef winRef = GetNativeWindowFromWindowRef((WindowRef)label->winId()); CGSSetWindowAlpha(_CGSDefaultConnection(), winRef, 0.5); label->show(); return app.exec(); }Следующий код добивается того же самого эффекта на платформе Windows, используя для этого Win32 API:
#define _WIN32_WINNT 0x0501 #include <qapplication.h> #include <qt_windows.h> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel *label = new QLabel("Hello Qt!", 0); app.setMainWidget(label); int exstyle = GetWindowLong(label->winId(), GWL_EXSTYLE); exstyle |= WS_EX_LAYERED; SetWindowLong(label->winId(), GWL_EXSTYLE, exstyle); SetLayeredWindowAttributes(label->winId(), 0, 128, LWA_ALPHA); label->show(); return app.exec(); }Этот код будет корректно работать в среде Windows 2000/XP. Если вы желаете собрать и запустить приложение в более ранних версиях Windows, которые не поддерживают полупрозрачность, вам придется использовать библиотеку QLibrary, которая определяет функцию SetLayeredWindowAttributes во время исполнения, а не во время сборки:
typedef BOOL (__stdcall *PSetLayeredWindowAttributes) (HWND, COLORREF, BYTE, DWORD); PSetLayeredWindowAttributes pSetLayeredWindowAttributes = (PSetLayeredWindowAttributes) QLibrary::resolve("user32", "SetLayeredWindowAttributes"); if (pSetLayeredWindowAttributes) { int exstyle = GetWindowLong(label->winId(), GWL_EXSTYLE); exstyle |= WS_EX_LAYERED; SetWindowLong(label->winId(), GWL_EXSTYLE, exstyle); pSetLayeredWindowAttributes(label->winId(), 0, 128, LWA_ALPHA); }Qt/Windows использует такой подход, чтобы гарантировать возможность использования расширенных особенностей, таких как поддержка Unicode и преобразования шрифтов, везде, где это возможно, допуская при этом возможность работы приложений на старых версиях Windows.
В X11 нет стандартного способа добиться эффекта полупрозрачности. Однако, в этой среде у нас есть возможность изменить свойства окна:
Atom atom = XInternAtom(win->x11Display(), "MY_PROPERTY", False); long data = 1; XChangeProperty(win->x11Display(), win->winId(), atom, atom, 32, PropModeReplace, (unsigned char *)&data, 1);Qt/Embedded отличается от всех остальных версий Qt, где все это реализуется прямо поверх буфера изображений (frame buffer) Linux, без помощи промежуточного API. Она так же предоставляет свою собственную оконную систему -- QWS. За дополнительной информацией по Qt/Embedded обращайтесь по адресам: http://www.trolltech.com/products/embedded/ и http://doc.trolltech.com/3.2/winsystem.html.
При необходимости, можно использовать платформо-зависимые особенности не в ущерб переносимости, заключая специфический код в условные операторы препроцессора #if и #endif. Например:
#if defined(Q_WS_MAC) CGSWindowRef winRef = GetNativeWindowFromWindowRef((WindowRef)label->winId()); CGSSetWindowAlpha(_CGSDefaultConnection(), winRef, 0.5); #endifДля каждой из платформ, Qt определяет один из следующих символов: Q_WS_WIN, Q_WS_X11, Q_WS_MAC или Q_WS_QWS. Перед обращением к любому из них, исходный код приложения должен подключить хотя бы один заголовочный файл библиотеки. Кроме того, Qt предоставляет следующие символы препроцессора, для определения типа операционной системы:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Некоторые классы визуальных компонентов предоставляют платформозависимую функцию handle(). На рисунке 18.1 перечислены типы значений, возвращаемых функцией handle(), в зависимости от типа платформы.
Рисунок 18.1. Типы результата, возвращаемого функцией handle().
Некоторые классы, такие как QWidget, QPixmap, QPrinter и QPicture, ведут свою родословную от класса QPaintDevice. В X11 и Mac OS X, handle() означает то же самое самое, что и winId() класса QWidget. В Windows функция handle() возвращает контекст устройства, в то время как winId() -- дескриптор окна. Похожим образом, функция hbm(), класса QPixmap, в среде Windows, возвращает дескриптор растра (HBITMAP).
В X11, класс QPaintDevice предоставляет множество функций, которые возвращают различные указатели и дескрипторы, включая x11Display() и x11Screen(). Они могут использоваться для настройки графического контекста X11 в объектах QWidget или QPixmap.
Нередки ситуации, когда приложения, написанные с использованием библиотеки Qt, должны получить доступ к низкоуровневым событиям (XEvents -- в X11, MSG -- в Windows и Mac OS X, QWSEvents -- в Qt/Embedded) прежде, чем они будут преобразованы в QEvent. Добиться этого можно, породив свой класс от QApplication и перекрыв соответствующие фильтры событий: winEventFilter(), x11EventFilter(), macEventFilter() и qwsEventFilter().
Можно получить доступ к специфическим для платформы событиям, посылаемым заданному виджету, перекрыв одну из функций: winEvent(), x11Event(), macEvent() или qwsEvent(). Этот прием может оказаться единственно возможным, для обработки событий, которые обычно игнорируются библиотекой, например: событий от джойстика.
За дополнительной информацией, касающейся платформо-зависимых проблем, включая вопросы программирования с использованием Qt/Embedded и распространения готовых приложений для различных платформ, обращайтесь по адресу: http://doc.trolltech.com/3.2/winsystem.html.
Технология Microsoft ActiveX позволяет приложениям включать интерфейсные компоненты, предоставляемые другими приложениями и библиотеками. Она базируется на технологии Microsoft COM и определяет один набор интерфейсов для приложений, которые которые используют внешние компоненты, и другой набор -- для приложений и библиотек, которые могут предоставить эти компоненты.
Версия библиотеки Qt/Windows Enterprise Edition, включает в себя ActiveQt framework, которая позволяет объединить ActiveX и Qt. ActiveQt состоит из двух модулей:
QAxContainer -- который позволяет использовать COM-объекты и встраивать в приложение элементы управления ActiveX.
QAxServer -- позволяет экспортировать наши собственные COM-объекты и элементы управления ActiveX, написанные с помощью Qt.
Рисунок 18.2. Внешний вид приложения Media Player.
class PlayerWindow : public QWidget { Q_OBJECT Q_ENUMS(ReadyStateConstants) public: enum PlayStateConstants { Stopped = 0, Paused = 1, Playing = 2 }; enum ReadyStateConstants { Uninitialized = 0, Loading = 1, Interactive = 3, Complete = 4 }; PlayerWindow(QWidget *parent = 0, const char *name = 0); protected: void timerEvent(QTimerEvent *event); private slots: void onPlayStateChange(int oldState, int newState); void onReadyStateChange(ReadyStateConstants readyState); void onPositionChange(double oldPos, double newPos); void sliderValueChanged(int newValue); void openFile();Класс PlayerWindow порожден от класса QWidget. Макрос Q_ENUMS() используется для того, чтобы сообщить утилите moc о том, что тип ReadyStateConstants, используемый слотом onReadyStateChange(), относится к перечислениям.
private: QAxWidget *wmp; QToolButton *openButton; QToolButton *playPauseButton; QToolButton *stopButton; QSlider *seekSlider; QString fileFilters; int updateTimer; };В приватной секции объявлена переменная-член типа QAxWidget *.
PlayerWindow::PlayerWindow(QWidget *parent, const char *name) : QWidget(parent, name) { ... wmp = new QAxWidget(this); wmp->setControl("{22D6F312-B0F6-11D0-94AB-0080C74C7E95}");В конструкторе создается объект QAxWidget, в который будет внедрен элемент управления ActiveX -- Windows Media Player. Модуль QAxContainer содержит три класса: QAxObject -- инкапсулирующий COM-объект, QAxWidget -- инкапсулирующий элемент ActiveX и QAxBase, реализующий базовую функциональность классов QAxObject и QAxWidget.
Рисунок 18.3. Дерево наследования в модуле QAxContainer.
Типы COM автоматически конвертируются в соответствующие типы Qt, в соответствии с таблицей, приведенной на рисунке 18.4. Так например, входной параметр типа VARIANT_BOOL преобразуется в тип bool, а выходной VARIANT_BOOL -- в bool &. Если результатом преобразования является класс Qt (QString, QDateTime и т.п.), то входной параметр получает тип константной ссылки на этот класс (например const QString &).
Рисунок 18.4. Отношения между типами COM и Qt.
wmp->setProperty("ShowControls", QVariant(false, 0)); wmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(wmp, SIGNAL(PlayStateChange(int, int)), this, SLOT(onPlayStateChange(int, int))); connect(wmp, SIGNAL(ReadyStateChange(ReadyStateConstants)), this, SLOT(onReadyStateChange(ReadyStateConstants))); connect(wmp, SIGNAL(PositionChange(double, double)), this, SLOT(onPositionChange(double, double)));Вслед за функцией setControl(), в конструкторе PlayerWindow, вызывается setProperty(), чтобы записать значение false в свойство ShowControls плеера, поскольку приложение предоставляет свои элементы управления. Функция setProperty() реализована в классе QObject и может использоваться для установки значений свойств как в COM-объектах, так и в обычных классах Qt. Ее второй аргумент имеет тип QVariant. Поскольку некоторые компиляторы C++ до сих пор не поддерживают должным образом тип bool, приходится передавать конструктору QVariant заготовку типа int. Для типов отличных от bool, преобразование проходит автоматически.
Вслед за нею вызывается setSizePolicy(), отдавая компоненту все доступное на форме пространство. После чего выполняется подключение трех событий ActiveX плеера к слотам приложения.
Оставшаяся часть конструктора выполняет обычные, для любого Qt класса, действия, за исключением, разве что, подключения трех Qt-сигналов к слотам COM-объекта (Play(), Pause() и Stop()).
Оставим конструктор в покое и рассмотрим функцию timerEvent():
void PlayerWindow::timerEvent(QTimerEvent *event) { if (event->timerId() == updateTimer) { double curPos = wmp->property("CurrentPosition").toDouble(); onPositionChange(-1, curPos); } else { QWidget::timerEvent(event); } }Эта функция регулярно вызывается через определенные интервалы времени, во время проигрывания. С ее помощью устанавливается положение движка. Значение положения движка определяется путем чтения свойства CurrentPosition элемента ActiveX, с помощью функции property().
Мы не привели остальную часть кода конструктора по той причине, что он напрямую не связан с ActiveX и не демонстрирует ничего такого, о чем бы мы не говорили ранее.
Чтобы связать приложение с модулем QAxContainer, необходимо в файл проекта добавить строку:
LIBS += -lqaxcontainerИногда возникает необходимость прямого вызова методов COM-объекта (без соединения с сигналом Qt). Самый простой способ -- вызвать функцию dynamicCall(), которой в качестве первого аргумента передать имя и сигнатуру вызываемого метода, а аргументы метода -- в виде последующих входных параметров, например:
wmp->dynamicCall("TitlePlay(uint)", 6);Функция dynamicCall() может принимать до восьми аргументов типа QVariant и возвращает значение типа QVariant. Если методу нужно передать аргумент типа IDispatch * или IUnknown *, то можно сначала инкапсулировать его в объект QAxObject, а затем вызвать метод asVariant(), чтобы преобразовать его в тип QVariant. Если метод COM-объекта возвращает значение типа IDispatch * или IUnknown, или если нужно получить доступ к свойству COM-объекта одного из этих типов, то нам придется воспользоваться функцией querySubObject():
QAxObject *session = outlook.querySubObject("Session"); QAxObject *defaultContacts = session->querySubObject("GetDefaultFolder(OlDefaultFolders)", "olFolderContacts");Если вызываемая функция имеет аргументы неподдерживаемых типов, вы должны сначала получить COM-интерфейс, вызовом функции QAxBase::queryInterface(), а затем вызвать нужный метод напрямую. Не забывайте вызвать Release(), когда надобность в интерфейсе отпадет.
Если необходимость в вызове подобных функций возникает достаточно часто, вы можете создать класс-потомок от QAxObject или от QAxWidget, и добавить функции-члены для работы с COM-интерфейсом. При этом вы должны помнить, что потомки классов QAxObject и QAxWidget не могут определять новые свойства, сигналы и слоты.
Теперь перейдем к обзору модуля QAxServer. Этот модуль позволяет превратить обычную Qt-программу в ActiveX-сервер. Сервер может быть выполнен либо в виде динамической библиотеки, либо в виде автономного приложения. Серверы, собранные в виде динамической библиотеки часто называют "внутренними" (in-process) серверами, а автономные приложения -- "внешними" (out-of-process) серверами.
Наш первый пример будет собран в виде "внутреннего" сервера, который реализует виджет, отображающий прыгающий шарик. Мы так же покажем, как встроить виджет в Internet Explorer.
Рисунок 18.5. Виджет AxBouncer в Internet Explorer.
class AxBouncer : public QWidget, public QAxBindable { Q_OBJECT Q_ENUMS(Speed) Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(Speed speed READ speed WRITE setSpeed) Q_PROPERTY(int radius READ radius WRITE setRadius) Q_PROPERTY(bool running READ isRunning)Класс AxBouncer является потомком сразу двух классов: QWidget и QAxBindable. Класс QAxBindable реализует интерфейс между виджетом и ActiveX-клиентом. Любой виджет может быть экспортирован как элемент ActiveX, но порождая его от QAxBindable, мы получаем возможность извещать клиента при изменении значений свойств виджета, а так же реализовать COM-интерфейсы, в дополнение к тем, что уже реализованы в QAxServer.
При использовании множественного наследования, с участием класса QObject, вы всегда должны указывать этот класс первым в списке предков, чтобы обеспечить корректную работу утилиты moc.
В нашем определении мы объявили три свойства, доступные для чтения/записи и одно свойство, доступное только для чтения. Макрос Q_ENUMS() сообщает утилите moc, что Speed -- это перечисление, которое объявлено в публичной секции
public: enum Speed { Slow, Normal, Fast }; AxBouncer(QWidget *parent = 0, const char *name = 0); void setSpeed(Speed newSpeed); Speed speed() const { return ballSpeed; } void setRadius(int newRadius); int radius() const { return ballRadius; } void setColor(const QColor &newColor); QColor color() const { return ballColor; } bool isRunning() const { return myTimerId != 0; } QSize sizeHint() const; QAxAggregated *createAggregate(); public slots: void start(); void stop(); signals: void bouncing();В конструкторе AxBouncer нет ничего необычного, это стандартный конструктор виджета, с аргументами parent и name. Макрос QAXFACTORY_DEFAULT(), который используется для экспорта компонента, принимает в виде параметра конструктор именно с такой сигнатурой.
Функция createAggregate() перекрывает метод родительского класса QAxBindable. К ее описанию мы вскоре вернемся.
protected: void paintEvent(QPaintEvent *event); void timerEvent(QTimerEvent *event); private: int intervalInMilliseconds() const; QColor ballColor; Speed ballSpeed; int ballRadius; int myTimerId; int x; int delta; };Защищенная и приватная секции класса не содержат ничего необычного, что отличало бы их от привычных виджетов Qt.
AxBouncer::AxBouncer(QWidget *parent, const char *name) : QWidget(parent, name, WNoAutoErase) { ballColor = blue; ballSpeed = Normal; ballRadius = 15; myTimerId = 0; x = 20; delta = 2; }Конструктор выполняет инициализацию приватных переменных.
void AxBouncer::setColor(const QColor &newColor) { if (newColor != ballColor amp;amp; requestPropertyChange("color")) { ballColor = newColor; update(); propertyChanged("color"); } }Функция setColor() записывает новое значение цвета в переменную-член color и перерисовывает виджет, вызовом функции update().
Необычными моментами здесь являются обращения к функциям requestPropertyChange() и propertyChanged(). Эти функции унаследованы от класса QAxBindable. В идеале эти функции должны вызываться в паре всякий раз, когда необходимо изменить значение свойства. Функция requestPropertyChange() запрашивает права клиента на изменение свойства и возвращает true, если клиенту позволено это делать. Функция propertyChanged() извещает клиента о том, что изменение произведено.
Функции setSpeed() и setRadius(), которые так же устанавливают новые значения свойств, следуют тому же шаблону. Аналогичные действия выполняют слоты start() и stop(), потому что они изменяют свойство running.
Осталась еще одна функция класса AxBouncer, которая представляет для нас интерес:
QAxAggregated *AxBouncer::createAggregate() { return new ObjectSafetyImpl; }Функция createAggregate() перекрывает метод предка -- QAxBindable. Она позволяет реализовать COM-интерфейс(ы), которые не реализованы в модуле QAxServer, или обойти COM-интерфейсы по-умолчанию. В данном случае возвращается интерфейс IObjectSafety, который используется в Internet Explorer, для безопасного доступа к элементам компонента. Это обычный трюк, помогающий избежать сообщения об ошибке: "Object not safe for scripting".
Ниже приводится определение класса, который реализует интерфейс IObjectSafety:
class ObjectSafetyImpl : public QAxAggregated, public IObjectSafety { public: long queryInterface(const QUuid &iid, void **iface); QAXAGG_IUNKNOWN HRESULT WINAPI GetInterfaceSafetyOptions(REFIID riid, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions); HRESULT WINAPI SetInterfaceSafetyOptions(REFIID riid, DWORD pdwSupportedOptions, DWORD pdwEnabledOptions); };Кдасс ObjectSafetyImpl порожден от QAxAggregated и IObjectSafety. Класс QAxAggregated -- это абстрактный класс, использующийся в качестве базового, для реализации дополнительных COM-интерфейсов. Доступ к COM-объекту, который является расширением QAxAggregated, может быть получен вызовом функции controllingUnknown(). Этот COM-объект негласно создается самим модулем QAxServer.
Макрос QAXAGG_IUNKNOWN подставляет стандартную реализацию функций QueryInterface(), AddRef() и Release().
long ObjectSafetyImpl::queryInterface(const QUuid &iid, void **iface) { *iface = 0; if (iid == IID_IObjectSafety) *iface = (IObjectSafety *)this; else return E_NOINTERFACE; AddRef(); return S_OK; }Функция queryInterface() вызывается программой-клиентом, управляющей COM-объектом, для того, чтобы получить доступ к интерфейсу, реализуемому классом-наследником от QAxAggregated. Для интерфейсов, реализация которых отсутствует, следует возвращать значение E_NOINTERFACE.
HRESULT WINAPI ObjectSafetyImpl::GetInterfaceSafetyOptions( REFIID riid, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions) { *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA | INTERFACESAFE_FOR_UNTRUSTED_CALLER; *pdwEnabledOptions = *pdwSupportedOptions; return S_OK; } HRESULT WINAPI ObjectSafetyImpl::SetInterfaceSafetyOptions(REFIID, DWORD, DWORD) { return S_OK; }Функции GetInterfaceSafetyOptions() и SetInterfaceSafetyOptions() объявлены в IObjectSafety. Мы выполняем их реализацию для того, чтобы сообщить вызывающей программе о том, что объект безопасен.
Теперь перейдем к содержимому файла main.cpp:
#include <qaxfactory.h> #include "axbouncer.h" QAXFACTORY_DEFAULT(AxBouncer, "{5e2461aa-a3e8-4f7a-8b04-307459a4c08c}", "{533af11f-4899-43de-8b7f-2ddf588d1015}", "{772c14a5-a840-4023-b79d-19549ece0cd9}", "{dbce1e56-70dd-4f74-85e0-95c65d86254d}", "{3f3db5e0-78ff-4e35-8a5d-3d3b96c83e09}") int main() { return 0; }Макрос QAXFACTORY_DEFAULT() экспортирует компонент ActiveX. Этот макрос используется в случаях, когда сервер экспортирует единственный элемент управления ActiveX. В противном случае, сервер должен перекрыть класс QAxFactory и использовать макрос QAXFACTORY_EXPORT(). Следующий пример в этом разделе продемонстрирует, как это делается.
Первый аргумент QAXFACTORY_DEFAULT() -- имя экспортируемого класса Qt. Остальные пять аргументов -- это GUID класса, GUID интерфейса, GUID интерфейса событий, GUID библиотеки типов и GUID приложения. Для генерации этих значений могут использоваться стандартные утилиты guidgen и uuidgen.
Поскольку наш сервер является библиотекой, мы не предусматриваем никаких действий в функции main(). Однако она нужна нам, чтобы "умиротворить" программу-компоновщик (linker).
Файл проекта для .pro нашего сервера:
TEMPLATE = lib CONFIG += activeqt dll HEADERS = axbouncer.h \ objectsafetyimpl.h SOURCES = axbouncer.cpp \ main.cpp \ objectsafetyimpl.cpp RC_FILE = qaxserver.rc DEF_FILE = qaxserver.defФайлы qaxserver.rc и qaxserver.def могут быть скопированы из подкаталога библиотеки Qt: extensions\activeqt\control.
Файл makefile или файл проекта Visual C++, созданные qmake, уже содержат правила регистрации сервера в реестре Windows. Чтобы зарегистрировать сервер на другой машине, можно воспользоваться утилитой regsvr32, которая входит в состав ОС Windows.
После этого мы можем включить компонент Bouncer в HTML-страничку, с помощью тега <object>:
<object id="AxBouncer" classid="clsid:5e2461aa-a3e8-4f7a-8b04-307459a4c08c"> <b>The ActiveX control is not available. Make sure you have built and registered the component server.</b> </object>А для управления объектом -- разместить кнопки:
<input type="button" value="Start" onClick="AxBouncer.start()"> <input type="button" value="Stop" onClick="AxBouncer.stop()">Этим объектом можно управлять из JavaScript или VBScript точно так же, как и любым другим элементом управления ActiveX.
В нашем последнем примере мы рассмотрим реализацию приложения Address Book. Это приложение может использоваться как обычная Qt/Windows программа или как "внешний" ActiveX сервер, например для программ, написанных на Visual Basic.
class AddressBook : public QMainWindow { Q_OBJECT Q_PROPERTY(int count READ count) public: AddressBook(QWidget *parent = 0, const char *name = 0); ~AddressBook(); int count() const; public slots: ABItem *createEntry(const QString &contact); ABItem *findEntry(const QString &contact) const; ABItem *entryAt(int index) const; ... };Класс AddressBook -- это виджет главного окна приложения.
class ABItem : public QObject, public QListViewItem { Q_OBJECT Q_PROPERTY(QString contact READ contact WRITE setContact) Q_PROPERTY(QString address READ address WRITE setAddress) Q_PROPERTY(QString phoneNumber READ phoneNumber WRITE setPhoneNumber) public: ABItem(QListView *listView); void setContact(const QString &contact); QString contact() const { return text(0); } void setAddress(const QString &address); QString address() const { return text(1); } void setPhoneNumber(const QString &number); QString phoneNumber() const { return text(2); } public slots: void remove(); };Класс ABItem представляет одну запись в адресной книге. Он является производным от класса QListViewItem, что позволяет отображать его в QListView. А поскольку в число его предков входит класс QObject, то это позволяет экспортировать его как COM-объект.
int main(int argc, char *argv[]) { QApplication app(argc, argv); if (!QAxFactory::isServer()) { AddressBook addressBook; app.setMainWidget(&addressBook); addressBook.show(); return app.exec(); } return app.exec(); }В функции main() выполняется проверка -- запущено ли приложение как автономная программа, или как сервер. Если программа запускается как автономное приложение, то создается главный виджет и дальше все идет как в обычных Qt-приложениях. Чтобы запустить программу как сервер, нужно передать ей ключ командной строки: -activex.
В дополнение к ключу -activex, серверы ActiveX могут принимать следующие ключи:
-regserver -- регистрирует сервер в реестре Windows.
-unregserver -- удаляет регистрацию сервера из реестра Windows.
-dumpidl file -- записывает IDL сервера в указанный файл.
QAXFACTORY_EXPORT(ABFactory, "{2b2b6f3e-86cf-4c49-9df5-80483b47f17b}", "{8e827b25-148b-4307-ba7d-23f275244818}")Макрос QAXFACTORY_EXPORT() экспортирует фабрику COM-объектов. Поскольку наше приложение экспортирует два COM-объекта, то мы уже не можем воспользоваться макросом QAXFACTORY_DEFAULT(), как это было сделано в предыдущем примере.
Первый аргумент макроса QAXFACTORY_EXPORT() -- имя класса-наследника QAxFactory, который реализует COM-объект. Другие два аргумента -- это GUID библиотеки типов и приложения.
class ABFactory : public QAxFactory { public: ABFactory(const QUuid &lib, const QUuid &app); QStringList featureList() const; QWidget *create(const QString &key, QWidget *parent, const char *name); QUuid classID(const QString &key) const; QUuid interfaceID(const QString &key) const; QUuid eventsID(const QString &key) const; QString exposeToSuperClass(const QString &key) const; };Класс ABFactory порожден от класса QAxFactory, он реализует виртуальную функцию, экспортирующую класс AddressBook, как элемент управления ActiveX, и класс ABItem, как COM-объект.
ABFactory::ABFactory(const QUuid &lib, const QUuid &app) : QAxFactory(lib, app) { }Конструктор класса ABFactory просто передает два аргумента конструктору родительского класса.
QStringList ABFactory::featureList() const { return QStringList() << "AddressBook" << "ABItem"; }Функция featureList() возвращает список COM-объектов, которые могут быть созданы фабрикой.
QWidget *ABFactory::create(const QString &key, QWidget *parent, const char *name) { if (key == "AddressBook") return new AddressBook(parent, name); else return 0; }Функция create() создает экземпляр элемента управления ActiveX. Для случая ABItem возвращается пустой указатель, поскольку пользователь не должен иметь возможность создавать объекты этого типа.
QUuid ABFactory::classID(const QString &key) const { if (key == "AddressBook") return QUuid("{588141ef-110d-4beb-95ab-ee6a478b576d}"); else if (key == "ABItem") return QUuid("{bc82730e-5f39-4e5c-96be-461c2cd0d282}"); else return QUuid(); }Функция classId() возвращает идентификаторы классов, которые могут быть экспортированы фабрикой.
QUuid ABFactory::interfaceID(const QString &key) const { if (key == "AddressBook") return QUuid("{718780ec-b30c-4d88-83b3-79b3d9e78502}"); else if (key == "ABItem") return QUuid("{c8bc1656-870e-48a9-9937-fbe1ceff8b2e}"); else return QUuid(); }Функция interfaceId() возвращает идентификаторы интерфейсов классов, экспортируемых фабрикой.
QUuid ABFactory::eventsID(const QString &key) const { if (key == "AddressBook") return QUuid("{0a06546f-9f02-4f14-a269-d6d56ffeb861}"); else if (key == "ABItem") return QUuid("{105c6b0a-3fc7-460b-ae59-746d9d4b1724}"); else return QUuid(); }Функция eventsId() возвращает идентификаторы интерфейсов событий, для экспортируемых классов.
QString ABFactory::exposeToSuperClass(const QString &key) const { return key; }По-умолчанию, элементы управления ActiveX поставляют клиентам не только свои собственные свойства, сигналы и слоты, но и свойства, сигналы и слоты своих базовых классов, вплоть до QWidget. Мы можем перекрыть метод exposeToSuperClass(), чтобы ограничить верхнюю границу (в дереве наследования) поставляемых классов.
Здесь, в виде верхней границы, мы просто возвращаем имя класса компонента ("AddressBook" или "ABItem"). Это означает, что свойства, сигналы и слоты классов-предков для AddressBook и ABItem поставляться не будут.
Ниже приводится содержимое файла проекта для нашего "внешнего" сервера ActiveX:
CONFIG += activeqt HEADERS = abfactory.h \ abitem.h \ addressbook.h \ editdialog.h SOURCES = abfactory.cpp \ abitem.cpp \ addressbook.cpp \ editdialog.cpp \ main.cpp RC_FILE = qaxserver.rcФайл qaxserver.rc может быть скопирован из подкаталога extensions\activeqt\control.
На этом мы завершаем краткий обзор ActiveQt framework. Дистрибутив библиотеки Qt включает в себя ряд дополнительных примеров, которые содержат сведения о модулях QAxContainer и QAxServer и решения наиболее общих проблем совместимости.
В момент завершения сеанса X11, некоторые оконные менеджеры выдают запрос на подтверждение завершения сеанса. Если мы подтверждаем завершение сессии, то приложения, которые работали в этот момент, будут автоматически запущены в начале следующего сеанса, с теми же экранными координатами и, в идеале, в том же самом состоянии.
Компонент X11, который управляет сохранением и восстановлением сеанса называется менеджер сеанса (или, если хотите, менеджер сессии). Чтобы добавить в Qt-приложение возможность сохранения своего состояния, в момент завершения сессии, необходимо перекрыть метод QApplication::saveState(), в котором выполнять сохранение всех необходимых параметров.
Рисунок 18.6. Окно запроса подтверждения завершения сеанса в KDE.
Момент завершения работы может быть перехвачен приложением, для этого надо перекрыть метод QApplication::commitData(). Это позволит сохранить любые несохраненные данные и запросить что либо у пользователя, если в этом возникнет необходимость. Это поведение реализуется одинаково на обеих платформах: X11 и Windows.
Мы исследуем поведение приложения, которое может взаимодействовать с менеджером сеанса, на примере программы "Крестики-нолики". Сначала рассмотрим функцию main():
int main(int argc, char *argv[]) { Application app(argc, argv); TicTacToe tic(0, "tic"); app.setTicTacToe(&tic); tic.show(); return app.exec(); }Здесь создается экземпляр класса Application, производный от класса QApplication. Этот класс перекрывает методы предка -- commitData() и saveState().
Затем создается виджет TicTacToe и выводится на экран. Виджету TicTacToe было присаоено имя "tic". Если вы хотите обеспечить взаимодействие программы с менеджером сеанса, то всем виджетам верхнего уровня должны быть присвоены уникальные имена.
Рисунок 18.7. Внешний вид приложения "Крестики-нолики".
class Application : public QApplication { Q_OBJECT public: Application(int &argc, char *argv[]); void setTicTacToe(TicTacToe *tic); void commitData(QSessionManager &sessionManager); void saveState(QSessionManager &sessionManager); private: TicTacToe *ticTacToe; };Класс Application хранит указатель на виджет TicTacToe в приватной переменной.
void Application::saveState(QSessionManager &sessionManager) { QString fileName = ticTacToe->saveState(); QStringList discardCommand; discardCommand << "rm" << fileName; sessionManager.setDiscardCommand(discardCommand); }На платформе X11, менеджер сеансов вызывает функцию saveState(), для сохранения состояния приложения. Она доступна и на других платформах, но никогда не вызывается. Аргумент типа QSessionManager позволяет взаимодействовать с менеджером сеансов.
Функция начинается с сохранения состояния виджета TicTacToe в файл. Затем менеджеру сеанса передается команда удаления. Команда удаления -- это команда, которая будет использована менеджером сеанса для удаления любой информации, имеющей отношение к текущему состоянию приложения. В данном случае команда выглядит как:
rm fileгде file -- это имя файла, в котором сохраняется информация о текущем состоянии, а rm -- это стандартная команда Unix, выполняющая удаление файлов.
Менеджеру сеанса может быть передана команда восстановления, которая будет выполнена менеджером для перезапуска приложения. По-умолчанию, Qt устанавливает команду восстановления:
appname -session id_keyгде appname берется из argv[0], id -- идентификатор сессии, поставляемый самим менеджером. Менеджер сеансов гарантирует уникальность идентификатора для каждого экземпляра приложения. И наконец key -- дополнительная информация, содержащая время сохранения состояния приложения. По различным причинам, функция saveState() может вызываться несколько раз, на протяжении одной сессии, таким образом пара id и key гарантируют уникальность каждого из сохраненных состояний.
Из-за ограничений, существующих в менеджерах сеансов, путь к исполняемому файлу приложения должен быть прописан в переменной PATH. В данном конкретном случае, если вы пожелаете испытать приложение "Крестики-нолики", вы должны переписать исполняемый файл программы в каталог, скажем, /usr/bin, и запустить ее командой tictactoe.
Для простых приложений, таких как "Крестики-нолики", состояние может быть сохранено в виде аргумента командной строки, которая перезапускает приложение в начале следующей сессии, например:
tictactoe -state OX-XO-X-OВ этом случае отпадает необходимость сохранения информации в отдельный файл и установки команды удаления файла.
void Application::commitData(QSessionManager &sessionManager) { if (ticTacToe->gameInProgress() && sessionManager.allowsInteraction()) { int ret = QMessageBox::warning(ticTacToe, tr("Tic-Tac-Toe"), tr("The game hasn t finished.\n" "Do you really want to quit?"), QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape); if (ret == QMessageBox::Yes) sessionManager.release(); else sessionManager.cancel(); } }Функция commitData() вызывается в момент завершения сеанса. Здесь выводится запрос на подтверждение завершения приложения, чтобы предотвратить потерю данных. По-умолчанию она закрывает все виджеты верхнего уровня, точно так же, как и в случае завершения приложения нажатием на кнопку "X", в заголовке окна. В Главе 3 мы уже демонстрировали, как перекрыть метод closeEvent() и вывести запрос на подтверждение.
В данном примере, мы перекрыли метод commitData() и выводим запрос на подтверждение из него, если менеджер сеанса позволяет это сделать. Когда пользователь щелкает по кнопке Yes, то вызывается функция release(), которая сообщает менеджеру сеанса о том, что он может продолжить процедуру завершения сессии. В противном случае процедура завершения сеанса будет остановлена, вызовом метода cancel().
Рисунок 18.8. Запрос на подтверждение завершения работы программы.
class TicTacToe : public QWidget { Q_OBJECT public: TicTacToe(QWidget *parent = 0, const char *name = 0); QSize sizeHint() const; bool gameInProgress() const; QString saveState() const; protected: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *event); private: enum { Empty = '-', Cross = 'X', Nought = 'O' }; void clearBoard(); void restoreState(); QString sessionFileName() const; QRect cellRect(int row, int col) const; int cellWidth() const { return width() / 3; } int cellHeight() const { return height() / 3; } char board[3][3]; int turnNumber; };Класс TicTacToe порожден от QWidget и перекрывает методы предка: sizeHint(), paintEvent() и mousePressEvent(). Он так же реализует новые методы: gameInProgress() и saveState(), которые используются классом Application.
TicTacToe::TicTacToe(QWidget *parent, const char *name) : QWidget(parent, name) { setCaption(tr("Tic-Tac-Toe")); clearBoard(); if (qApp->isSessionRestored()) restoreState(); }В конструкторе производится очистка игрового поля и, если приложение вызвано с ключом -session, то вызывается приватная функция restoreState().
void TicTacToe::clearBoard() { for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { board[row][col] = Empty; } } turnNumber = 0; }Функция clearBoard() выполняет очистку ячеек игрового поля и записывает значение 0 в переменную turnNumber (номер хода).
QString TicTacToe::saveState() const { QFile file(sessionFileName()); if (file.open(IO_WriteOnly)) { QTextStream out(&file); for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { out << board[row][col]; } } } return file.name(); }В функции saveState() производится сохранение состояния игрового поля в файл. Формат файла очень прост -- на место крестика записывается символ 'X', на место нолика -- 'O' и на место пустой ячейки -- '-'.
QString TicTacToe::sessionFileName() const { return QDir::homeDirPath() + "/.tictactoe_" + qApp->sessionId() + "_" + qApp->sessionKey(); }Функция sessionFileName() возвращает имя файла, которое соответствует текущему идентификатору и ключу сеанса. Эта функция вызывается как из saveState(), так и из restoreState().
void TicTacToe::restoreState() { QFile file(sessionFileName()); if (file.open(IO_ReadOnly)) { QTextStream in(&file); for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { in >> board[row][col]; if (board[row][col] != Empty) ++turnNumber; } } } repaint(); }Функция restoreState() загружвет файл, в котором было сохранено предыдущее состояние приложения и заполняет игровое поле. Номер хода рассчитывается как сумма крестиков и ноликов на игровом поле.
Функция restoreState() вызывается в конструкторе класса TicTacToe, если QApplication::isSessionRestored() возвращает true. В этом случае, функции sessionId() и sessionKey() возвращают те же значения, с которыми было сохранено предыдущее состояние приложения. Отсюда и sessionFileName() вернет имя файла, соответствующее этой сессии.
Отладка взаимодействия с менеджером сеанса может оказаться занятием нудным и трудоемким, поскольку придется неоднократно перезапускать сессию. К счастью, в состав X11 входит утилита xsm. На запуске, эта утилита откроет окно менеджера сеанса и терминал. Приложения, запускаемые из терминала, будут использовать xsm, в качестве менеджера сеанса. После этого мы можем завершать и перезапускать сессии и следить за поведением отлаживаемого приложения. Дополнительные сведения по этой теме вы найдете по адресу: http://doc.trolltech.com/3.2/session.html.
|