- 1. Project structure
- 2. QmlDataBase.pro
- 3. main.cpp
- 4. database.h
- 5. database.cpp
- 6. listmodel.h
- 7. listmodel.cpp
- 8. main.qml
- 9. Conclusion
- 10. Video
A small example to work with the database in QML Qt. This lesson brings together information on the use of signals and slots in the Qt QML , access to C++ classes from QML layer implementation of the application interface on QML, as well as the implementation of the model/view for the database tables.
The database contains a table with a list of people, which has four columns:
- id (INTEGER) - a unique record number;
- FirstName (VARCHAR (255)) - First Name;
- SurName (VARCHAR (255)) - Last Name;
- Nik (VARCHAR (255)) - Nik name.
The application must implement removing and adding records to the database through the application interface. To add records to the database three fields for data entry will be used and a button that initiates the addition of data to an SQL database. Adding entries to the table through a wrapper class designed for this application is present in metodom.Takzhe ListModel class that implements the model of data representation for the information display in the TableView in QML layer.
Project structure
- QmlDataBase.pro - the profile of the project;
- database.h - wrapper class header file to work with the database;
- database.cpp - file source wrapper class codes for the work with the database;
- listmodel.h - header data model file;
- listmodel.cpp - file source data model;
- main.cpp - the main file of the application source code;
- main.qml - basic qml file.
QmlDataBase.pro
To work with the database you need to connect sql module, as well as widgets module for native application appearance.
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
Basically, source files are included wrapper classes to work with the database and data model class. Objects of these classes must be declared and initialized in the file, and configure access to these objects and their properties of QML layer.
Once access to the objects set of QML layer become available following object properties and functions are declared in its class as:
- Signals
- Slots
- As well as the functions that appear in the macro 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(); engine.rootContext()->setContextProperty("myModel", model); engine.rootContext()->setContextProperty("database", &database); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }
database.h
In this class, declared methods for working with the database:
- methods to connect to the database, restore it;
- methods for adding records to the database;
- methods for deleting entries in the data table.
Connecting to a database is necessary for the proper operation of the data model that inherit from QSqlQueryModel . And, accordingly, it uses SQL queries to open in the database application.
This class implements the Facade design pattern, although not completely, because as I said, that one of QSqlQueryModel entities used in the data model in this lesson.
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 "NameDataBase" #define DATABASE_NAME "Name.db" #define TABLE "NameTable" #define TABLE_FNAME "FisrtName" #define TABLE_SNAME "SurName" #define TABLE_NIK "Nik" 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); // Adding entries to the table bool inserIntoTable(const QString &fname, const QString &sname, const QString &nik); bool removeRecord(const int id); // Removing records from the table on its id }; #endif // DATABASE_H
database.cpp
Initialize the database connection is made by connectToDataBase() . The name of the table, database, file, and columns in the table specified in the directives define in the header file.
#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() << "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_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) { QSqlQuery query; 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()); 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) { QSqlQuery query; 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
The data model is a class inherited from QSqlQueryModel , which override the data() and roleNames() . Also listed in the class of the role in which information is transmitted in the representation in the interface. To delete data, you must obtain a unique record ID, which extends from the model by the method of getID() on the role and line number that was passed from view. To retrieve data from databases used updateModel() method, which established SQL-query the database.
#ifndef LISTMODEL_H #define LISTMODEL_H #include <QObject> #include <QSqlQueryModel> class ListModel : 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 { IdRole = Qt::UserRole + 1, // id FNameRole, // Firt name SNameRole, // Last name NikRole // Nik name }; explicit ListModel(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 the wilds of the base class QAbstractItemModel, * from which inherits the class QSqlQueryModel * */ QHash<int, QByteArray> roleNames() const; signals: public slots: void updateModel(); int getId(int row); }; #endif // LISTMODEL_H
listmodel.cpp
For the implementation of the model in a file source connect the header file database.h , which features define directive for the names of tables and columns. Return data is performed on predefined roles, which must be identical in the TableView , which will be used to display data.
#include "listmodel.h" #include "database.h" ListModel::ListModel(QObject *parent) : QSqlQueryModel(parent) { this->updateModel(); } // The method for obtaining data from the model QVariant ListModel::data(const QModelIndex & index, int role) const { // Define the column number, on the role of number int columnId = role - Qt::UserRole - 1; // Create the index using a column ID QModelIndex modelIndex = this->index(index.row(), columnId); 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; } // The method updates the tables in the data model representation void ListModel::updateModel() { // The update is performed SQL-queries to the database this->setQuery("SELECT id, " TABLE_FNAME ", " TABLE_SNAME ", " TABLE_NIK " FROM " TABLE); } // Getting the id of the row in the data view model int ListModel::getId(int row) { return this->data(this->index(row, 0), IdRole).toInt(); }
main.qml
In Qt Model / View / Controller paradigm change in the model / view . Presentation combines a controller and a view. Thus, main.qml processes the information that the user enters and passes it to the backend in a digestible form, but when you consider that the object class is given database access layer of QML, and the addition of data is made through the function slot, it can be roughly assumed that the the controller is implemented in the view.
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") 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("Add") // Make a new entry in the database onClicked: { database.inserIntoTable(fnameField.text , snameField.text, nikField.text) myModel.updateModel() // And updates the data model with a new record } } } 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: "First Name" } TableViewColumn { role: "sname" title: "Last name" } TableViewColumn { role: "nik" title: "Nik name" } model: myModel // Setting lines in TableView to intercept mouse left click 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() // Call the context menu break default: break } } } } } // The context menu offers deleting a row from the database Menu { id: contextMenu MenuItem { text: qsTr("Remove") onTriggered: { /* Call the dialog box that will clarify the intention to remove the row from the database * */ dialogDelete.open() } } } // Dialog of confirmation the removal line from the database MessageDialog { id: dialogDelete title: qsTr("Remove record") text: qsTr("Confirm the deletion of log entries") icon: StandardIcon.Warning standardButtons: StandardButton.Ok | StandardButton.Cancel // If the answer ... onAccepted: { /* ... remove the line by id, which is taken from the data model * on the line number in the presentation * */ database.removeRecord(myModel.getId(tableView.currentRow)) myModel.updateModel(); } } }
Conclusion
As a result, you get an application that will appear as shown in the following figure.
In addition, I recommend reading more about Facade design pattern and see a more complete embodiment of this pattern in the following article: "Design pattern "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) в этой части:
Нужно указывать другие версии, но я не могу найти какие, можете подсказать, пожалуйста?