Для того, чтобы представить информацию, содержащуюся в таблице базы данных, во фреймворке Qt используется несколько классов:
- QSqlQueryModel - модель, которая формирует таблицу путем задания сырого SQL-запроса. Может быть полезна при формировании особо изощренных фильтров и компиляции информации из различных таблиц базы данных. О ней подробнее в следующих уроках.
- QSqlTableModel - предмет нашей беседы в данной статье. Модель, которая формирует таблицу по имени той таблицы, которая существует в базе данных. Из минусов можно отметить отсутствие методов подключения связей c другими таблицами, чтобы подставлять значения в поля из других таблиц по ID.
- QSqlRelationalTableModel - класс, который позволяет формировать таблицу со связями из других таблиц, подменяя значения таблицы, которую представляет данная модель, по ID записей, содержащихся в других таблицах.
Для комфортной работы с информацией, которая помещена в базу данных применяется дополнительный класс, который частично представляет собой шаблон проектирования "Фасад" .
Программный код был написан в QtCreator 3.3.1 на основе Qt 5.4.1.
Структура проекта для QSqlTableModel
Проект создается как Приложение Qt Widgets, в котором создаются следующие файлы:
- DataBase.pro - профайл;
- mainwindow.h - заголовочный файл основного окна приложения;
- mainwindow.cpp - исходный код окна;
- main.cpp - основной исходный файл, с которого стартует приложение;
- mainwindow.ui - формочка основного окна приложения;
- database.h - заголовочный файл вспомогательного класса, применяющегося для работы с информацией, которая помещена в базу данных;
- database.cpp - исходный файл вспомогательного класса, применяющегося для работы с информацией, которая помещена в базу данных;
Примечание. Большую часть интерфейса создаю в дизайнере, чтобы не загромождать логику основного кода лишней информацией. По сути это лишь дело вкуса и привычки.
mainwindow.ui
Формочка для QSqlTableModel Создаем формочку для тестового приложения, в котором будет из графического интерфейса использоваться объект QTableView под названием tableView .
DataBase.pro
В Профайл проекта необходимо добавить директиву, которая объявляет об использовании библиотек SQL.
#------------------------------------------------- # # Project created by QtCreator 2015-08-10T16:08:24 # #------------------------------------------------- QT += core QT += gui QT += sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = DataBase TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ database.cpp HEADERS += mainwindow.h \ database.h FORMS += mainwindow.ui
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
В данном заголовочном файле помещается объявление объекта QSqlTableModel и DataBase , вспомогательного класса, применяющегося для работы с информацией, которая помещена в базу данных. Помимо них объявлены методы для инициализации внешнего вида и модели QSqlTableModel нашего приложения.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QSqlTableModel> /* Подключаем заголовочный файл для работы с информацией, которая помещена в базу данных */ #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; QSqlTableModel *model; private: /* Также присутствуют два метода, которые формируют модель * и внешний вид TableView * */ void setupModel(const QString &tableName, const QStringList &headers); void createUI(); }; #endif // MAINWINDOW_H
mainwindow.cpp
Исходный файл, в котором происходит всё основное действо по взаимодействию с моделью представления данных.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); /* Первым делом необходимо создать объект, который будет использоваться для работы с данными нашей БД * и инициализировать подключение к базе данных * */ db = new DataBase(); db->connectToDataBase(); /* После чего производим наполнение таблицы базы данных * контентом, который будет отображаться в TableView * */ for(int i = 0; i < 4; i++){ QVariantList data; int random = qrand(); // Получаем случайные целые числа для вставки а базу данных data.append(QDate::currentDate()); // Получаем текущую дату для вставки в БД data.append(QTime::currentTime()); // Получаем текущее время для вставки в БД // Подготавливаем полученное случайное число для вставки в БД data.append(random); // Подготавливаем сообщение для вставки в базу данных data.append("Получено сообщение от " + QString::number(random)); // Вставляем данные в БД db->inserIntoTable(data); } /* Инициализируем модель для представления данных * с заданием названий колонок * */ this->setupModel(TABLE, QStringList() << trUtf8("id") << trUtf8("Дата") << trUtf8("Время") << trUtf8("Рандомное число") << trUtf8("Сообщение") ); /* Инициализируем внешний вид таблицы с данными * */ this->createUI(); } MainWindow::~MainWindow() { delete ui; } /* Метод для инициализации модеи представления данных * */ void MainWindow::setupModel(const QString &tableName, const QStringList &headers) { /* Производим инициализацию модели представления данных * с установкой имени таблицы в базе данных, по которому * будет производится обращение в таблице * */ model = new QSqlTableModel(this); model->setTable(tableName); /* Устанавливаем названия колонок в таблице с сортировкой данных * */ for(int i = 0, j = 0; i < model->columnCount(); i++, j++){ model->setHeaderData(i,Qt::Horizontal,headers[j]); } // Устанавливаем сортировку по возрастанию данных по нулевой колонке model->setSort(0,Qt::AscendingOrder); } void MainWindow::createUI() { ui->tableView->setModel(model); // Устанавливаем модель на 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); model->select(); // Делаем выборку данных из таблицы }
database.h
Чтобы не оставлять в полной тьме вопрос по взаимодействию с базой данных, также привожу листинги данного класса с комментариями. Работа производится с базой данных SQLite, но принцип работы с сетевыми базами данных будет аналогичен, за исключением сетевого подключения и нюансов работы с каждой конкретной базой данных.
В заголовочном файле класса DataBase необходимо указать директивы названий таблицы и колонок таблицы для удобства дальнейшей работы. А также методы, которые используются для взаимодействия с базой данных, а также предоставления интерфейсов для этого взаимодействия с более высокоуровневыми прослойками приложения.
#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 "ExampleDataBase" #define DATABASE_NAME "DataBase.db" #define TABLE "TableExample" #define TABLE_DATE "Date" #define TABLE_TIME "Time" #define TABLE_MESSAGE "Message" #define TABLE_RANDOM "Random" class DataBase : public QObject { Q_OBJECT public: explicit DataBase(QObject *parent = 0); ~DataBase(); /* Методы для непосредственной работы с классом * Подключение к базе данных и вставка записей в таблицу * */ void connectToDataBase(); bool inserIntoTable(const QVariantList &data); private: // Сам объект базы данных, с которым будет производиться работа QSqlDatabase db; private: /* Внутренние методы для работы с базой данных * */ bool openDataBase(); bool restoreDataBase(); void closeDataBase(); bool createTable(); }; #endif // DATABASE_H
database.cpp
Исходный файл вспомогательного класса, применяющегося для работы с информацией, которая помещена в базу данных. В данном случае формирование SQL запросов прячется по максимуму в данный класс, чтобы уменьшить количество связей в программном коде. Подобная практика в самом начале может помочь привить новичку умение сразу писать код, в котором различные классы по максимум обособлены друг от друга, что и является одним из аспектов ООП.
#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()){ if(!this->createTable()){ return false; } else { return true; } } 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_DATE " DATE NOT NULL," TABLE_TIME " TIME NOT NULL," TABLE_RANDOM " INTEGER NOT NULL," TABLE_MESSAGE " 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_DATE ", " TABLE_TIME ", " TABLE_RANDOM ", " TABLE_MESSAGE " ) " "VALUES (:Date, :Time, :Random, :Message )"); query.bindValue(":Date", data[0].toDate()); query.bindValue(":Time", data[1].toTime()); query.bindValue(":Random", data[2].toInt()); query.bindValue(":Message", data[3].toString()); // После чего выполняется запросом методом exec() if(!query.exec()){ qDebug() << "error insert into " << TABLE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; }
Итог
В результате должна получиться табличка следующего вида с данными, которые были подготовлены и вставлены в конструкторе главного окна приложения.
Внешний вид приложения с QSqlTableModel
Здравствуйте, а, скажите, сам объект QSqlTableModel предоставляет доступ к данным, которые уже есть в таблице? Например, получить в какую - то переменную ранее записанное рандомное число из строки с индексом n?Или, это только возможно запросом
Метод setData(строка, столбец, значение) есть, а, метода, например, типа, getData(строка, столбец), не нашел
Разобрался QSqlRecord rec = model(номер строки) - получаем строку int num = rec.value("TABLE_RANDOM"); - получаем поле
Всё гораздо проще, метод, который Вы искали называется не getData() , а просто data() . Для получения данных нужно дернуть QModelIndex из модели. Выглядеть это будет так:
Метод вернёт QVariant , который будет содержать данные.
А у меня выхлоп: Не удалось восстановить базу данных QSqlQuery::prepare: database not open error insert into TableExample "No query Unable to fetch row" QSqlQuery::prepare: database not open error insert into TableExample "No query Unable to fetch row" QSqlQuery::prepare: database not open error insert into TableExample "No query Unable to fetch row" QSqlQuery::prepare: database not open error insert into TableExample "No query Unable to fetch row" QSqlQuery::prepare: database not open error insert into TableExample "No query Unable to fetch row" QSqlDatabasePrivate::database: unable to open database: "out of memory Error opening database"
В методах connectToDataBase и openToDataBase пропишите свой путь к базе данных.
Тоесть надо создать пустую базу sqlite сторонним ПО?
Нет. нужно указать корректный пути к базе данных. Если вы скопипастили код, то скорее всего у вас нет директории example на диске C, поэтому и не фурычит.
Здравствуйте, очень понравился Ваш урок. Сначала решил взять идею - не получилось, причем ошибки странные. После этого решил взять Ваш код и выяснилось, что ошибки остаются теми же. Компилятор ругается на то, что вроде как конструктор и деструктор класса DataBase должны быть виртуальными...Плохое решение проблемы, но все же отчасти ее решение - это убрать макрос Q_Object в заголовочном файле DataBase. При таком решении проблемы выскакивает ошибка " ASSERT: "!isEmpty()" in file ....." и программа сворачивается. Пожалуйста, подскажите в чем загвоздка, почему так и как это все исправить? Есть предположение, что это связано с настройкой компилятора, но...не знаю.
К посту выше...Проблема "виртуальных" конструктора и деструктора решается с помощью перезапуска qmake (его вообще нужно всегда перезапускать, если добавляешь макрос Q_Object в свой класс). Но вторая проблема, к сожалению осталась актуальной(( "ASSERT: "!isEmpty()" in file C:/Users/qt/work/qt/qtbase/src/corelib/tools/qlist.h, line 345 "
Добрый день! В методе connectToDataBase вы прописали свой путь к базе данных?
Да
Сейчас сразу и не припомню, было ли нечто такое, может в новых версиях Qt что-то поменяли, что генерирует ошибку. После работы гляну, какая там может быть проблема.
Хорошо, спасибо большое! Очень жду Вашего ответа. Я тоже пока попробую разобраться и может реализовать как-нибудь по-другому.
У меня все заработало. Не могу точно сказать в чем была причина, есть только догадки. Но программа заработала только после того, как я удалил базу данных с таким же названием, которая была создана мною вручную. Наверное, это мешало корректной работе программы, но не совсем ясно почему...ведь мы всего лишь обращаемся к этой БД с запросами и все.
Скорее всего дело в метаинформации, которой не было при создании файла вручную. При подключении некорректный файл крашил программу...
Да, спасибо Вам большое! У Вас очень интересные уроки!!!
Спасибо за отзыв. Будут вопросы не по статьям, то не стесняйтесь задавать их на форуме сайта .
DataBase is unknown type. Подскажите, в чем проблема, вроде все заголовочные подключаю
Все понял
ММм.. хорошо, А что там не так было?
Я не подключил header класса, поэтому он не знал тип такой. Подскажите пожалуйста
Я qDebug()`ом вызываю значения, которые подаю в базу данных, как я понимаю, база ругается на QDate и QTime
QDate("2018-10-08")
QTime("13:37:04.454")
26500
"Получено сообщение от 26500"
error insert into TableExample
" Parameter count mismatch"
Наверное у вас базад данных не создалась, поправьте путь к место, где должна создаться база данных вот в этом методе
В отладчике возвращает true, проблема не в этом
Возможно, что пока вы повторяли работу кода из данной статьи, была создана таблица, но с другим количествов столбцов, в результату строка не добавляется из-за различий в запросе и самой таблице. Попробуйте удались базу данных, сконтролируйте, чтобы столбцы соответсвовали тому, что вы пытаетесь добавить в таблицу.
Спасибо за урок!
Вопрос: что нужно сделать, чтобы в tableView показывалось, например, 12:56, а не 12:56:34.554?
Наследоваться от QSqlTableModel и переопределить метод data для Qt::DisplayRole. Там сделать необходимые проебразования формата.
И потом уже использовать переопределённую модель вместо QSqlTableModel
Сделал вот так. В tableView ничего нет, кроме заголовка.
потому, что нужно сохранять информацию для всех остальных ролей и столбцов через вызов переопределённого метода. Да к тому же вы ещё и зациклили вызов метода data.
Всё равно пусто, хотя строка с данными в базу добавляется.
Заработало. Забыл model->select(); вписать.
Спасибо!
Ещё небольшой вопрос.
Я добился, чтобы в tableView в колонке flag показывался чекбокс (через флаг Qt::ItemIsUserCheckable) и управлял содержимым базы . Как теперь убрать цифру из поля, чтобы остался только чекбокс?
flag.JPG
Переопределить метод data для той колонки и роли Qt::DisplayRole, чтобы в том случае возвращался QVariant() я так думаю...
Но возможно, что у вас там будут нюансы, если вы туда чекбокс запихали
Получилось приемлемо. Спасибо!
Нюанс только в том, что поле рядом с чекбоксом не пропадает, оно просто пустое, что видно при выделении ячейки. Но этого достаточно.
Если будет не приемлемо потом, то тогда через кастомный Item Delegate нужно будет перерисовать ячейки в той колонке.
Не проще тогда использовать сразу кастомный делегат с чекбоксом? Я попробовал, но там засада в том, что чекбокс показывается только при щелчке на ячейку, а дефолтно показывается значение. Как эту засаду обойти, я не смог придумать, плохо ещё ориентируюсь в моделях.
Создайте тогда тему здесь на форуме в разделе Qt с выкладками кода и вашими попытками внедрения делегата, позже гляну или может кто-то ещё глянет из опытных пользователей.
Всем привет. Возник вопрос, можно ли работать с QtSql без сырых sql запросов, как в различный orm, через классы qsqltablemodel, qsqlrelationtablemodel?
Добрый день. В Qt нет ORM в QtSql, а классы qsqltablemodel и qsqlrelationtablemodel дают относительно слабый функционал, даже для использования фильтрации потребуется писать кусок SQL запроса. Без сырых запросов не обойдётесь.
Спасибо. Очень жаль, хотя с другой стороны это дает гораздо больше гибкости.
Вы заблуждаетесь. Любая нормальная ORM позволяет выполнение сырых SQL запросов. А если хорошо разобраться в работе моделей данных в Qt, то не составит труда использовать ORM вместе с Qt, ту же самую ORM Wt::Dbo. Нет особой гибкости в том, что приходится тратить уйму времени на написание сырых запросов, когда ORM позволяет всё это выполнять значительно быстрее. Я думаю, что со стороны Qt Company - это является недоработкой, что они не попытались сделать свою ORM, ресурсы им сейчас это позволяют. Так что действительно жаль, что этого нет из коробки.
Спасибо за инфу. Поиск качественной ORM привел меня только к sqlite_orm, но не подходит из-за необходимости полноценной поддержки c++14. Про framework Wt не слышал, спасибо за наводку.
Рекомендую Wt, достаточно мощная вещь. Этот фреймворк может использоваться для написания сайтов на C++, либо можно использовать только отдельный компоненты, например только ORM. Но я не знаю, какая требуется минимальная версия стандарта C++ для него. А также он требует boost библиотеку. У нас в проекте используется boost, а также C++17. Поэтому никогда не задумывался о минимальных требованиях к этому фреймворку.
не удается подключиить библеотеку
include "database.h"
выдает ошибку. Можете помочь?
Потому что это файл который нужно создать, а не библиотека.
В статье есть содержание этого файла. Добавляйте в проект. Копируйте содержимое из статьи.
полностью повторил структору проекта. В форму дабавил tableView. Но при запуске получаю форму только с пустым tableView. Можете подсказать в чем пробелма?
У вас база данных не открылась
Исправьте путь к базе данных на свой корректный в следующих методах
спасибо, все сработало
Подскажите пожалуйста как изменить данные в базе, тип столбцов и сделать столбцы шире
Напишите метод с SQL запросом, который изменит данные или их тип. Это ALTER методы для изменения типа данных, а также INSERT и UPDATE методы для добавления и обновления данных. Это вы можете найти в документации на SQL. Просто напишите QSqlQuery запросы, как это показано в методе inserIntoTable
Что касается ширины столбцов, то QTableView имеет метод setColumnWidth
Почему-то такой метод для обновления не работает, который можно было бы применить в данном примере. То есть в представлении данные удаляются и обновляются, а в базе данных изменений не происходит:
А также для удаления такой метод не обновляет базу данных SQLite (модель унаследована от QSqlTableModel):
Не могу понять как связывается БД и модель. Подскажите, пожалуйста.
model = new QSqlTableModel(this);
model->setTable(tableName);
Это строки 61-62 в mainwindow.cpp
IscanderChe. Мы создаем объект класс DataBase. Как этот объект связать с объектом класса QSqlTableMode?
QSqlTableModel(QObject *parent = nullptr, QSqlDatabase db = QSqlDatabase())
Мой вопрос был про объект класса DataBase. Как он связывается с объектом класса QSqlTableModel.Можете указать строчку в коде?
Объект QSqlDatabase() устанавливается глобально, для всего кода. DataBase, соответственно, делает то же самое, только с помощью своих методов, которые используются в mainwindow.cpp, а сам объект подключается и объявляется в mainwindow.h. Затем через таблицы из БД происходит связь с моделью. Это про первый мой ответ.
Если вы, к примеру, открываете несколько баз данных в одном приложении, то вы можете указать при создании модели, какую именно базу данных использовать. Это про мой второй ответ вам.
error insert into TableExample
" Количество параметров не совпадает"
Я путь свой прописывала и даже бд удаляла, чтобы заново сделать, не работает. (всё остальное как у вас... Вроде как. Хотя я так и не увидела, где используется closeDataBase())