Evgenii Legotckoi
Evgenii Legotckoi11. November 2015 08:25

QML - Lektion 015. Entwicklung von QML Data Mapper – Analog zu QDataWidgetMapper

Bei der Übertragung von GUI einer Software von QWidgets auf QML fehlt ein Analogon von QDataWidgetMapper für QML wurde gefunden . Diese Tatsache verdirbt etwas die Stimmung. Aber es gab nichts zu tun, außer nach anderen Möglichkeiten der Umsetzung zu suchen oder ein komplettes Fahrrad dafür zu bauen, einen eigenen Qml Data Mapper.

Dennoch ergaben Recherchen in den Weiten des Netzes ihre Ergebnisse als Beispiel aus dem Jahr 2011 von den Entwicklern von Nokia über die Implementierung von SQL Widget Mapper für BlackBerry auf Basis von Cascades (Framework für native Entwicklung auf QML Qt unter BlackBerry ). Durch abendliche Meditation über dieses Beispiel haben wir es geschafft, den Code für das aktuelle QML Qt zu kürzen, was sich sowohl beim Arbeiten unter Desktop als auch beim Arbeiten unter Android (cross -Plattform, egal wie ).

Um zu demonstrieren, wie QML Data Mapper funktioniert, wird eine Anwendung geschrieben, die mit einer SQLite-Datenbank arbeitet. Bei jedem Start schreibt die Anwendung mehrere Zeilen in die Datenbank, aus der die Daten mithilfe eines von QSqlQueryModel geerbten Datenmodells entnommen und in einer QML-TableView angezeigt werden. Im Hauptfenster der Anwendung gibt es eine Schaltfläche, durch deren Betätigung ein Dialogfenster aufgerufen wird, das Informationen über die erste Zeile der Tabelle anzeigt. Ein Doppelklick auf eine der Zeilen in der Tabelle öffnet ebenfalls ein Dialogfeld, jedoch mit Informationen zu dieser Zeile. Außerdem gibt es im Dialogfeld Schaltflächen zum Blättern durch die Informationen aus der Tabelle.


Änderungen an der QML Data Mapper-Implementierung

  1. Zunächst einmal wirkten sich die Änderungen auf den Objekttyp aus, der übergeben wird, um mit diesem Data Mapper zu arbeiten. Bei BlackBerry wurde ein Objekt der Klasse Control des Cascades-Frameworks übergeben. In der neuen Version wird ein Objekt der Klasse QObject. übergeben. Oder sofort ein Objekt der QQuickItem -Klasse, die Objekte der QML -Schnittstelle sind. Dies ist möglich, weil QQuickItem von QObject. erbt.
  2. Die zweite Änderung besteht darin, dass als Parameter des tabellarischen Datenmodellabschnitts, aus dem Daten zur Substitution in QML -Objekte übernommen werden, Variablen vom Typ QString übergeben wurden, während dies in meinem Fall der Fall war Es ist bequemer, einen Wert vom Typ * int zu übergeben, um der Rolle der Spalte im Datenmodell zu entsprechen, deren Implementierung für QML in der vorherigen Lektion zum Übergeben von Daten von QSqlQueryModel beschrieben wurde. zu QML TableView . Daher wurde der Objekttyp auf int. * geändert
  3. Ändern des Typs des Datenmodells. Im Fall von BlackBerry wurde bb::cascades:DataModel verwendet. Während diese Option mit QAbstractItemModel -Objekten funktioniert, sowie mit solchen, die von dieser Klasse geerbt wurden.
  4. Die nächste Änderung ist die Vereinfachung des Codes der Methode update() in der private Klasse QmlDataMapperPrivate , die auf den Unterschied zwischen dem Aufruf von QAbstractItemModel::data zurückzuführen ist () Methode aus der gleichnamigen Methode * bb::cascades:DataModel. *
  5. Deklarieren von addMapping() -Methoden als öffentliche Q_SLOTS anstelle der üblichen öffentlichen . Diese Änderung war darauf zurückzuführen, dass es notwendig wurde, an das QML -Objekt zu gelangen aus der C++ -Schicht , mit der QML Data Mapper arbeiten muss und die ein Dialogfeld ist, das in den folgenden Wilds der QML -Objekthierarchie versteckt ist: rootObject -> TableView -> Tab -> ItemTab -> Dialog. ... Solche Perversionen habe ich einfach nicht gemeistert. Außerdem ist Data Mapper immer noch in QML registriert und auf seine Slots wird von QML aus zugegriffen, um durch die Zeile zu scrollen, warum zum Beispiel nicht dasselbe mit addMapping() . Daher besteht keine Notwendigkeit, mit den findChild -Methoden nach dem benötigten Objekt zu suchen. Wir übergeben dieses Objekt einfach von der QML -Schicht durch die Eigenschaft id.**
  6. Neue Methode updateDate(int index) hinzugefügt. Beim Setzen von Parametern in Data Mapper erhielten QML -Objekte nicht sofort Datenaktualisierungen, sie mussten mit dieser Krücke verwässert werden.

Projektstruktur zur Demonstration der Funktionsweise von QML Data Mapper

Das Projekt besteht aus bereits bekannten Dateien aus der Lektion mit QML QSqlQueryModel, und hat auch eine neue Klasse QmlDataMapper. Und auch die Datei DialogMapper.qml wurde dem Projekt hinzugefügt, das ist ein Dialogfeld, in dem festgelegt wird, welche Daten aus dem SQLite. -Datenbankdatenmodell abgeglichen werden

QmlSqlQueryModel.pro

TEMPLATE = app

QT += qml quick widgets sql

SOURCES += main.cpp \
    database.cpp \
    model.cpp \
    qmldatamapper.cpp

RESOURCES += qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Default rules for deployment.
include(deployment.pri)

HEADERS += \
    database.h \
    model.h \
    qmldatamapper.h

Datenbank.h

Die Datenbank wird mithilfe einer Wrapper-Klasse erstellt und mit Daten gefüllt, die auch in den Lektionen zum Arbeiten mit QSqlTableModel und QSqlRelationalTableModel .

AUFMERKSAMKEIT!!! - Die Datenbankdatei wird im Ordner C:/example erstellt. Korrigieren Sie also entweder die Methode DataBase::connectToDataBase() oder erstellen Sie den Ordner example auf dem Ordner * C * Antrieb.

#ifndef DATABASE_H
#define DATABASE_H

#include <QObject>
#include <QSql>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlDatabase>
#include <QFile>
#include <QDate>
#include <QDebug>

/* Директивы имен таблицы, полей таблицы и базы данных */
#define DATABASE_HOSTNAME   "ExampleDataBase"
#define DATABASE_NAME       "DataBase.db"

#define TABLE                   "TableExample"
#define TABLE_DATE              "date"
#define TABLE_TIME              "time"
#define TABLE_MESSAGE           "message"
#define TABLE_RANDOM            "random"

class DataBase : public QObject
{
    Q_OBJECT
public:
    explicit DataBase(QObject *parent = 0);
    ~DataBase();
    /* Методы для непосредственной работы с классом
     * Подключение к базе данных и вставка записей в таблицу
     * */
    void connectToDataBase();
    bool inserIntoTable(const QVariantList &data);

private:
    // Сам объект базы данных, с которым будет производиться работа
    QSqlDatabase    db;

private:
    /* Внутренние методы для работы с базой данных
     * */
    bool openDataBase();
    bool restoreDataBase();
    void closeDataBase();
    bool createTable();
};

#endif // DATABASE_H

Datenbank.cpp

#include "database.h"

DataBase::DataBase(QObject *parent) : QObject(parent)
{
    // Подключаемся к базе данных
    this->connectToDataBase();
    /* После чего производим наполнение таблицы базы данных
     * контентом, который будет отображаться в TableView
     * */
    for(int i = 0; i < 4; i++){
        QVariantList data;
        int random = qrand(); // Получаем случайные целые числа для вставки а базу данных
        data.append(QDate::currentDate()); // Получаем текущую дату для вставки в БД
        data.append(QTime::currentTime()); // Получаем текущее время для вставки в БД
        // Подготавливаем полученное случайное число для вставки в БД
        data.append(random);
        // Подготавливаем сообщение для вставки в базу данных
        data.append("Получено сообщение от " + QString::number(random));
        // Вставляем данные в БД
        inserIntoTable(data);
    }
}

DataBase::~DataBase()
{

}

/* Методы для подключения к базе данных
 * */
void DataBase::connectToDataBase()
{
    /* Перед подключением к базе данных производим проверку на её существование.
     * В зависимости от результата производим открытие базы данных или её восстановление
     * */
    if(!QFile("C:/example/" DATABASE_NAME).exists()){
        this->restoreDataBase();
    } else {
        this->openDataBase();
    }
}

/* Методы восстановления базы данных
 * */
bool DataBase::restoreDataBase()
{
    if(this->openDataBase()){
        if(!this->createTable()){
            return false;
        } else {
            return true;
        }
    } else {
        qDebug() << "Не удалось восстановить базу данных";
        return false;
    }
    return false;
}

/* Метод для открытия базы данных
 * */
bool DataBase::openDataBase()
{
    /* База данных открывается по заданному пути
     * и имени базы данных, если она существует
     * */
    db = QSqlDatabase::addDatabase("QSQLITE");
    db.setHostName(DATABASE_HOSTNAME);
    db.setDatabaseName("C:/example/" DATABASE_NAME);
    if(db.open()){
        return true;
    } else {
        return false;
    }
}

/* Методы закрытия базы данных
 * */
void DataBase::closeDataBase()
{
    db.close();
}

/* Метод для создания таблицы в базе данных
 * */
bool DataBase::createTable()
{
    /* В данном случае используется формирование сырого SQL-запроса
     * с последующим его выполнением.
     * */
    QSqlQuery query;
    if(!query.exec( "CREATE TABLE " TABLE " ("
                            "id INTEGER PRIMARY KEY AUTOINCREMENT, "
                            TABLE_DATE      " DATE            NOT NULL,"
                            TABLE_TIME      " TIME            NOT NULL,"
                            TABLE_RANDOM    " INTEGER         NOT NULL,"
                            TABLE_MESSAGE   " VARCHAR(255)    NOT NULL"
                        " )"
                    )){
        qDebug() << "DataBase: error of create " << TABLE;
        qDebug() << query.lastError().text();
        return false;
    } else {
        return true;
    }
    return false;
}

/* Метод для вставки записи в базу данных
 * */
bool DataBase::inserIntoTable(const QVariantList &data)
{
    /* Запрос SQL формируется из QVariantList,
     * в который передаются данные для вставки в таблицу.
     * */
    QSqlQuery query;
    /* В начале SQL запрос формируется с ключами,
     * которые потом связываются методом bindValue
     * для подстановки данных из QVariantList
     * */
    query.prepare("INSERT INTO " TABLE " ( " TABLE_DATE ", "
                                             TABLE_TIME ", "
                                             TABLE_RANDOM ", "
                                             TABLE_MESSAGE " ) "
                  "VALUES (:Date, :Time, :Random, :Message )");
    query.bindValue(":Date",        data[0].toDate());
    query.bindValue(":Time",        data[1].toTime());
    query.bindValue(":Random",      data[2].toInt());
    query.bindValue(":Message",     data[3].toString());
    // После чего выполняется запросом методом exec()
    if(!query.exec()){
        qDebug() << "error insert into " << TABLE;
        qDebug() << query.lastError().text();
        return false;
    } else {
        return true;
    }
    return false;
}

Modell.h

Das Datendarstellungsmodell bleibt gegenüber der vorherigen Lektion unverändert. Ich nehme zur Kenntnis, dass Sie die Abschnittsnummer in der QML -Schicht gemäß den Rollennummern in diesem Datenmodell übertragen müssen. Das heißt, wenn die Spalte, die dem Steuerelement zugeordnet wird, die Rollennummer Qt:UserRole + 4 hat, muss die Zuordnung in der Ebene QML mit der folgenden Kombination 0x0100 erfolgen + 4.

#ifndef MODEL_H
#define MODEL_H

#include <QObject>
#include <QSqlQueryModel>

class Model : public QSqlQueryModel
{
    Q_OBJECT
public:
    /* Перечисляем все роли, которые будут использоваться в TableView
     * Как видите, они должны лежать в памяти выше параметра Qt::UserRole
     * Связано с тем, что информация ниже этого адреса не для кастомизаций
     * */
    enum Roles {
        DateRole = Qt::UserRole + 1,    // дата
        TimeRole,                       // время
        RandomRole,                     // псевдослучаное число
        MessageRole                     // сообщение
    };

    // объявляем конструктор класса
    explicit Model(QObject *parent = 0);

    // Переопределяем метод, который будет возвращать данные
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

protected:
    /* хешированная таблица ролей для колонок.
     * Метод используется в дебрях базового класса QAbstractItemModel,
     * от которого наследован класс QSqlQueryModel
     * */
    QHash<int, QByteArray> roleNames() const;

signals:

public slots:
};

#endif // MODEL_H

model.cpp

#include "model.h"

Model::Model(QObject *parent) :
    QSqlQueryModel(parent)
{
    // Конструктор будет пустой ;-)
}

// Метод для получения данных из модели
QVariant Model::data(const QModelIndex & index, int role) const {

    // Определяем номер колонки, адрес так сказать, по номеру роли
    int columnId = role - Qt::UserRole - 1;
    // Создаём индекс с помощью новоиспечённого ID колонки
    QModelIndex modelIndex = this->index(index.row(), columnId);

    /* И с помощью уже метода data() базового класса
     * вытаскиваем данные для таблицы из модели
     * */
    return QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
}

// Метод для получения имен ролей через хешированную таблицу.
QHash<int, QByteArray> Model::roleNames() const {
    /* То есть сохраняем в хеш-таблицу названия ролей
     * по их номеру
     * */
    QHash<int, QByteArray> roles;
    roles[DateRole] = "date";
    roles[TimeRole] = "time";
    roles[RandomRole] = "random";
    roles[MessageRole] = "message";
    return roles;
}

qmldatamapper.h

Und nun zur Data Mapper -Klasse selbst für QML. Diese Klasse kann ohne Änderungen auch für alle anderen Datenmodelle verwendet werden, die nach dem gleichen Prinzip wie das obige Datenmodell organisiert sind diejenigen, die von QAbstractItemModel und seinen abgeleiteten Klassen geerbt wurden.

#ifndef QMLDATAMAPPER_H
#define QMLDATAMAPPER_H

/****************************************************************************
 ** Ниже следующий программный код является результатом доработки программного
 ** кода для платформы BlackBerry 10 из Qt Toolkit и также распростряется
 ** по ниже следующей лицензии, которая была применена к исходному програмному коду
 **
 ** EVILEG - Evgenii Legotckoi - 2015
 ** Contact: http://www.evileg.com
 ** Contact: Evgenii Legotckoi (legotskoy@gmail.com)
 ** All rights reserved.
 ****************************************************************************/

/****************************************************************************
 **
 ** Portions Copyright (C) 2012 Research In Motion Limited.
 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 ** All rights reserved.
 ** Contact: Research In Motion Ltd. )
 ** Contact: Nokia Corporation (qt-info@nokia.com)
 **
 ** This file is part of the examples of the BB10 Platform and is derived
 ** from a similar file that is part of the Qt Toolkit.
 **
 ** You may use this file under the terms of the BSD license as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of Research In Motion, nor the name of Nokia
 **     Corporation and its Subsidiary(-ies), nor the names of its
 **     contributors may be used to endorse or promote products
 **     derived from this software without specific prior written
 **     permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ****************************************************************************/

#include <QObject>
#include <QPointer>
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QQuickItem>

class QmlDataMapperPrivate;

/**
 * Класс QmlDataMapper предоставляет mapping между строками модели данных и представлением в виджете QML.
 * QmlDataMapper может быть использован для создания элементов управления базой даных путём сопоставления их со строками модели данных.
 * Каждый раз, когда изменяется текущий индекс, каждый элемент управления обновляется данными из модели.
 */

class QmlDataMapper : public QObject
{
    Q_OBJECT

    /**
     * Текущий индекс QmlDataMapper. Если используется SQL модель на основе SQL таблицы, то
     * индекс будет между 0 и числом строк. .
     */
    Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)

    // Количество строк, предодставленных моделью
    Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
    explicit QmlDataMapper(QObject *parent = 0);

    ~QmlDataMapper();

    /**
     * Удаляет все сопоставления, которые были созданы методом addMapping().
     */
    void clearMapping();

    // Удаляет сопоставление по объекту из QML слоя
    void removeMapping(QObject *object);

    // Методы доступа к свойствам
    int currentIndex() const;
    int count() const;

    // Возвращает им свойства, которое используется для обновления значений в элементе управления
    QByteArray mappedPropertyName(QObject *object) const;

    // Returns the section identifier that is mapped to the given control
    int mappedSection(QObject *object) const;

    // Возвращает элемент управления, которой сопоставлено с идентификатором секции в модели
    QObject * mappedControlAt(const int &section) const;

    // Возвращает модель данных, с которой работает QmlDataMapper
    QAbstractItemModel * model() const;

    // Устанавливает модель, с которой будет работать QmlDataMapper
    void setModel(QAbstractItemModel *model);

public Q_SLOTS:
    /**
     * Этот метод создаёт сопоставление между элементов упралвения и идентификатором секции
     * в модели данных.
     * Для SQL модели идентификатором секции является но роли колонки в модели представления данных
     * Данные будут установлены в свойство элемента управления, которые зависят от свойства данного элемента управления
     * Данный метод используется для установки элемента управления и секции без наименования свойства.
     * И в данном случае используется только наименование "text", в которое будут подставляться данные
     */
    void addMapping(QObject *object, const int &section);

    /**
     * Этот метод создаёт сопоставление между элементов упралвения и идентификатором секции
     * в модели данных.
     * Для SQL модели идентификатором секции является но роли колонки в модели представления данных
     * Данные будут подставляться в элемент управление в указанное свойство
     */
    void addMapping(QObject *object, const int &section, const QByteArray &propertyName);

    // Данный метод сбрасывает данные в элементе управления
    void revert();

    // Данный метод устанавливает индекс по заданном значению
    void setCurrentIndex(int index);

    // Данный метод устанавливает индекс первой строки
    void toFirst();

    // Данный метод устанавливает индекс последней строки
    void toLast();

    // Данный метод производит инкремент текущего индекса строки
    void toNext();

    // Данный метод производит декремент текущего индекса строки
    void toPrevious();

    // Обновление данных по заданном индексу
    void updateData(int index);

Q_SIGNALS:
    // Сигналы уведомления об изменении для свойств класса
    void currentIndexChanged(int index);
    void countChanged();

private:
    // Private класс, который скрыт API данного класса
    QmlDataMapperPrivate * const d;
};

#endif // QMLDATAMAPPER_H

qmldatamapper.cpp

/****************************************************************************
 ** Ниже следующий программный код является результатом доработки программного
 ** кода для платформы BlackBerry 10 из Qt Toolkit и также распростряется
 ** по ниже следующей лицензии, которая была применена к исходному програмному коду
 **
 ** EVILEG - Evgenii Legotckoi - 2015
 ** Contact: http://www.evileg.com
 ** Contact: Evgenii Legotckoi (legotskoy@gmail.com)
 ** Contact: Евгений Легоцкой (legotskoy@gmail.com)
 ** All rights reserved.
 ****************************************************************************/

/****************************************************************************
 **
 ** Portions Copyright (C) 2012 Research In Motion Limited.
 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 ** All rights reserved.
 ** Contact: Research In Motion Ltd. )
 ** Contact: Nokia Corporation (qt-info@nokia.com)
 **
 ** This file is part of the examples of the BB10 Platform and is derived
 ** from a similar file that is part of the Qt Toolkit.
 **
 ** You may use this file under the terms of the BSD license as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of Research In Motion, nor the name of Nokia
 **     Corporation and its Subsidiary(-ies), nor the names of its
 **     contributors may be used to endorse or promote products
 **     derived from this software without specific prior written
 **     permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ****************************************************************************/

#include "qmldatamapper.h"
#include <QDataWidgetMapper>

struct Mapping {
    QPointer <QObject> object;
    int section;
    QByteArray propertyName;
};

/**
 * Private класс, который содержит все скрытые методы и переменные.
 * Используется концепция, которая позволяет изменять внутреннее API без воздействия на внешнее API
 * (see 
 */
class QmlDataMapperPrivate
{
public:
    QmlDataMapperPrivate()
        : m_model(0), m_currentIndex(-1)
    {
    }

    // Вспомогательный метод обновления участников по заданным сопоставления с заданными параметрами
    void updateMapping(Mapping &mapping, QObject *object, const int &section, const QByteArray &propertyName);

    // Данный метод устанавливает актуальные сопоставления данных из модели данных в элементы управления
    void update();

    // Модель данных, с которой производится работа
    QAbstractItemModel* m_model;

    // Список сопоставлений, котроые установленны
    QVector<Mapping> m_mappings;

    // Текущий индекс QmlDataMapper
    int m_currentIndex;
};

void QmlDataMapperPrivate::updateMapping(Mapping &mapping, QObject *object, const int &section, const QByteArray &propertyName)
{
    mapping.object = object;
    mapping.section = section;

    // Если свойство имени не задано, то по умолчанию используется свойство "text"
    mapping.propertyName = (propertyName.isEmpty() ? "text" : propertyName);
}

void QmlDataMapperPrivate::update()
{
    // Список проверок перед обновлением данных
    if (!m_model)
        return;

    if (m_mappings.isEmpty())
        return;

    if (m_currentIndex == -1)
        return;

    // Перебор всех доступных сопоставлений
    foreach (const Mapping &mapping, m_mappings) {
        if (mapping.object) {
            // Обновляем данные элементов управления, устанавливая данные в свойства по роли
            mapping.object->setProperty(mapping.propertyName, m_model->data(m_model->index(m_currentIndex,0), mapping.section));
        }
    }
}

QmlDataMapper::QmlDataMapper(QObject *parent)
        : QObject(parent), d(new QmlDataMapperPrivate())
{

}

QmlDataMapper::~QmlDataMapper()
{
    delete d;
}


void QmlDataMapper::addMapping(QObject *object, const int &section)
{
    // Добавление сопоставление с свойство по умолчанию
    addMapping(object, section, "text");
}

void QmlDataMapper::addMapping(QObject *object, const int &section, const QByteArray &propertyName)
{
    // Проверка на то, что добавляемое сопоставление уже существует ...
    for (int i = 0; i < d->m_mappings.count(); ++i) {
        Mapping &mapping = d->m_mappings[i];
        if (mapping.object == object) {
            // ... в случае существования сопоставление, производим обновление данных о секции и свойстве элемента управления ...
            d->updateMapping(mapping, object, section, propertyName);

            // ... и производим обновление содержимого элемента управления
            d->update();
            return;
        }
    }

    // В противном случае добавляем новое сопоставление
    Mapping mapping;
    d->updateMapping(mapping, object, section, propertyName);
    d->m_mappings.append(mapping);

    // ... и производим обновление содержимого элемента управления
    d->update();
}

void QmlDataMapper::clearMapping()
{
    // Очистка списка сопоставлений
    d->m_mappings.clear();
}

int QmlDataMapper::currentIndex() const
{
    return d->m_currentIndex;
}

int QmlDataMapper::count() const
{
    if (!d->m_model)
        return 0;

    // Возвращаем число строк в модели представления данных
    return d->m_model->rowCount();
}

QByteArray QmlDataMapper::mappedPropertyName(QObject *object) const
{
    foreach(const Mapping &mapping, d->m_mappings) {
        if (mapping.object == object)
        return mapping.propertyName;
    }

    return QByteArray();
}

int QmlDataMapper::mappedSection(QObject *object) const
{
    foreach(const Mapping &mapping, d->m_mappings) {
        if (mapping.object == object)
        return mapping.section;
    }

    return 0;
}

QObject* QmlDataMapper::mappedControlAt(const int &section) const
{
    foreach(const Mapping &mapping, d->m_mappings) {
        if (mapping.section == section)
        return mapping.object;
    }

    return 0;
}

QAbstractItemModel* QmlDataMapper::model() const
{
    return d->m_model;
}

void QmlDataMapper::removeMapping(QObject *object)
{
    for (int i = 0; i < d->m_mappings.count(); ++i) {
        if (d->m_mappings[i].object == object) {
            d->m_mappings.remove(i);
            return;
        }
    }
}

void QmlDataMapper::setModel(QAbstractItemModel *model)
{
    d->m_model = model;

    // Установка первоначального индекса содержимого модели представления данных
    d->m_currentIndex = 0;

    // Производим обновление содержимого элемента управления
    d->update();
    emit countChanged();
}

void QmlDataMapper::revert()
{
    // производим обновление содержимого элемента управления
    d->update();
}

void QmlDataMapper::setCurrentIndex(int index)
{
    // Проверка на наличие модели данных
    if (!d->m_model)
        return;

    // получаем число строк
    const int rowCount = d->m_model->rowCount();
    // ... игнорируем неверные значения индекса
    if (index < 0 || index >= rowCount)
        return;

    d->m_currentIndex = index;
    d->update();
    emit currentIndexChanged(d->m_currentIndex);
}

void QmlDataMapper::toFirst()
{
    setCurrentIndex(0);
}

void QmlDataMapper::toLast()
{
    if (!d->m_model)
        return;

    const int rowCount = d->m_model->rowCount();

    setCurrentIndex(rowCount - 1);
}

void QmlDataMapper::toNext()
{
    setCurrentIndex(d->m_currentIndex + 1);
}

void QmlDataMapper::toPrevious()
{
    setCurrentIndex(d->m_currentIndex - 1);
}

void QmlDataMapper::updateData(int index)
{
    // Устанавливаем требуемый индекс
    d->m_currentIndex = index;

    // и обновляем значения в элементах управления
    d->update();
    emit countChanged();
}

main.cpp

Um mit dem Data Mapper im C++ -Teil zu arbeiten, müssen Sie ihn nur deklarieren, initialisieren und den Zugriff darauf von der QML -Schicht mit der setContextProperty("mapper ", mapper). Methode. Nun, vergessen Sie nicht, das Datenrepräsentationsmodell mit der Methode setModel(). einzufügen.

Alle anderen Arbeiten mit dem Data Mapper werden in der QML-Schicht erledigt. Einschließlich der Einrichtung von Zuordnungen von Objekten zu Spalten des Datenmodells.

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

#include <qmldatamapper.h>
#include <database.h>
#include <model.h>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QQmlApplicationEngine engine;

    /// Инициализируем базу данных
    DataBase database;
    /// Объявляем и инициализируем модель представления данных
    Model *model = new Model();
    /** Поскольку Мы отнаследовались от QSqlQueryModel, то
     * для выборки данных нам необходимо выполнить SQL-запрос,
     * в котором мы выберем все необходимы поля из нужной нам таблицы
     * */
    model->setQuery("SELECT " TABLE_DATE ", " TABLE_TIME ", " TABLE_RANDOM ", " TABLE_MESSAGE
                   " FROM " TABLE);

    // Объявляем и инициализируем объект QmlDataMapper
    QmlDataMapper *mapper = new QmlDataMapper();
    mapper->setModel(model);

    /** А это уже знакомо из уроков по сигналам и слотам в QML
     * Помещаем полученную модель в контекст QML, чтобы была возможность
     * обращаться к модели по имени "myModel"
     * */
    engine.rootContext()->setContextProperty("myModel", model);

    /* А также даём доступ к Mapper в контексте QML
     * */
    engine.rootContext()->setContextProperty("mapper", mapper);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

main.qml

Diese Datei deklariert eine Ebene mit einer Schaltfläche, die beim Drücken ein Dialogfeld öffnet, und TableView , in der Daten aus dem Datenmodell angezeigt werden. Außerdem wird durch Doppelklicken auf eine Zeile in TableView ein Dialogfeld geöffnet. Die Schaltfläche öffnet ein Dialogfeld, das Informationen über die allererste Zeile in der Tabelle anzeigt. Ein Doppelklick öffnet ein Dialogfeld mit Informationen über die Zeile, auf die wir geklickt haben.

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    RowLayout {
        id: row

        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.margins: 5

        height: 35

        Button {
            id: button
            text: qsTr("Открыть Mapper")
            width: 150

            Layout.fillHeight: true

            // Открываем диалоговое окно с индексом первой строки в TableView
            onClicked: {
                dialog.show()
            }
        }

    }

    TableView {
        id: tableView
        anchors.top: row.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.margins: 5

        TableViewColumn {
            role: "date"    // Эти роли совпадают с названиями ролей в C++ модели
            title: "Date"
        }

        TableViewColumn {
            role: "time"    // Эти роли совпадают с названиями ролей в C++ модели
            title: "Time"
        }

        TableViewColumn {
            role: "random"  // Эти роли совпадают с названиями ролей в C++ модели
            title: "Random"
        }

        TableViewColumn {
            role: "message" // Эти роли совпадают с названиями ролей в C++ модели
            title: "Message"
        }

        // Устанавливаем модель в TableView
        model: myModel

        // Внешний вид строк настраивается для реализации реакции на даблКлик
        rowDelegate: Rectangle {
            anchors.fill: parent
            color: styleData.selected ? 'skyblue' : (styleData.alternate ? 'whitesmoke' : 'white');
            MouseArea {
                anchors.fill: parent
                acceptedButtons: Qt.RightButton | Qt.LeftButton
                onClicked: {
                    tableView.selection.clear()
                    tableView.selection.select(styleData.row)
                    tableView.currentRow = styleData.row
                    tableView.focus = true
                }

                onDoubleClicked: {
                    /* при даблКлике по строке открываем диалоговое окно
                     * с информацией из соответствующей строки
                     * */
                    dialog.editEntry(styleData.row)
                }
            }
        }
    }

    DialogMapper {
        id: dialog
    }
}

DialogMapper.qml

Das Dialogfeld wird in eine separate Datei geschrieben. Die Arbeit mit QML Data Mapper beschränkt sich darauf, QML -Objekte zu Data Mapper hinzuzufügen, Datenmodellzeilen umzukehren und den Zeilenindex in Data Mapper festzulegen, um Daten für a zu erhalten bestimmte Zeile aus der Tabelle, wenn das Dialogfeld durch Doppelklicken auf eine Zeile in TableView aufgerufen wird.

import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1

Dialog {
    title: qsTr("Dialog Mapper")
    height: 220
    width: 480

    // функция, которая открывает диалоговое окно с данными из первой строки
    function show() {
        open()
        mapper.updateData(0)
    }

    /* Функция, которая открывает диалоговое окно с данными из строки,
     * по которой был произведён даблКлик
     * */
    function editEntry(row) {
        open()
        mapper.updateData(row)
    }

    contentItem: Rectangle {
        implicitHeight: 220
        implicitWidth: 480

        GridLayout {
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.margins: 5
            rowSpacing: 10
            columnSpacing: 10
            rows: 4
            columns: 2

            Text {
                text: qsTr("Дата")
                Layout.fillWidth: true
            }

            TextField {
                id: dateField  // По данному id передаём объект в Data Mapper
                Layout.preferredWidth: 200
            }

            Text {
                text: qsTr("Время")
                Layout.fillWidth: true
            }

            TextField {
                id: timeField   // По данному id передаём объект в Data Mapper
                Layout.preferredWidth: 200
            }

            Text {
                text: qsTr("Случайное число")
                Layout.fillWidth: true
            }

            TextField {
                id: randomField  // По данному id передаём объект в Data Mapper
                Layout.preferredWidth: 200
            }

            Text {
                text: qsTr("Сообщение")
                Layout.fillWidth: true
            }

            TextField {
                id: messageField  // По данному id передаём объект в Data Mapper
                Layout.preferredWidth: 200
            }
        }

        Rectangle {
            color: "#eeeeee"
            height: 50
            anchors.bottom: parent.bottom
            anchors.left: parent.left
            anchors.right: parent.right

            RowLayout {
                anchors.bottom: parent.bottom
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.margins: 5
                spacing: 10

                // Кнопка пролистывает строки от последней к первой
                Button {
                    id: buttonPrevious
                    text: qsTr("Предыдущий")
                    Layout.preferredWidth: 80

                    onClicked: {
                        mapper.toPrevious()
                    }
                }

                // Кнопка пролистывает строки от первой к последней
                Button {
                    id: buttonNext
                    text: qsTr("Следующий")
                    Layout.preferredWidth: 80

                    onClicked: {
                        mapper.toNext()
                    }
                }

                Rectangle {
                    Layout.fillWidth: true
                    color: "#eeeeee"
                }

                Button {
                    id: buttonOk
                    text: qsTr("Ok")
                    Layout.preferredWidth: 80
                    onClicked: close()
                }

                Button {
                    id: buttonCancel
                    text: qsTr("Cancel")
                    Layout.preferredWidth: 80
                    onClicked: close()
                }
            }
        }
    }

    /* По результату создания диалогового окна добавляем
     * в Data Mapper объекты QML по их id, с указанием номера роли в модели данных
     * и свойством объекта, в которое будут подставляться данные.
     *
     * То есть: dateField  - это id объекта TextField
     *          0x0100 + 1 - это DateRole модели, которая равна Qt::UserRole + 1
     *          "text"     - это свойство объекта TextField, куда будут подставляться данные
     * */
    Component.onCompleted: {
        mapper.addMapping(dateField, (0x0100 + 1), "text")
        mapper.addMapping(timeField, (0x0100 + 2), "text")
        mapper.addMapping(randomField, (0x0100 + 3), "text")
        mapper.addMapping(messageField, (0x0100 + 4), "text")
    }
}

Insgesamt

Zusammenfassend können wir sagen, dass alles Neue ein vergessenes Altes ist. Tatsächlich können Sie die vorhandenen Entwicklungen auf BlackBerry schaufeln und den Programmcode auf das aktuelle QML Qt für Desktop und Android portieren, indem Sie die fehlenden oder nicht realisierten Objekte finalisieren und Klassen, um mit QML zu arbeiten. Qt-Entwickler wissen jedoch besser, wie man die Entwicklung des Frameworks erstellt.

Diese Klasse ist kein vollständiges Analogon zu QDataWidgetMapper , da sie keine Bearbeitung von Datensätzen in der Datenbank implementiert. Aber gleichzeitig erfüllt es seinen Zweck.

Das Ergebnis der Anwendung ist in der folgenden Abbildung dargestellt. Das Video-Tutorial demonstriert auch die Bedienung der Anwendung.

Der Quellcode des Projekts kann unter folgendem Link heruntergeladen werden: Projektquelle mit QmlDataMapper

Videoanleitung

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken