- 1. Структура проекта
- 2. QmlDataBase.pro
- 3. main.cpp
- 4. база даних.h
- 5. database.cpp
- 6. listmodel.h
- 7. listmodel.cpp
- 8. main.qml
- 9. Підсумок
- 10. Відеоурок
Невеликий приклад роботи з базою даних в QML Qt . У даному уроці зводиться докупи інформація про застосування сигналів і слотів у QML Qt , про доступ до C++ класів з QML шару, реалізації інтерфейсу програми на QML, а також про реалізацію моделі/уявлення для таблиці бази даних.
База даних містить таблицю зі списком людей, в якій є чотири колонки:
- id (INTEGER) - унікальний номер запису;
- Ім'я (VARCHAR (255)) - Імя;
- SurName (VARCHAR (255)) - Прізвище;
- Nik (VARCHAR (255)) - Нік.
Програма має реалізовувати видалення та додавання записів до бази даних через інтерфейс програми. Для додавання записів до Бази даних буде використовуватися три поля для введення даних та кнопка, яка ініціалізує додавання даних до бази даних SQL . Додавання записів у таблицю здійснюється через клас обгортку призначеним для цього методом. Також у додатку присутній клас ListModel , який реалізує модель представлення даних для відображення інформації в TableView у шарі QML.
Структура проекта
- QmlDataBase.pro - профайл проекту;
- database.h - заголовний файл класу-обертки для роботи з базою даних;
- database.cpp - файл вихідних кодів класу-обгортки для роботи з базою даних;
- listmodel.h - заголовний файл моделі даних;
- listmodel.cpp - файл вихідних кодів моделі даних;
- main.cpp - основний файл вихідних кодів програми;
- main.qml - основний файл qml.
QmlDataBase.pro
Для роботи з базою даних необхідно підключити модуль sql, а також модуль widgets для зовнішнього вигляду програми.
TEMPLATE = app QT += qml quick widgets sql SOURCES += main.cpp \ database.cpp \ listmodel.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 \ listmodel.h
main.cpp
В основному файлі вихідних кодів підключаємо клас-обертку для роботи з базою даних та клас моделі даних. Об'єкти цих класів необхідно оголосити та ініціалізувати в даному файлі, а також налаштувати доступ до цих об'єктів та їх властивостей із шару QML.
Після того, як доступ до об'єктів налаштований, з шару QML стають доступні наступні властивості та функції об'єкта, які оголошені в його класі як:
- Сигналів
- Слотів
- А також функції, що фігурують у макросі Q_PROPERTY
#include <QApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "database.h" #include "listmodel.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; // Подключаемся к базе данных DataBase database; database.connectToDataBase(); // Объявляем и инициализируем модель данных ListModel *model = new ListModel(); // Обеспечиваем доступ к модели и классу для работы с базой данных из QML engine.rootContext()->setContextProperty("myModel", model); engine.rootContext()->setContextProperty("database", &database); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }
база даних.h
У даному класі оголошено методи для роботи з базою даних:
- способи для підключення до бази даних, її відновлення;
- методи для додавання запису до бази даних;
- методи видалення записів у таблиці данных.
Підключення до бази даних потрібне для правильної роботи моделі даних, яка успадкована від QSqlQueryModel . І відповідно використовує SQL запити до відкритої в додатку бази даних.
Цей клас реалізує патерн проектування Facade, хоча й не повністю, оскільки, як я вже сказав, що одна з сутностей QSqlQueryModel використовується в моделі даних у цьому уроці.
УВАГА!!! - файл бази даних створюється в папці 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 "NameDataBase" #define DATABASE_NAME "Name.db" #define TABLE "NameTable" // Название таблицы #define TABLE_FNAME "FisrtName" // Вторая колонка #define TABLE_SNAME "SurName" // Третья колонка #define TABLE_NIK "Nik" // Четвертая колонка // Первая колонка содержит Autoincrement ID class DataBase : public QObject { Q_OBJECT public: explicit DataBase(QObject *parent = 0); ~DataBase(); /* Методы для непосредственной работы с классом * Подключение к базе данных и вставка записей в таблицу * */ void connectToDataBase(); private: // Сам объект базы данных, с которым будет производиться работа QSqlDatabase db; private: /* Внутренние методы для работы с базой данных * */ bool openDataBase(); // Открытие базы данных bool restoreDataBase(); // Восстановление базы данных void closeDataBase(); // Закрытие базы данных bool createTable(); // Создание базы таблицы в базе данных public slots: bool inserIntoTable(const QVariantList &data); // Добавление записей в таблицу bool inserIntoTable(const QString &fname, const QString &sname, const QString &nik); bool removeRecord(const int id); // Удаление записи из таблицы по её id }; #endif // DATABASE_H
database.cpp
Ініціалізація підключення до бази даних провадиться методом connectToDataBase() . Назва таблиці, бази даних, файлу, а також колонок у таблиці визначені у директивах define у заголовному файлі.
#include "database.h" DataBase::DataBase(QObject *parent) : QObject(parent) { } DataBase::~DataBase() { } /* Методы для подключения к базе данных * */ void DataBase::connectToDataBase() { /* Перед подключением к базе данных производим проверку на её существование. * В зависимости от результата производим открытие базы данных или её восстановление * */ if(!QFile("C:/example/" DATABASE_NAME).exists()){ this->restoreDataBase(); } else { this->openDataBase(); } } /* Методы восстановления базы данных * */ bool DataBase::restoreDataBase() { // Если база данных открылась ... if(this->openDataBase()){ // Производим восстановление базы данных return (this->createTable()) ? true : false; } 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_FNAME " VARCHAR(255) NOT NULL," TABLE_SNAME " VARCHAR(255) NOT NULL," TABLE_NIK " 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_FNAME ", " TABLE_SNAME ", " TABLE_NIK " ) " "VALUES (:FName, :SName, :Nik)"); query.bindValue(":FName", data[0].toString()); query.bindValue(":SName", data[1].toString()); query.bindValue(":Nik", data[2].toString()); // После чего выполняется запросом методом exec() if(!query.exec()){ qDebug() << "error insert into " << TABLE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; } /* Второй метод для вставки записи в базу данных * */ bool DataBase::inserIntoTable(const QString &fname, const QString &sname, const QString &nik) { QVariantList data; data.append(fname); data.append(sname); data.append(nik); if(inserIntoTable(data)) return true; else return false; } /* Метод для удаления записи из таблицы * */ bool DataBase::removeRecord(const int id) { // Удаление строки из базы данных будет производитсья с помощью SQL-запроса QSqlQuery query; // Удаление производим по id записи, который передается в качестве аргумента функции query.prepare("DELETE FROM " TABLE " WHERE id= :ID ;"); query.bindValue(":ID", id); // Выполняем удаление if(!query.exec()){ qDebug() << "error delete row " << TABLE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; }
listmodel.h
Модель даних є класом, успадкованим від QSqlQueryModel, у якому перевизначені методи data() і roleNames(). Також у класі перераховані ролі, якими передається інформація на представлення в інтерфейсі. Для видалення даних необхідно отримувати унікальний ID запису, який витягується з моделі за методом getID() за роллю та номером рядка, який був переданий з уявлення. Для отримання даних із Бази даних використовується метод updateModel(), у якому встановлюється SQL-запит до Бази даних.
#ifndef LISTMODEL_H #define LISTMODEL_H #include <QObject> #include <QSqlQueryModel> class ListModel : public QSqlQueryModel { Q_OBJECT public: /* Перечисляем все роли, которые будут использоваться в TableView * Как видите, они должны лежать в памяти выше параметра Qt::UserRole * Связано с тем, что информация ниже этого адреса не для кастомизаций * */ enum Roles { IdRole = Qt::UserRole + 1, // id FNameRole, // имя SNameRole, // фамилия NikRole // ник }; // объявляем конструктор класса explicit ListModel(QObject *parent = 0); // Переопределяем метод, который будет возвращать данные QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; protected: /* хешированная таблица ролей для колонок. * Метод используется в дебрях базового класса QAbstractItemModel, * от которого наследован класс QSqlQueryModel * */ QHash<int, QByteArray> roleNames() const; signals: public slots: void updateModel(); int getId(int row); }; #endif // LISTMODEL_H
listmodel.cpp
Для реалізації роботи моделі файл вихідних кодів підключаємо заголовний файл database.h, у якому є директиви define для назв таблиці і колонок. Повернення даних здійснюється за визначеними ролями, які мають бути ідентичними в TableView, який буде використаний для відображення даних.
#include "listmodel.h" #include "database.h" ListModel::ListModel(QObject *parent) : QSqlQueryModel(parent) { this->updateModel(); } // Метод для получения данных из модели QVariant ListModel::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> ListModel::roleNames() const { /* То есть сохраняем в хеш-таблицу названия ролей * по их номеру * */ QHash<int, QByteArray> roles; roles[IdRole] = "id"; roles[FNameRole] = "fname"; roles[SNameRole] = "sname"; roles[NikRole] = "nik"; return roles; } // Метод обновления таблицы в модели представления данных void ListModel::updateModel() { // Обновление производится SQL-запросом к базе данных this->setQuery("SELECT id, " TABLE_FNAME ", " TABLE_SNAME ", " TABLE_NIK " FROM " TABLE); } // Получение id из строки в модели представления данных int ListModel::getId(int row) { return this->data(this->index(row, 0), IdRole).toInt(); }
main.qml
У Qt парадигма моделі/виду/контролера змінена на модель/виставу. Уявлення поєднує в собі контролер та вигляд. Таким чином, main.qml обробляє інформацію, яку вводить користувач і передає її в backend у зручному вигляді, але якщо врахувати, що до об'єкта класу database дано доступ із QML шару, а додавання даних здійснюється через функцію слот, то умовно можна прийняти, що контролер реалізований у поданні.
import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.2 ApplicationWindow { visible: true width: 640 height: 480 title: qsTr("Hello World") // Слой с TaxtField`ами и Button для занесения записей в базу данных RowLayout { id: rowLayout anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: 5 spacing: 10 Text {text: qsTr("Имя")} TextField {id: fnameField} Text {text: qsTr("Фамилия")} TextField { id: snameField} Text {text: qsTr("НИК")} TextField {id: nikField} Button { text: qsTr("Добавить") // Вносим новую запись в базу данных onClicked: { database.inserIntoTable(fnameField.text , snameField.text, nikField.text) myModel.updateModel() // И обновляем модель данных с новой записью } } } TableView { id: tableView anchors.top: rowLayout.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: 5 TableViewColumn { role: "fname" title: "Имя" } TableViewColumn { role: "sname" title: "Фамилия" } TableViewColumn { role: "nik" title: "НИК" } model: myModel // Настройка строки в TableView для перехавата левого клика мыши 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 switch(mouse.button) { case Qt.RightButton: contextMenu.popup() // Вызываем контексткное меню break default: break } } } } } // Контекстно меню предлагает удаление строки из базы данных Menu { id: contextMenu MenuItem { text: qsTr("Удалить") onTriggered: { /* Вызываем диалоговое окно, * которое уточнит намерение удалить строку из базы данных * */ dialogDelete.open() } } } // Диалог подтверждения удаления строки из базы данных MessageDialog { id: dialogDelete title: qsTr("Удаление записи") text: qsTr("Подтвердите удаление записи из журнала") icon: StandardIcon.Warning standardButtons: StandardButton.Ok | StandardButton.Cancel // При положительном ответе ... onAccepted: { /* ... удаляем строку по id, * который забираем из модели данных * по номеру строки в представлении * */ database.removeRecord(myModel.getId(tableView.currentRow)) myModel.updateModel(); // Обновляем модель данных } } }
Підсумок
В результаті Ви отримаєте програму, яка буде виглядати так, як показано на наступному рисунку. Також рекомендую до ознайомлення відеоурок за цією статтею, оскільки в ньому є докладніша інформація та доповнення за цим програмним кодом.
Крім цього рекомендую почитати докладніше про патерн проектування Facade і переглянути більш повний варіант реалізації даного патерну в статті: Шаблон проектування "Фасад"
Добрый день, пытаюсь передать текст из QML слоя в C++ при нажатии на кнопку. При нажатии на кнопку выводит ошибку.
что-то мне сдаётся, что здесь просто пересобрать проект нужно с удалением build каталога
Добрый день, подскажите пожалуйста как сделать изменение данных в таблице из запущенного приложения
Добрый день. В статье про это как раз и говорится. Там для этого есть метод inserIntoTable и он в сатье используется.
Добрый день, пытаюсь разобраться и подргнать пример под себя. Есть бд с огромным количеством полей. В приложении на виджетах при использовании QTableView все работает и путем простого sql запроса может вывести сразу всю таблицу. В qml же приложении я так понял жизненно необходимо определять роли. от этого никак не уйти? И как поступать когда ролей порядка 40, а таблиц много.
Второй вопрос : как ваш класс listmodel связывается с бд, т.е. каким образом он понимает , что она существует. проштудировал ваш пример и нигде нет ни ссылки ни намека на класс database, кроме подключения хедера. При попытке воспроизвести ваш класч listview в методе update в дебаге выдает сообщение о том что не удалось открыть бд. Заранее спасибо
Добрый день! можно как то обойтись без метода updateModel()? После вызова этого метода происходит перерисовка страницы(если я правильно понимаю), и все элементы, например, CheckBox перерисовываются и это очень заметно пользователю, либо если элемент имеет свойство скрыть/отобразить, то он принимает свое первоначальное состояние, а если в этом элементе работаешь и он после updateModel() скрывается не очень хорошо, опять разворачивай. Пробывал emit dataChanged(index, index, {role}) но ничего не происходит, модель не обновляет данные, только после updateModel(), происходит обновление модели, хотя в qml я меняю значение модели onClicked: {model.flag = checked;}. Может кто что подскажет?
updateModel() используется для выборки данных из базы данных, поэтому естественно, что если не выбирать данные, то ничего не обновится.
Что касается сохранения скрытого или развёрнутого состояния, то там нужно разбираться с временным сохранением состояния и после использования updateModel() восстанавливать состояние таблицы.
Проблема в том, что просто - это не сделать. Потребуется приличное количество дополнительного кода.
Помогите, пожалуйста. У меня похожая задача, но я в qml слой долен передать не чистый запрос, а со сложной обработкой, поэтому у меня в С++ слое есть иерархия классов, которая имитирует бд и заполняется при включении приложения, но после того, как там всё посчитается, мне нужно отобразить это в qml слое и тут я сломался. Никак не могу понять, как я могу из плюсового слоя наплодить элементов list view в qml слое? Выходит, мне из плюсового слоя нужно как-то заполнять делегат qml слоя и отображать его. Вообще не могу вшарить, как это происходит.
Добрый день. Я тоже присоеденяюсь к вопросу:
[s]"Второй вопрос : как ваш класс listmodel связывается с бд, т.е. каким образом он понимает , что она существует. проштудировал ваш пример и нигде нет ни ссылки ни намека на класс database, кроме подключения хедера. При попытке воспроизвести ваш класч listview в методе update в дебаге выдает сообщение о том что не удалось открыть бд. Заранее спасибо"[/s]
Смотрите, QSqlDatabase имеет статический метод addDatabase("QSQLITE"), который создаёт соединение с базой данных и возвращает инстанс базы данных
Поэтому все дальнейшие манипуляции с базой данных, если инстанс всего один, выполняются автоматически. Qt сам внутри себя хранит подключение к базе данных и выполняет все запросы через созданное соединение. Поэтому все SQL модели уже знают куда обращаться.
А сама инициализация этого подключения в данном кода выполняется в файле main.cpp
Здравствуйте, возникает такая проблема (я новичок):
ApplicationWindow неизвестный элемент. (М300)
для TextField и Button аналогично.
Могу предположить, что из-за более новой версии(я использую 6.7.2) в этой части:
Нужно указывать другие версии, но я не могу найти какие, можете подсказать, пожалуйста?