Evgenii Legotckoi
Evgenii LegotckoiNov. 11, 2015, 8:25 a.m.

QML - Lesson 015. Development QML Data Mapper – Analog of QDataWidgetMapper

In the process of transferring a GUI softiny with QWidgets on QML QDataWidgetMapper found no analogue for QML. This fact was somewhat spoiling the mood. But there was nothing, but to look for other ways to implement or make their full version of this, your Qml Data Mapper.

Nevertheless, searching on the web expanses yielded the results as an example in 2011 by Nokia developers to implement the SQL Widget Mapper for BlackBerry based on Cascades (framework for native development on QML Qt under BlackBerry, respectively). By evening meditation on the present example was able to saw through the code for the current QML Qt, which is quite nice to feel like when working under the Desktop, and when running under Android, (cross-platform no matter how how).

To demonstrate the QML Data Mapper will write an application that works with the SQLite database. Each time an application writes a few lines in a database, from which data are collected using a data model to inherit from QSqlQueryModel and displayed in QML TableView. The main window of the application there is a button, pressing that cause, which displays information on the first row of the table dialog. When you double-click on one of the rows in a table is also called a dialog box, but the information on this line. Also present button in the dialog box to scroll through the information from the table.


Changes to implement QML Data Mapper

  1. In the first place changes affected the type of object that is passed to this operation Data Mapper. In the case of BlackBerry transmitted object Control framework Cascades class. The new version of the object passed to QObject class. Or just an object of class QQuickItem , which are objects QML interface. This is possible because QQuickItem inherited from QObject.
  2. The second change is that as a section parameter tabular data models, where will be taken the data for substitution in QML objects, variables of type QString transferred, whereas in my case it was more convenient to pass an int value to match the column's role in the data model, implementation QML for which was given in the previous lesson on data from QSqlQueryModel in QML TableView. Therefore the object type has been changed to int.
  3. Changing the type of the data model. In the case of BlackBerry used bb::cascades::DataModel. While this option works with objects QAbstractItemModel, and inherit from this class.
  4. Next Change - a simplification code update() method in the class private QmlDataMapperPrivate, which was due to the difference of calling QAbstractItemModel::data() method of the same name bb::cascades::DataModel.
  5. Ad addMapping() methods as public Q_SLOTS instead of the usual public. This change was due to the fact that when it was necessary to get to the object QML from a C ++ layer, which should work QML Data Mapper, and that is the dialog box hidden in these wilds object hierarchy QML: rootObject ->TableView->Tab->ItemTab->Dialog . ... I just have not mastered such perversion. Especially because the Data Mapper still registered in QML and his handling of slots goes QML, to swipe a line, for example, why should the same not be done with addMapping() method. Thus, there is no need to search for methods of using findChild required object. We simply pass the object from QML layer by id property.
  6. A new method updateDate (int index). When setting parameters in the Data Mapper objects QML did not immediately receive the data update, the data had to be diluted with a crutch.

Projectstructure for QML Data Mapper

The project consists of the known files on a lesson with QML QSqlQueryModel , and also has a new class QmlDataMapper . And also added DialogMapper.qml project file, which is a dialog box that controls will be compared the data from SQLite database data model.

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

database.h

The database is created and filled with data using a wrapper class, which is also used in the classroom to work with QSqlTableModel and QSqlRelationalTableModel .

ATTENTION!!! - The database file is created in the folder C:/example , so the correct method or DataBase::connectToDataBase() example or create a folder on drive C .

#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();
    /* Methods to work directly with the class. 
     * Connect to the database and insert records into the table
     * */
    void connectToDataBase();
    bool inserIntoTable(const QVariantList &data);

private:
    QSqlDatabase    db;

private:
    bool openDataBase();
    bool restoreDataBase();
    void closeDataBase();
    bool createTable();
};

#endif // DATABASE_H

database.cpp

#include "database.h"

DataBase::DataBase(QObject *parent) : QObject(parent)
{
    this->connectToDataBase();
    /* After that is done filling the database tables of content that will appear in the TableView
     * */
    for(int i = 0; i < 4; i++){
        QVariantList data;
        int random = qrand(); // Get random integers to be inserted into the database
        data.append(QDate::currentDate()); // Get the current date to be inserted into the database
        data.append(QTime::currentTime()); // Get the current time to be inserted into the database
        // Prepare the received random number to be inserted into the database
        data.append(random);
        // Prepare message for insertion into the database
        data.append("Получено сообщение от " + QString::number(random));
        // Insert data into the database
        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() << "Failed to restore the database";
        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()
{
    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)
{
    QSqlQuery query;
    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());

    if(!query.exec()){
        qDebug() << "error insert into " << TABLE;
        qDebug() << query.lastError().text();
        return false;
    } else {
        return true;
    }
    return false;
}

model.h

Model data representation remains unchanged from the previous lesson. Note that the transmit section of a room in QML layer should be in accordance with the role of numbers in the data model. That is, if the column, which will be compared with the control, has the role of the number of Qt:UserRole + 4 , then QML layer matching the installation must be carried out following the combination 0x0100 + 4 .

#ifndef MODEL_H
#define MODEL_H

#include <QObject>
#include <QSqlQueryModel>

class Model : public QSqlQueryModel
{
    Q_OBJECT
public:
    /* We list all the roles that will be used in the TableView. 
     * As you can see, they have to be in the memory above the parameter Qt::UserRole. 
     * Due to the fact that the information below this address is not for customizations
     * */
    enum Roles {
        DateRole = Qt::UserRole + 1,  
        TimeRole,                       
        RandomRole,                    
        MessageRole                     
    };

    explicit Model(QObject *parent = 0);

    // Override the method that will return the data
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

protected:
    /* hashed table of roles for speakers.
     * The method used in QAbstractItemModel base class 
     * from which the class inherits QSqlQueryModel
     * */
    QHash<int, QByteArray> roleNames() const;

signals:

public slots:
};

#endif // MODEL_H

model.cpp

#include "model.h"

Model::Model(QObject *parent) :
    QSqlQueryModel(parent)
{

}

// The method for obtaining data from the model
QVariant Model::data(const QModelIndex & index, int role) const {

    // Determine the number of speakers, on the role of number
    int columnId = role - Qt::UserRole - 1;
    // Create the index using the ID column
    QModelIndex modelIndex = this->index(index.row(), columnId);

    return QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
}

// Method for role names through a hashed table.
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

Now what concerns the very Data Mapper class for QML. This class can be used without any changes to any other data models, which are organized on the principle of identity, as well as above the given data model, as well as inherited from QAbstractItemModel and its derived classes.

#ifndef QMLDATAMAPPER_H
#define QMLDATAMAPPER_H

/****************************************************************************
 ** Here the following code is the result of a software revision
 ** сode for the BlackBerry platform 10 of the Qt Toolkit and also ditributed under
 ** for the following license, which has been applied to the original software code
 **
 ** 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;

/**
 * Class QmlDataMapper provides mapping between the lines and the data model representation in the widget QML.
 * QmlDataMapper can be used to create the controls database danyh by comparing them with the data model lines.
 * Whenever changing the current index, each control element is updated with data from the model.
 */

class QmlDataMapper : public QObject
{
    Q_OBJECT

    /**
     * Current index QmlDataMapper. If you are using SQL model based on SQL tables, 
     * the index will be between 0 and the number of rows. .
     */
    Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)

    // The number of rows represented by a model
    Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
    explicit QmlDataMapper(QObject *parent = 0);

    ~QmlDataMapper();

    /**
     * It removes all mappings that have been created by addMapping ().
     */
    void clearMapping();

    // Removes the mapping for the object of QML layer
    void removeMapping(QObject *object);

    // Methods of access to properties
    int currentIndex() const;
    int count() const;

    // Return them to a property that is used to update the values in the control
    QByteArray mappedPropertyName(QObject *object) const;

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

    // It returns the control that is associated with the ID section in the model
    QObject * mappedControlAt(const int &section) const;

    // Returns data model that works QmlDataMapper
    QAbstractItemModel * model() const;

    // Sets the model that will work QmlDataMapper
    void setModel(QAbstractItemModel *model);

public Q_SLOTS:
    /**
     * This method creates a mapping between the identifier and the control section of the data model.
     * For SQL model identifier section is but the role of the column in the data view model
     * These will be installed to control property, which depend on the properties of the control
     * This method is used to set and control section without the properties here.
     * And in this case, only the name "text", in which the data will be substituted
     */
    void addMapping(QObject *object, const int &section);

    /**
     * This method creates a mapping between the identifier and the control section of the data model.
     * For SQL model identifier section is but the role of the column in the data view model
     * The data will be supplied to a control element in the specified property
     */
    void addMapping(QObject *object, const int &section, const QByteArray &propertyName);

    // This method discards the data in the control
    void revert();

    //This method sets the index at a given value
    void setCurrentIndex(int index);

    // This method sets the index of the first line
    void toFirst();

    // This method sets the index of the last row
    void toLast();

    // This method produces the increment of the current row index
    void toNext();

    // This method produces a decrement of the current row index
    void toPrevious();

    // Update the specified index data
    void updateData(int index);

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

private:
    // Private class, which is hidden in this class API
    QmlDataMapperPrivate * const d;
};

#endif // QMLDATAMAPPER_H

qmldatamapper.cpp

/****************************************************************************
 ** 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 class that contains all the hidden methods and variables.
 * It uses a concept that allows you to change the internal API without affecting the external API
 *  
 */
class QmlDataMapperPrivate
{
public:
    QmlDataMapperPrivate()
        : m_model(0), m_currentIndex(-1)
    {
    }

    // Auxiliary method of updating participants on the set matching the specified parameters
    void updateMapping(Mapping &mapping, QObject *object, const int &section, const QByteArray &propertyName);

    // This method establishes the actual data comparison of the data model in the controls
    void update();

    // The data model, which made the work
    QAbstractItemModel* m_model;

    // The list of comparisons that are installed
    QVector<Mapping> m_mappings;

    // Current index QmlDataMapper
    int m_currentIndex;
};

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

    // If the property name is not specified, it defaults to "text" property
    mapping.propertyName = (propertyName.isEmpty() ? "text" : propertyName);
}

void QmlDataMapperPrivate::update()
{
    // List of checks before the data update
    if (!m_model)
        return;

    if (m_mappings.isEmpty())
        return;

    if (m_currentIndex == -1)
        return;

    // Enumerating all the available comparisons
    foreach (const Mapping &mapping, m_mappings) {
        if (mapping.object) {
            // Updating controls, setting data on the properties of the role
            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)
{
    // Adding a comparison with the default property
    addMapping(object, section, "text");
}

void QmlDataMapper::addMapping(QObject *object, const int &section, const QByteArray &propertyName)
{
    // Verification that the added mapping already exists ...
    for (int i = 0; i < d->m_mappings.count(); ++i) {
        Mapping &mapping = d->m_mappings[i];
        if (mapping.object == object) {
            // ... in the case of the existence of the comparison, produce update data section and control properties ...
            d->updateMapping(mapping, object, section, propertyName);

            // ... and updating content control
            d->update();
            return;
        }
    }

    // Otherwise, add the new mapping
    Mapping mapping;
    d->updateMapping(mapping, object, section, propertyName);
    d->m_mappings.append(mapping);

    // ... and updating content control
    d->update();
}

void QmlDataMapper::clearMapping()
{
    // Cleaning Comparison list
    d->m_mappings.clear();
}

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

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

    // Return the number of rows in the data view model
    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;

    // Setting the initial contents of the index data representation model
    d->m_currentIndex = 0;

    // We produce content update control
    d->update();
    emit countChanged();
}

void QmlDataMapper::revert()
{
    // We produce updated content control
    d->update();
}

void QmlDataMapper::setCurrentIndex(int index)
{
    //Check the availability of the data model
    if (!d->m_model)
        return;

    // We obtain the number of lines
    const int rowCount = d->m_model->rowCount();
    // ... We ignore the incorrect value of the index
    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)
{
    // Set the desired index
    d->m_currentIndex = index;

    // and update the values in the controls
    d->update();
    emit countChanged();
}

main.cpp

To use the Data Mapper in C++ part need only declare it, initialize and register to access it from QML layer using the method setContextProperty( "mapper", mapper). And do not forget to put it in the model data representation method setModel() .

The rest of the work with the Data Mapper will be made in QML layer. Including setting comparison object data model columns.

#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;

    /// Initialize database
    DataBase database;
    /// We declare and initialize the data model representation
    Model *model = new Model();
    /** Since we otnasledovalis from QSqlQueryModel, 
     * the data sample, we need to perform SQL-query 
     * in which we select all the needed fields from the desired table to us
     * */
    model->setQuery("SELECT " TABLE_DATE ", " TABLE_TIME ", " TABLE_RANDOM ", " TABLE_MESSAGE
                   " FROM " TABLE);

    // We declare and initialize the objectQmlDataMapper
    QmlDataMapper *mapper = new QmlDataMapper();
    mapper->setModel(model);

    /** And it is already familiar from the lessons of the signals and slots in QML. 
     * We put the resulting model in QML context to be able to refer to the model name "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

This file layer declared button, by pressing which will open a dialog box, and TableView, in which data from the data model will be displayed. Also, the dialog box will be opened by double-clicking on an entry in the TableView . The button will open a dialog box in which information will be displayed on the very first line in the table. Double-click to open a dialog with information about that line on which we clicked.

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("Open Mapper")
            width: 150

            Layout.fillHeight: true

            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"    // These roles are roles names match with a C ++ model
            title: "Date"
        }

        TableViewColumn {
            role: "time"    // These roles are roles names match with a C ++ model
            title: "Time"
        }

        TableViewColumn {
            role: "random"  // These roles are roles names match with a C ++ model
            title: "Random"
        }

        TableViewColumn {
            role: "message" // These roles are roles names match with a C ++ model
            title: "Message"
        }

        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: {
                    /* doubleclick on the line at the open information 
                     * of the corresponding line dialog box
                     * */
                    dialog.editEntry(styleData.row)
                }
            }
        }
    }

    DialogMapper {
        id: dialog
    }
}

DialogMapper.qml

The dialog is written in a separate file. Working with QML Data Mapper is reduced only to the addition of QML objects in the Data Mapper, Flipped rows of data models, as well as installing the line index in the Data Mapper data for a particular row of the table, if the dialog box by double clicking on a line in the TableView .

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, which opens with the data from the first line of the dialog box
    function show() {
        open()
        mapper.updateData(0)
    }

    /* The function, which opens with the line of the data dialog box, 
     * on which was promoted doubleclick
     * */
    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  // With this id we send object to Data Mapper
                Layout.preferredWidth: 200
            }

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

            TextField {
                id: timeField   // With this id we send object to Data Mapper
                Layout.preferredWidth: 200
            }

            Text {
                text: qsTr("Random number")
                Layout.fillWidth: true
            }

            TextField {
                id: randomField  //  With this id we send object to Data Mapper
                Layout.preferredWidth: 200
            }

            Text {
                text: qsTr("Message")
                Layout.fillWidth: true
            }

            TextField {
                id: messageField  // With this id we send object to 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("Previous")
                    Layout.preferredWidth: 80

                    onClicked: {
                        mapper.toPrevious()
                    }
                }

                Button {
                    id: buttonNext
                    text: qsTr("Next")
                    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()
                }
            }
        }
    }

    /* According to the result of the creation of the dialog box to add 
     * a Data Mapper QML objects by their id, with an indication of the role of numbers 
     * in the data model and object property, which will be substituted in the data.
     *
     * То есть: dateField  - this id object TextField
     *          0x0100 + 1 - this DateRole of model, which equal Qt::UserRole + 1
     *          "text"     - this property of TextField object
     * */
    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")
    }
}

Conclusion

Summing up the above given information, we can say that everything is new - is well forgotten old. In fact, you can shovel existing developments on BlackBerry and port code under current QML Qt for Desktop and Android , elaborating or missing unrealized objects and classes for working with QML. However, Qt developers to know better how to build a development framework.

This class is not a complete analog QDataWidgetMapper, because it does not implement edit entries in the database. But at the same time it performs its intended purpose.

You can download the source code of the project on the following link: The source project QmlDataMapper

Video

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

Comments

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • April 26, 2024, 11:56 a.m.

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
d
  • dsfs
  • April 26, 2024, 11:45 a.m.

C++ - Test 002. Constants

  • Result:50points,
  • Rating points-4
d
  • dsfs
  • April 26, 2024, 11:35 a.m.

C++ - Test 001. The first program and data types

  • Result:73points,
  • Rating points1
Last comments
k
kmssrFeb. 9, 2024, 2:43 a.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 6:30 p.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 4:38 p.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 19, 2023, 5:01 a.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
IscanderChe
IscanderCheApril 30, 2024, 11:22 a.m.
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…
G
GarApril 22, 2024, 12:46 p.m.
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil AcademicsApril 20, 2024, 2:45 p.m.
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasovApril 14, 2024, 1:41 p.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел ДорофеевApril 14, 2024, 9:35 a.m.
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь

Follow us in social networks