Использование QSqlQueryModel для формирования таблиц в Qt является наиболее хардкорным вариантом с наиболее низким уровнем абстракции, чем использование QSqlTableModel или QSqlRelationalTableModel . Но при всём при этом и наиболее гибкий вариант, который требует более глубокого знания языка SQL-запросов. В предыдущей статье были построены две таблицы:
- Основная , в которой были колонки Дата, Время, Имя Хоста, IP-адрес.
- Таблица устройств, в которой были колонки Имя Хоста и IP-адрес.
В Основной таблице в колонках Имени Хоста и IP-адреса указывались ID-устройств, по которым проводилась подстановка данных из Таблицы устройств. В данной статье структура первой таблицы и следовательно таблицы будут выглядеть иначе:
- Основная , в которой были колонки Дата, Время, ID устройства.
- Таблица устройств, в которой были колонки Имя Хоста и IP-адрес.
Генерация Основной таблицы в Приложении будет производиться SQL-запросом и таблица будет иметь соответственно колонки Дата, Время, Имя Хоста и IP-адрес.
Структура проекта для QSqlQueryModel
Структура проекта QSqlQueryModel остается такой же, как в предыдущей статье .
mainwindow.ui
Таблицы также используются те же, что и в предыдущей статье, напомним себе их названия:
- tableView
- tableViewDevice
main.cpp
Файл используется в проекте, будучи созданным по умолчанию.
- #include "mainwindow.h"
- #include <QApplication>
- int main(int argc, char *argv[])
- {
- QApplication a(argc, argv);
- MainWindow w;
- w.show();
- return a.exec();
- }
mainwindow.h
В данном файле подключается библиотека QSqlQueryModel , тогда как классы QSqlRelationalTableModel , QSqlRelationalDelegate и QSqlRelation не будут использоваться. Также изменяется сигнатура методов инициализации моделей, поскольку для объекта QSqlQueryModel нет метода по установке имени таблицы, к которой будет обращаться данный объект.
- #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
Главное отличие кода в этом исходном файле от кода из предыдущей статьи заключается в том, что в методах для инициализации моделей используется SQL-запрос, которые выполняется методом setQuery() .
- #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);
- }
Для того, чтобы обновить данные в таблице, необходимо повторить SQL-запрос. Это делается вызовом метода модели query() , у которого применяется метод lastQuery() , как показано в коде ниже:
- model->setQuery(modelDevice->query().lastQuery());
database.h
Заголовочный файл для класса, выступающего фасадом для работы с базой данных изменяется всего на две строчки, по сравнению с предыдущей статьей . Изменения приведены в коде ниже:
- /* Директивы имен таблицы, полей таблицы и базы данных */
- #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
В данном файле изменению подвергаются лишь два метода, которые работают с Основной таблицей . Из-за того, что изменилось число колонок, которые применены в данной таблице.
- /* Метод для создания основной таблицы в базе данных
- * */
- 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;
- }
Итог
Визуальный результат у Вас не должен отличаться от того, что вы получили в предыдущем уроке. Это будет окно с двумя таблицами.
Какаим образом можно вставить в базу даннных сразу несколько строк? Ситуация такая - на 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, значит что-то там есть.
Спасибо за подсказку, получилось. Операция сравнения даты почему-то не выполнялась с использованием переменной, пришлось немного переделать запрос к базе данных.