Тема: Информирование пользователя об ошибках, QML
Аннотация.
QML предоставляет возможность просто и быстро разрабатывать замечательные интерфейсы, а порой его инструментария хватает и на самостоятельное приложение. Но, разумеется, не всегда. Часто основной функционал пишется на С++. О том, как организовывается взаимодействие С++ и QML, написано немало, подробно останавливаться на этом я не буду, но продемонстрирую, как это происходит. Основное внимание в этой статье я уделю одному из применений этого взаимодействия - об информировании пользователя о возникающих в процессе работы приложения ошибках.
Статья рассчитана, скорее, на новичков, но, думаю, не будет лишним просмотреть её и остальным.
Введение.
Приложение QtQuick состоит из набора .qml файлов, qmlapplicationviewer, и набора пользовательских С++ классов, некоторые из которых могут быть плагинами qml. В каждом из этих классов могут происходить различные ошибки, о которых пользователь должен быть уведомлен. Далее я предлагаю способ, как это реализовать наиболее лаконично и просто.
Приложение.
Пусть у нас есть 2 C++/Qt класса: FirstPlugin, который наследуется от QDeclarativeItem, и SecondPlugin, который наследуется от QObject. Наполнять их особым функционалом не будем, их назначение - продемонстрировать, что уведомления об ошибках, возникающих в различных C++/Qt классах будут показываться пользователю в QML интерфейсе, причем независимо от того, на какой странице приложения пользователь находится в данный момент. Итак, FirstPlugin - это наш собственный объект QML, можно было бы его визуализировать, но нам сейчас это ни к чему. Его основная функция - он совершает ошибку каждые 7 секунд.
firstplugin.h
#ifndef FIRSTPLUGIN_H
#define FIRSTPLUGIN_H
#include <QDeclarativeItem>
#include <QTimer>
/**
* Just make a mistake every 7 seconds.
*/
class FirstPlugin : public QDeclarativeItem
{
Q_OBJECT
Q_PROPERTY (QString error READ error WRITE setError
NOTIFY error)
public:
explicit FirstPlugin(QDeclarativeItem *parent = 0);
const QString &error() const;
signals:
void error(QString);
private:
QString errorInfo;
QTimer *timer;
void setError(const QString &newError);
private slots:
void makeError();
};
#endif // FIRSTPLUGIN_H
firstplugin.cpp
#include "firstplugin.h"
FirstPlugin::FirstPlugin(QDeclarativeItem *parent) :
QDeclarativeItem(parent)
{
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()),
this, SLOT(makeError()));
timer->start(7000);
}
// Get a string with error info
const QString &FirstPlugin::error() const
{
return errorInfo;
}
// Set a new error info, when emit errorChanged() signal,
// it will be available on GUI (QML).
void FirstPlugin::setError(const QString &newError)
{
errorInfo = newError;
emit error(errorInfo);
}
// Make a mistake))
void FirstPlugin::makeError()
{
setError("Error in FirstPlugin");
}
Когда происходит ошибка, испускается сигнал error(QString) с текстовым уведомлением об ошибке. Класс SecondPlugin не является объектом QML, просто класс, который может посылать сигналы. Устроен аналогично, только ошибается раз в десять секунд.
Создадим класс ErrorNotifier, который унаследуем от QDeclarativeItem, это будет наш универсальный уведомитель об ошибках. Он тоже прост:
errornotifier.h
#ifndef ERRORNOTIFIER_H
#define ERRORNOTIFIER_H
#include <QDeclarativeItem>
/**
* Notifies the user about errors.
*/
class ErrorNotifier : public QDeclarativeItem
{
Q_OBJECT
public:
explicit ErrorNotifier(QDeclarativeItem *parent = 0);
const QString &error() const;
Q_PROPERTY (QString error READ error WRITE setError
NOTIFY error)
private:
QString errorInfo;
signals:
void error(QString);
public slots:
void setError(const QString &newError);
};
#endif // ERRORNOTIFIER_H
errornotifier.cpp
#include "errornotifier.h"
ErrorNotifier::ErrorNotifier(QDeclarativeItem *parent) :
QDeclarativeItem(parent)
{
}
/// Get a string with error info
const QString &ErrorNotifier::error() const
{
return errorInfo;
}
/**
* Set a new error info, when emit errorChanged() signal,
* it will be available on GUI (QML).
*/
void ErrorNotifier::setError(const QString &newError)
{
qDebug() << "ERROR: " << newError;
errorInfo = newError;
emit error(errorInfo);
}
Вот как выглядит главный .cpp файл:
main.cpp
#include <QtGui/QApplication>
#include "qmlapplicationviewer.h"
#include "firstplugin.h"
#include "secondplugin.h"
#include "errornotifier.h"
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
QmlApplicationViewer viewer;
// Make FirstPlugin available in QML
qmlRegisterType<FirstPlugin>("FirstPlugin", 1, 0, "FirstPlugin");
// Make ErrorNotifier available in QML
qmlRegisterType<ErrorNotifier>("ErrorNotifierPlugin", 1, 0, "ErrorNotifier");
viewer.setMainQmlFile(QLatin1String("qml/ErrorsExample/main.qml"));
viewer.showExpanded();
// Get a pointer to QML object ErrorNotifier
ErrorNotifier *errorNotifier = viewer.rootObject()->findChild<ErrorNotifier*>("");
// Get a pointer to QML object ErrorNotifier
FirstPlugin *firstPlugin = viewer.rootObject()->findChild<FirstPlugin*>("");
// Create an object of secondPlugin
SecondPlugin secondPlugin;
// Connect errors signals to notifier
QObject::connect(firstPlugin, SIGNAL(error(QString)),
errorNotifier, SLOT(setError(QString)));
QObject::connect(&secondPlugin, SIGNAL(error(QString)),
errorNotifier, SLOT(setError(QString)));
return app->exec();
}
Как видно, все ошибки просто передаются уведомителю.
*Рекомендую обратить внимание, как с помощью rootObject()->findChild можно получить указатель на QML - объект, наверняка когда-нибудь пригодится.
Дальше создадим собственный QML ErrorDialog.
ErrorDialog.qml
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
import QtQuick 1.1
import com.nokia.symbian 1.1
import ErrorNotifierPlugin 1.0
Item {
id: errorDialog
ErrorNotifier {
id: errorNotifier
onError: {
dialog.open();
errorText.text = "<center>" + error + "</center>";
}
}
Dialog {
id: dialog
title: Text {
anchors.centerIn: parent
color: "white"
text: "Error"
}
content: Item {
anchors.fill: parent
Text {
id: errorText
width: parent.width * 0.9
wrapMode: Text.WordWrap
anchors.centerIn: parent
color: "white"
}
}
buttons: ButtonRow {
width: parent.width
Button {
id: okButton
text: "OK"
anchors.verticalCenter: parent.verticalCenter
onClicked: {
dialog.close();
}
}
}
onClickedOutside: {
dialog.close();
}
}
}
Собственно, всё.
Можно описать работу нашего приложения следующим образом: errorNotifier ждёт в фоновом режиме, пока в каком-нибудь из классов (first или secondplugin) не произойдет ошибка. Когда она всё же происходит, текст с сообщением попадает к errorNotifier, и он об этом сигналит (emit error(errorInfo);) В ответ на сигнал, обрабатываемый в QML, открывается диалоговое окно с текстом для пользователя.
В проекте созданы 2 страницы, на одной из которых расположен FirstPlugin, на другой вообще ничего нет. Переключаясь между ними (кликом по свободной области), и немного ожидая, можно убедиться, что пользователь получит сообщение об ошибке, где бы она ни произошла, и независимо от того, какую из страниц приложения просматривал пользователь в этот момент.
Заключение.
Я постарался продемонстрировать удобный способ уведомления пользователя об ошибках, произошедших в Qt классах. Это кажется очень просто, но почему-то часто разработчики либо вообще не думают об уведомлениях, либо реализовывают эти самые оповещения для каждого элемента (а классов и особенных ситуаций может быть много), что и порождает многократное дублирование кода и влечет к множеству ошибок и неединообразному стилю. При описанном мной выше способе вывод всех сообщений осуществляется централизованно. Если необходимо изменить внешний вид уведомления, нужно переписать часть кода лишь в одном месте - ErrorDialog.qml.
Подобный подход, естественно, можно применять и не только для ошибок. Прописав собственный объект в main.qml, вы делаете его доступным всюду.
Спасибо за внимание!