1

Тема: Информирование пользователя об ошибках, 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, вы делаете его доступным всюду.

Спасибо за внимание!

Post's attachments

ErrorsExample.zip 24.72 kb, 3 загрузок с 2012-10-11 

У Вас недостаточно прав для загрузки файлов, прикрепленных к этому сообщению.