У процесі перенесення GUI однієї софтини з QWidgets на QML виявилася відсутність аналога QDataWidgetMapper для QML . Цей факт був дещо псуючим настрій. Але робити було нічого, окрім як шукати інші способи реалізації або робити повністю свій велосипед для цього, свій Qml Data Mapper.
Проте пошуки на просторах мережі дали свої результати як приклад 2011 від розробників Nokia з реалізації SQL Widget Mapper для BlackBerry на основі Cascades (фреймворку для native розробки на QML Qt під BlackBerry, відповідно). Шляхом вечірньої медитації над цим прикладом вдалося перепиляти код для нинішнього QML Qt, який цілком непогано буде почуватися як при роботі під Desktop, так і при роботі під Android, (кроссплатформенність як ні як ).
Для демонстрації роботи QML Data Mapper буде написано програму, яка працює з базою даних SQLite. При кожному запуску програма записує кілька рядків у базу даних, з якої дані забираються за допомогою моделі даних, успадкованої від QSqlQueryModel, і відображаються в QML TableView. У головному вікні програми є кнопка, за натисканням якої викликається діалогове вікно, в якому відображається інформація про перший рядок таблиці. При подвійному кліку по одному рядку таблиці також викликається діалогове вікно, але з інформацією про цей рядок. Також у діалоговому вікні є кнопки для перегортання інформації з таблиці.
Зміни для реалізації QML Data Mapper
- Насамперед зміни торкнулися типу об'єкта, який передається до роботи даному Data Mapper . У випадку з BlackBerry передавався об'єкт класу Control фреймворку Cascades. У новому варіанті передається об'єкт класу QObject. Або одразу об'єкт класу QQuickItem, яким є об'єкти QML інтерфейс. Це можливе, оскільки QQuickItem успадкований від QObject.
- Другою зміною є те, що як параметр секції табличної моделі даних, звідки будуть братися дані для підстановки в QML об'єкти, передавалися змінні типу QString, тоді як у моєму випадку зручніше було передавати значення типу * int для зіставлення з роллю колонки в моделі даних, реалізація якої для QML була наведена в попередньому уроці передачі даних з QSqlQueryModel в QML TableView . Відтак тип об'єкта було змінено на int. *
- Зміна типу моделі даних. У випадку з BlackBerry використовувалася bb::cascades:DataModel. Тоді як цей варіант працює з об'єктами QAbstractItemModel, а також успадкованими від даного класу.
- Наступна зміна - це спрощення коду методу update() у private класі QmlDataMapperPrivate, яке було пов'язане з відмінністю виклику методу QAbstractItemModel::data() від однойменного методу * bb::cascades:DataModel. *
- Оголошення методів addMapping() як public Q_SLOTS замість звичайного public. Ця зміна була пов'язана з тим, що коли виникла необхідність докопатися до об'єкта QML з-під C++ шару , з яким повинен працювати QML Data Mapper , і який є діалоговим вікном захованим у наступних нетрах ієрархії об'єктів QML : rootObject -> TableView -> Tab -> ItemTab -> Dialog. ... Я просто не подужав такі збочення. Тим більше, що Data Mapper все одно реєструється в QML і до його слотів йде звернення з QML, щоб прогорнути рядок, наприклад, чому б теж саме не проробити і з методом addMapping(). Таким чином, відпадає потреба за допомогою методів findChild шукати потрібний Нам об'єкт. Ми просто передаємо цей об'єкт з QML шару за якістю id.
- Доданий новий метод updateDate(int index) . При установці параметрів в Data Mapper об'єкти QML відразу не отримували оновлення даних, довелося розбавити цим милицею.
Структура проекту для демонстрації роботи QML Data Mapper
Проект складається з вже відомих файлів з уроку з QML QSqlQueryModel, а також має новий клас QmlDataMapper. І також у проект доданий файл DialogMapper.qml , який є діалоговим вікном, елементам управління яких будуть зіставлятись дані з моделі даних бази даних SQLite.
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
база даних.h
База даних створюється і наповнюється даними за допомогою класу обгортки, який також використовувався і в уроках роботи з QSqlTableModel та QSqlRelationalTableModel .
УВАГА!!! - файл бази даних створюється в папці C:/example , тому або виправте метод DataBase::connectToDataBase() або створіть папку example на диску 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(); /* Методы для непосредственной работы с классом * Подключение к базе данных и вставка записей в таблицу * */ 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(); /* После чего производим наполнение таблицы базы данных * контентом, который будет отображаться в 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; }
model.h
Модель представлення даних залишається незмінною проти попереднім уроком. Зазначу, що передавати номер секції в QML шарі потрібно відповідно до номерів ролей цієї моделі даних. Тобто, якщо колонка, яка буде зіставлятися з елементом керування, має номер ролі Qt:UserRole + 4 , то в QML шарі установка відповідності повинна проводитись наступною комбінацією 0x0100 + 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
А тепер те, що стосується самого класу Data Mapper для QML. QAbstractItemModel та її похідних класів.
#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 §ion) const; // Возвращает модель данных, с которой работает QmlDataMapper QAbstractItemModel * model() const; // Устанавливает модель, с которой будет работать QmlDataMapper void setModel(QAbstractItemModel *model); public Q_SLOTS: /** * Этот метод создаёт сопоставление между элементов упралвения и идентификатором секции * в модели данных. * Для SQL модели идентификатором секции является но роли колонки в модели представления данных * Данные будут установлены в свойство элемента управления, которые зависят от свойства данного элемента управления * Данный метод используется для установки элемента управления и секции без наименования свойства. * И в данном случае используется только наименование "text", в которое будут подставляться данные */ void addMapping(QObject *object, const int §ion); /** * Этот метод создаёт сопоставление между элементов упралвения и идентификатором секции * в модели данных. * Для SQL модели идентификатором секции является но роли колонки в модели представления данных * Данные будут подставляться в элемент управление в указанное свойство */ void addMapping(QObject *object, const int §ion, 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 §ion, 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 §ion, 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 §ion) { // Добавление сопоставление с свойство по умолчанию addMapping(object, section, "text"); } void QmlDataMapper::addMapping(QObject *object, const int §ion, 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 §ion) 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
Для роботи з Data Mapper у C++ частини необхідно лише оголосити його, ініціалізувати та зареєструвати доступ до нього із QML шару за допомогою методу setContextProperty("mapper", mapper). Ну і не забути помістити в нього модель представлення даних методом setModel ().
Решта роботи з Data Mapper буде проводитися в шарі QML. У тому числі й настроювання зіставлень об'єктів із колонками моделі даних.
#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
У цьому файлі оголошуються шар з кнопкою, натисканням якої буде відкриватися діалогове вікно, і TableView, у якому відображатимуться дані з моделі даних. Також діалогове вікно відкриватиметься по подвійному кліку по рядку в TableView. Кнопка відкриватиме діалогове вікно, в якому буде відображено інформацію про перший рядок у таблиці. Подвійний клік відкриватиме діалогове вікно з інформацією про той рядок, за яким ми клікнули.
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
Діалогове вікно пишеться в окремому файлі. Робота з QML Data Mapper зводиться лише до додавання об'єктів QML у Data Mapper , перегортання рядків моделі даних, а також встановлення індексу рядка в Data Mapper для отримання даних конкретного рядка з таблиці, якщо діалогове вікно викликається подвійним кліком по рядку 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 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") } }
Підсумок
Підсумовуючи наведену інформацію, можна сказати, що все нове - це добре забуте старе. По суті можна перелопатити вже існуючі напрацювання по BlackBerry і портувати програмний код під нинішній QML Qt для Desktop та Android, доопрацювавши об'єкти та класи, що відсутні або нереалізовані, для роботи з QML. Втім, розробникам Qt видніше, як будувати розвиток фреймворку.
Цей клас не є повним аналогом QDataWidgetMapper, оскільки не реалізує редагування записів у базі даних. Але при цьому він виконує своє призначення.
Результат роботи програми наведено на наступному рисунку. Також у відеоуроці продемонстровано роботу програми.
Вихідний код проекту можна завантажити за наступним посиланням: Вихідник проекту з QmlDataMapper