- 1. Projektstruktur
- 2. QmlDataBase.pro
- 3. main.cpp
- 4. Datenbank.h
- 5. Datenbank.cpp
- 6. listmodel.h
- 7. listmodel.cpp
- 8. main.qml
- 9. Insgesamt
- 10. Videoanleitung
Ein kleines Beispiel für die Arbeit mit einer Datenbank in QML Qt . Diese Lektion vereint Informationen zur Verwendung von Signalen und Slots in QML Qt , zum Zugriff auf C++ -Klassen aus der QML -Schicht, die Implementierung der Anwendungsschnittstelle in QML, sowie die Implementierung des model/view für die Datenbanktabelle.
Die Datenbank enthält eine Tabelle mit einer Personenliste, die vier Spalten hat:
- id (INTEGER) - eindeutige Datensatznummer;
- Vorname (VARCHAR (255)) - Имя;
- Nachname (VARCHAR (255)) - Nachname;
- Nik (VARCHAR (255)) - Nick.
Die Anwendung muss das Löschen und Hinzufügen von Datensätzen zur Datenbank über die Anwendungsschnittstelle implementieren. Um Datensätze zur Datenbank hinzuzufügen, werden drei Felder zur Eingabe von Daten und eine Schaltfläche verwendet, die das Hinzufügen von Daten zur SQL -Datenbank initialisiert. Das Hinzufügen von Datensätzen zur Tabelle erfolgt über die Wrapper-Klasse durch die dafür vorgesehene Methode.Auch in der Anwendung gibt es eine ListModel -Klasse, die das Datenpräsentationsmodell zur Anzeige von Informationen in der TableView in der QML-Schicht implementiert.
Projektstruktur
- QmlDataBase.pro - Projektprofil;
- database.h - Header-Datei der Wrapper-Klasse für die Arbeit mit der Datenbank;
- database.cpp - Quellcodedatei der Wrapper-Klasse für die Arbeit mit der Datenbank;
- listmodel.h - Header-Datei des Datenmodells;
- listmodel.cpp - Quellcodedatei des Datenmodells;
- main.cpp - Quelldatei der Hauptanwendung;
- main.qml - Haupt-qml-Datei.
QmlDataBase.pro
Um mit der Datenbank zu arbeiten, müssen Sie das SQL-Modul sowie das Widgets-Modul für das native Erscheinungsbild der Anwendung verbinden.
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
In die Hauptquellcodedatei fügen wir eine Wrapper-Klasse für die Arbeit mit der Datenbank und eine Datenmodellklasse ein. Objekte dieser Klassen müssen in dieser Datei deklariert und initialisiert werden, und der Zugriff auf diese Objekte und ihre Eigenschaften aus der QML-Schicht muss konfiguriert werden.
Nachdem der Zugriff auf Objekte konfiguriert wurde, werden die folgenden Eigenschaften und Funktionen des Objekts von der QML-Schicht verfügbar, die in ihrer Klasse wie folgt deklariert sind:
- Signale
- Schlitze
- Sowie die Funktionen, die im Makro Q_PROPERTY erscheinen
#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(); }
Datenbank.h
Diese Klasse deklariert Methoden zum Arbeiten mit der Datenbank:
- Methoden zum Verbinden mit der Datenbank, ihre Wiederherstellung;
- Methoden zum Hinzufügen von Datensätzen zur Datenbank;
- Methoden zum Löschen von Einträgen in der Datentabelle.
Die Verbindung zur Datenbank ist für den korrekten Betrieb des Datenmodells erforderlich, das von QSqlQueryModel . geerbt wird und dementsprechend SQL-Abfragen verwendet Datenbank in der Anwendung geöffnet.
Diese Klasse implementiert das Fassaden-Entwurfsmuster, wenn auch nicht vollständig, da, wie gesagt, eine der QSqlQueryModel -Entitäten im Datenmodell dieser Lektion verwendet wird.
AUFMERKSAMKEIT!!! - Die Datenbankdatei wird im Ordner C:/example erstellt. Korrigieren Sie also entweder die Methode DataBase::connectToDataBase() oder erstellen Sie den Ordner example auf dem Ordner * C * Antrieb.
#ifndef DATABASE_H #define DATABASE_H #include <QObject> #include <QSql> #include <QSqlQuery> #include <QSqlError> #include <QSqlDatabase> #include <QFile> #include <QDate> #include <QDebug> /* Директивы имен таблицы, полей таблицы и базы данных */ #define DATABASE_HOSTNAME "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
Datenbank.cpp
Die Datenbankverbindung wird mit der Methode connectToDataBase() initialisiert. Der Name der Tabelle, Datenbank, Datei sowie die Spalten in der Tabelle werden in den define-Direktiven in der Header-Datei definiert.
#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
Das Datenmodell ist eine von QSqlQueryModel geerbte Klasse, in der die Methoden data() und roleNames() überschrieben werden. Die Klasse listet auch die Rollen auf, von denen Informationen an die Ansicht übergeben werden in der Schnittstelle. Um Daten zu löschen, müssen Sie eine eindeutige ID des Datensatzes abrufen, die mithilfe der getID() -Methode gemäß der Rolle und Zeilennummer, die von der Ansicht übergeben wurde, aus dem Modell gezogen wird. Um Daten aus der Datenbank zu bekommen, wird die Methode updateModel() verwendet, in der die SQL-Abfrage an die Datenbank gesetzt wird.
#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
Um die Funktionsweise des Modells zu implementieren, binden wir die Header-Datei database.h , in der sich define -Direktiven für Tabellen- und Spaltennamen befinden, in die Quellcodedatei ein. Daten werden von vordefinierten Rollen zurückgegeben, die in der TableView , die zum Anzeigen der Daten verwendet wird, identisch sein müssen.
#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
In Qt wurde das Model/View/Controller-Paradigma in Model/View geändert. Eine Ansicht kombiniert einen Controller und eine Ansicht. Daher verarbeitet main.qml die Informationen, die der Benutzer eingibt, und übergibt sie in verdaulicher Form an das Backend, vorausgesetzt, dass auf das Klassenobjekt database von der QML-Schicht aus zugegriffen wird und Daten hinzugefügt werden den Funktionsslot, dann können wir bedingt davon ausgehen, dass der Controller in der View implementiert ist.
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(); // Обновляем модель данных } } }
Insgesamt
Als Ergebnis erhalten Sie eine Anwendung, die wie in der folgenden Abbildung dargestellt aussieht. Ich empfehle Ihnen auch, sich mit dem Video-Tutorial zu diesem Artikel vertraut zu machen, da es detailliertere Informationen und Ergänzungen zu diesem Programmcode enthält.
Darüber hinaus empfehle ich, mehr über das Fassaden-Designmuster zu lesen und eine vollständigere Implementierung dieses Musters im folgenden Artikel zu sehen: Fassaden-Designmuster
Добрый день, пытаюсь передать текст из 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) в этой части:
Нужно указывать другие версии, но я не могу найти какие, можете подсказать, пожалуйста?