- 1. Project structure
- 2. mainwindow.ui
- 3. main.cpp
- 4. mainwindow.h
- 5. mainwindow.cpp
- 6. database.h
- 7. database.cpp
- 8. Result
Using QSqlQueryModel to form tables in Qt is the most hardcore version with the lowest level of abstraction than the use QSqlTableModel or QSqlRelationalTableModel . But with all this, and the most flexible option, which requires a deeper knowledge of the language of SQL-queries. In a previous article , two tables were constructed:
- The main, which were columns Дата, Время, Имя Хоста, IP-адрес.
- Table devices , which were columns Имя Хоста and IP-адрес.
In the main table in the columns of the host name and IP-address points ID-devices, which carried out a substitution of data from the device tables. In this paper, the structure of the first table and therefore the table will look different:
- The main, which were columns Дата, Время, ID устройства.
- Table devices , which were columns Имя Хоста and IP-адрес.
The Generating of the main table in the Application will be SQL-query and the table will be a column respectively Дата, Время, Имя Хоста and IP-адрес.
Project structure
QSqlQueryModel project structure remains the same as in the previous article .
mainwindow.ui
Tables are also used are the same as in the previous article, we will remind ourselves of their names:
- tableView
- tableViewDevice
main.cpp
The file used in the project, being created by default.
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
This file connects library QSqlQueryModel, while QSqlRelationalTableModel classes, QSqlRelationalDelegate QSqlRelation and will not be used. Also change the method signature initialization models as object QSqlQueryModel no method to install the table name, to which will access the property.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QSqlQueryModel> /* Сonnecting the header file for the database */ #include "database.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; /* The project uses objects to interact with the database * and model representation database table * */ DataBase *db; QSqlQueryModel *modelMain; QSqlQueryModel *modelDevice; private: /* Also present are two methods that form the model * and appearance TableView * */ void setupMainModel(const QStringList &headers); void setupDeviceModel(const QStringList &headers); void createUI(); }; #endif // MAINWINDOW_H
mainwindow.cpp
The main difference between the code in the source file of the code from the previous article is that the methods used to initialize models SQL-query that is performed setQuery() method.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); this->setWindowTitle("QSqlQueryModel Example"); /* The first step is to create an object for the database * and initialize the database connection * */ db = new DataBase(); db->connectToDataBase(); /* After that produce content database tables content * to be displayed in the tableView and 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); } /* Initialize the model to represent the data indicating * the names of the columns * */ 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) { /* Initialization of data representation model * */ 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" ); /* Set the columns names in a table with sorted data * */ for(int i = 0, j = 0; i < modelMain->columnCount(); i++, j++){ modelMain->setHeaderData(i,Qt::Horizontal,headers[j]); } } void MainWindow::setupDeviceModel(const QStringList &headers) { /* Initialization of data representation model * */ modelDevice = new QSqlQueryModel(this); modelDevice->setQuery("SELECT " DEVICE ".id, " DEVICE "." DEVICE_HOSTNAME ", " DEVICE "." DEVICE_IP " FROM " DEVICE ); /* Set the columns names in a table with sorted data * */ 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); ui->tableView->setColumnHidden(0, true); 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); ui->tableViewDevice->setColumnHidden(0, true); ui->tableViewDevice->setSelectionBehavior(QAbstractItemView::SelectRows); ui->tableViewDevice->setSelectionMode(QAbstractItemView::SingleSelection); ui->tableViewDevice->resizeColumnsToContents(); ui->tableViewDevice->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->tableViewDevice->horizontalHeader()->setStretchLastSection(true); }
To update the data in the table, it is necessary to repeat the SQL-query. This is done by calling query() , which is used lastQuery() method, as shown in the code below:
model->setQuery(modelDevice->query().lastQuery());
database.h
The header file for the class, projecting facade for working with database change only two lines, compared to the previous article . Changes are shown in the code below:
/* Directives of table names, fields, tables, and databases */ #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"
database.cpp
In this file are subject to change only two methods that work with the main table. Due to the fact that the changed number of columns which are used in this table.
/* Method for creating the main table in the database * */ bool DataBase::createMainTable() { /* In this case, a forming raw SQL-query with its subsequent execution. * */ 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; } /* The method for inserting entries in the main table * */ bool DataBase::inserIntoMainTable(const QVariantList &data) { /* SQL Query formed from QVariantList, * which are transmitted in data to be inserted into the table. * */ QSqlQuery query; /* At the beginning of the SQL query is generated with the keys, * which are then linked bindValue method for substituting data from 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()); if(!query.exec()){ qDebug() << "error insert into " << TABLE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; }
Result
The visual result you should not be different from what you received in the previous lesson. It will box with two tables.
Какаим образом можно вставить в базу даннных сразу несколько строк? Ситуация такая - на 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, значит что-то там есть.
Спасибо за подсказку, получилось. Операция сравнения даты почему-то не выполнялась с использованием переменной, пришлось немного переделать запрос к базе данных.