Використання 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());
база даних.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, значит что-то там есть.
Спасибо за подсказку, получилось. Операция сравнения даты почему-то не выполнялась с использованием переменной, пришлось немного переделать запрос к базе данных.