Die Verwendung von QSqlQueryModel zum Generieren von Tabellen in Qt ist die härteste Option mit einem niedrigeren Abstraktionsniveau als die Verwendung von QSqlTableModel oder QSqlRelationalTableModel . Bei all dem ist es aber auch die flexibelste Option, die tiefere Kenntnisse der SQL-Abfragesprache erfordert. Im vorherigen Artikel wurden zwei Tabellen erstellt:
- Main mit Spalten Datum, Uhrzeit, Hostname, IP-Adresse
- Gerätetabelle, mit den Spalten Hostname und IP-Adresse
In der Haupttabelle wurden in den Spalten Hostname und IP-Adresse die Geräte-IDs angegeben, mit denen die Datenersetzung aus der Gerätetabelle durchgeführt wurde. In diesem Artikel sieht der Aufbau der ersten Tabelle und damit der Tabellen anders aus:
- Main mit Spalten Datum, Uhrzeit, Geräte-ID
- Gerätetabelle mit Spalten Hostname und IP-Adresse
Die Generierung der Haupttabelle in der Anwendung wird durch eine SQL-Abfrage durchgeführt und die Tabelle hat jeweils die Spalten Datum, Uhrzeit, Hostname und IP-Adresse.
Projektstruktur für QSqlQueryModel
Die Struktur des QSqlQueryModel-Projekts bleibt dieselbe wie im vorherigen Artikel .
mainwindow.ui
Die Tabellen werden auch wie im vorherigen Artikel verwendet, erinnern Sie sich an ihre Namen:
- Tabellenansicht
- tableViewDevice
main.cpp
Die Datei wird im Projekt verwendet und wird standardmäßig erstellt.
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
Diese Datei enthält die Bibliothek QSqlQueryModel , während die Klassen QSqlRelationalTableModel , QSqlRelationalDelegate und QSqlRelation nicht verwendet werden. Die Signatur der Modellinitialisierungsmethoden wird ebenfalls geändert, da es keine Methode für das QSqlQueryModel Objekt gibt, um den Namen der Tabelle festzulegen, auf die dieses Objekt zugreift.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QSqlQueryModel> /* Подключаем заголовочный файл для работы с базой данных */ #include "database.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; /* В проекте используются объекты для работы с базой данных * и моделью представления таблицы базы данных * */ DataBase *db; QSqlQueryModel *modelMain; QSqlQueryModel *modelDevice; private: /* Также присутствуют два метода, которые формируют модель * и внешний вид TableView * */ void setupMainModel(const QStringList &headers); void setupDeviceModel(const QStringList &headers); void createUI(); }; #endif // MAINWINDOW_H
mainwindow.cpp
Der Hauptunterschied zwischen dem Code in dieser Quelldatei und dem Code im vorherigen Artikel besteht darin, dass die Methoden zum Initialisieren der Modelle eine SQL-Abfrage verwenden, die von der Methode setQuery() ausgeführt wird.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); this->setWindowTitle("QSqlQueryModel Example"); /* Первым делом необходимо создать объект для работы с базой данных * и инициализировать подключение к базе данных * */ db = new DataBase(); db->connectToDataBase(); /* После чего производим наполнение таблицы базы данных * контентом, который будет отображаться в tableView и tableViewDevice * */ for(int i = 1; i < 4; i++){ QVariantList data; data.append("Device " + QString::number(i)); data.append("172.168.13." + QString::number(i)); db->inserIntoDeviceTable(data); } for(int i = 0; i < 10; i++){ QVariantList data; QString random = QString::number(qrand() % ((4 + 1) - 1) + 1); data.append(QDate::currentDate()); data.append(QTime::currentTime()); data.append(random); db->inserIntoMainTable(data); } /* Инициализируем модели для представления данных * с заданием названий колонок * */ this->setupMainModel(QStringList() << trUtf8("id") << trUtf8("Дата") << trUtf8("Время") << trUtf8("Имя хоста") << trUtf8("IP адрес") ); this->setupDeviceModel(QStringList() << trUtf8("id") << trUtf8("Имя хоста") << trUtf8("IP адрес") ); /* Инициализируем внешний вид таблицы с данными * */ this->createUI(); } MainWindow::~MainWindow() { delete ui; } /* Метод для инициализации модели представления данных * */ void MainWindow::setupMainModel(const QStringList &headers) { /* Производим инициализацию модели представления данных * */ modelMain = new QSqlQueryModel(this); modelMain->setQuery("SELECT " TABLE ".id, " TABLE "." TABLE_DATE ", " TABLE "." TABLE_TIME ", " DEVICE "." DEVICE_HOSTNAME ", " DEVICE "." DEVICE_IP " FROM " TABLE ", " DEVICE " WHERE " DEVICE ".id = " TABLE "." TABLE_DEVICE_ID " ORDER BY " TABLE "." TABLE_DATE " DESC , " TABLE "." TABLE_TIME " DESC" ); /* Устанавливаем названия колонок в таблице с сортировкой данных * */ for(int i = 0, j = 0; i < modelMain->columnCount(); i++, j++){ modelMain->setHeaderData(i,Qt::Horizontal,headers[j]); } } void MainWindow::setupDeviceModel(const QStringList &headers) { /* Производим инициализацию модели представления данных * */ modelDevice = new QSqlQueryModel(this); modelDevice->setQuery("SELECT " DEVICE ".id, " DEVICE "." DEVICE_HOSTNAME ", " DEVICE "." DEVICE_IP " FROM " DEVICE ); /* Устанавливаем названия колонок в таблице с сортировкой данных * */ for(int i = 0, j = 0; i < modelDevice->columnCount(); i++, j++){ modelDevice->setHeaderData(i,Qt::Horizontal,headers[j]); } } void MainWindow::createUI() { ui->tableView->setModel(modelMain); // Устанавливаем модель на TableView ui->tableView->setColumnHidden(0, true); // Скрываем колонку с id записей // Разрешаем выделение строк ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows); // Устанавливаем режим выделения лишь одно строки в таблице ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection); // Устанавливаем размер колонок по содержимому ui->tableView->resizeColumnsToContents(); ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->tableView->horizontalHeader()->setStretchLastSection(true); ui->tableViewDevice->setModel(modelDevice); // Устанавливаем модель на TableView ui->tableViewDevice->setColumnHidden(0, true); // Скрываем колонку с id записей // Разрешаем выделение строк ui->tableViewDevice->setSelectionBehavior(QAbstractItemView::SelectRows); // Устанавливаем режим выделения лишь одно строки в таблице ui->tableViewDevice->setSelectionMode(QAbstractItemView::SingleSelection); // Устанавливаем размер колонок по содержимому ui->tableViewDevice->resizeColumnsToContents(); ui->tableViewDevice->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->tableViewDevice->horizontalHeader()->setStretchLastSection(true); }
Um die Daten in der Tabelle zu aktualisieren, müssen Sie die SQL-Abfrage wiederholen. Dies geschieht durch Aufrufen der Methode query() des Modells, das die Methode lastQuery() verwendet, wie im folgenden Code gezeigt:
model->setQuery(modelDevice->query().lastQuery());
database.h
Die Header-Datei für die Klasse, die als Fassade für die Arbeit mit der Datenbank fungiert, ist im Vergleich zum vorherigen Artikel nur um zwei Zeilen geändert. Die Änderungen werden im folgenden Code angezeigt:
/* Директивы имен таблицы, полей таблицы и базы данных */ #define DATABASE_HOSTNAME "ExampleDataBase" #define DATABASE_NAME "DataBase.db" #define TABLE "MainTable" #define TABLE_DATE "Date" #define TABLE_TIME "Time" #define TABLE_DEVICE_ID "DeviceID" #define DEVICE "DeviceTable" #define DEVICE_IP "IP" #define DEVICE_HOSTNAME "Hostname"
datenbank.cpp
In dieser Datei werden nur zwei Methoden geändert, die mit der Haupttabelle arbeiten. Aufgrund der Tatsache, dass sich die Anzahl der Spalten, die in dieser Tabelle verwendet werden, geändert hat.
/* Метод для создания основной таблицы в базе данных * */ bool DataBase::createMainTable() { /* В данном случае используется формирование сырого 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_DEVICE_ID " INTEGER NOT NULL" " )" )){ qDebug() << "DataBase: error of create " << TABLE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; } *** /* Метод для вставки записи в основную таблицу * */ bool DataBase::inserIntoMainTable(const QVariantList &data) { /* Запрос SQL формируется из QVariantList, * в который передаются данные для вставки в таблицу. * */ QSqlQuery query; /* В начале SQL запрос формируется с ключами, * которые потом связываются методом bindValue * для подстановки данных из QVariantList * */ query.prepare("INSERT INTO " TABLE " ( " TABLE_DATE ", " TABLE_TIME ", " TABLE_DEVICE_ID " ) " "VALUES (:Date, :Time, :ID )"); query.bindValue(":Date", data[0].toDate()); query.bindValue(":Time", data[1].toTime()); query.bindValue(":ID", data[2].toInt()); // После чего выполняется запросом методом exec() if(!query.exec()){ qDebug() << "error insert into " << TABLE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; }
Ergebnis
Ihr visuelles Ergebnis sollte sich nicht von dem unterscheiden, was Sie in der vorherigen Lektion erhalten haben. Dies wird ein Fenster mit zwei Tabellen sein.
Какаим образом можно вставить в базу даннных сразу несколько строк? Ситуация такая - на COM-порт приходят данные, непрерывно и поступают в парсер. Там, после обработки записываются в QVariantList (как в примере и далее в БД), но из-за того, что данные идут непрерывно, база данных постоянно занята и обратиться к ней невозможно, пока не отключен порт. Какаим образом в QVariantList можно записать сразу несколько строк, а потом все их отправить в БД?
Тут скорее нужно правильно написать сырой запрос и использовать QSqlQuery
Например, если есть несколько инсертов
То нужно написать соответсвующий запрос
Не уверен, что здесь поможет bind, скорее придётся подготовить аналогичную строку с использованием QVariantList и подать её в QSqlQuery.
Как можно динамически отображать данные в таблице? На COM-порт непрерывно приходят данные, я их принимаю сохраняю в БД, а после остановка приема/передачи данные отображаются в таблице. В таблице, которая нас интересует нужно отображать iD с которых приходят данные и количество пакетов, которое от них пришло. Пытаюсь сделать такvoid
MainWindow::newCountID(QByteArray refreshValueArr)
Это запись метода которая работает параллельно с БД, данные из парсера поступают в БД и в наш метод одновременно
Может вам из того метода кидать сигнал о получении новых данных и делать апдейт таблицы по тому сигналу, чтобы была новая выборка из БД для обновления таблицы?
Сигнал кидайте в конце этого метода, который Вы привели.
Не подскажете как обновить данные в ячейке? Допустим есть строка, состоящая из 2-х столбцов если данные в первом столбце совпадают с пришедшими к значению во втором столбце просто прибавляется 1. Сигнал послал, данные принял, сравнил с данными в столбце №1, а вот значения в ячейке таблицы обновить не получается.
Постоянно обращаться к БД для обновления таблицы не получится, т.к. данные идут непрерывно.
Я так полагаю, что Вам нужно тогда перпроектировать модель данных приложения.
Базу данных используйте только как хранилище, а при динамическом изменении данных, которое у вас имеется используйте модель данных, наследованную от QAbstractItemModel. Там используйте вектор со структурами строки ваших данных. При получении данных, вставляйте запись в БД и в модель данных.
Единственный коннект модели к базе данных будет через сырую выборку SQL-запросом всех необходимых данных, и новое заполнения вектора данных в модели.
Тогда модель данных не будет постоянно обращаться к БД.
При вставке новый строк обычно используют методы beginInsertRows, endInsertRows и т.д, тогда модель будет уведомлять представление об изменениях и Table View будет автоматически перерисовываться. Также метод setData нужно использовать для обновления ячеек в таблице.
Просто то, что вам требуется, это более кастомная модель данных, а не так, которая по видимому у вас используется. То есть QSqlQueryModel здесь уже не очень-то подойдёт.
Тем более, что это ReadOnly модель. Она в своём базовом функционале не позволяет записывать или обновлять данные через себя.
Из разряда "а вдруг кому-нибудь пригодится"
Для динамического обновления таблицы по мере поступления в нее данных я воспользовался контейнером QMap, т.к. у меня есть ID - они выполняют роль ключей и обновляющиеся значения (количество сообщений, которые пришли на этот ID).
Сначала пишем новый класс унаследованный от QAbstractTableMode l:
.h
.cpp
В методе, в котором идет обработка пришедших данных, в нужном месте прописываем какой-нибудь сигнал, который принимает необходимые нам данные, в моем случае это QByteArray
Коннектим наш сигнал со слотом:
Наш слот
, где mapMod:
mainwindow.h
mainwindow.cpp
} не получается подредактировать
В Qt 5.11. при попытке вставить в БД запись выдает ошибку
Попробуйте передать инстанс базы данных в конструктор QSqlQuery
Не получается
Как-то даже странно, а вы что ли в отдельный поток убрали базу данных? То есть изначально инстанс создаётся в одном потоке, а все QSqlQuery в другом потоке? Они должны находиться в одном потоке так-то.
Добрый день.
Хотел спросить вот что. Создал проект на основе QAbstractTableModel. В MainWindow cоответственно создал модель и связал с представлением. Поиск веду по списку элементов модели, потом обнуляю его и копирую в него найденные элементы. Также работает редактирование и удаление для всей строки в диалоговом окне.
Потом изменил класс на QSqlQueryModel - работает, потом на QSqlTableModel - тоже работает, тот же самый проект. Единственное изменение, в выводе всего списка в модель в первых двух последних двух использовал строчку:
а в первой вместо неё:
Возникает вопрос, в чём различие между этими классами? По идее QSqlQueryModel модель для просмотра, но и в этой модели есть возможность редактирования и удаления.
QSqlTableModel выполняет ряд стандартных операций для одной таблицы из базы данных. Поэтому там и реализован функционал по удалению и редактированию.
QSqlQueryModel позволяет выполнить запрос такой хитровыделанности, насколько хватит вашей фантазии, навыков и извращённости. Поэтому она readOnly, поскольку в таком случае невозможно прелоположить, что программисту вообще надо от SQL.
а этот код выполняет нотификацию view о начале и конце обновления таблицы и в принципе не имеет ничего общего с базой данных. Это функционал базовых абстрактных моделей, когда вы пишите кастоные модели и вам требуется в ручную формировать внешний вид представления, часто используется для формирования Tree моделей.
Здарвствуйте, подскажите, как можно добавить еще один стобец в основную таблицу формы, например, повторно вывести столбец IP (дата, время, имя хоста, IP, IP)?
Изменить запрос при создании модели.
Здравствуйте! Подскажите как сделать запрос к базе SQLite с двумя параметрами, в итоге нужно получить не список строк, а только факт наличия строк, соответсвующи именно двум условиям.
В SQL такой запрос работает, но в SQLite допускается использование только одного условия
Как можно обойти это ограничение?
мне кажется у вас просто ошибка в запросе, если у вас реально так написано в коде.
фактически у вас получится такая строка
У вас всё просто сливается в районе даты, не хватает пробелов. Потому что я не вижу проблемы, чтобы подобные запросы не работали в SQLite.
Также посмотрите в сторону оператора COUNT, который вернёт количества.
Получите результат больше 0, значит что-то там есть.
Спасибо за подсказку, получилось. Операция сравнения даты почему-то не выполнялась с использованием переменной, пришлось немного переделать запрос к базе данных.