В Qt для представления таблиц поля, которых связаны внешними ключами с другими таблицами базы данных, может применяться QSqlRelationalTableModel , которая является более продвинутым вариантом класса QSqlTableModel , который был рассмотрен в предыдущей статье .
Данный класс позволяет устанавливать связи между таблицами и подменять в представлении значения формируемой таблицы значениями из связных таблиц.
В данной статье рассматривается вариант из двух таблиц. В одной из таблиц содержится информация об устройствах (Имя хоста и IP адрес), а во второй таблице ID этих устройств, по которым в второй таблице будут подставляться Имя хоста и IP адрес соответственно устройствам.
Структура проекта для QSqlRelationalTableModel
Структура проекта Проект для этого урока является доработанной версией предыдущей статьи и остается неизменной.
mainwindow.ui
При создании формочки добавляется дополнительная таблица, в которой будут указаны устройства. Названия таблиц следующие:
- tableView
- tableViewDevice
DataBase.pro
Данный файл остается неизменным, как и в предыдущей статье.
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 подключается библиотека QSqlRelationalTableModel. А также добавляется дополнительный метод, который отвечает за инициализацию модели представления таблицы устройств.
- #ifndef MAINWINDOW_H
- #define MAINWINDOW_H
- #include <QMainWindow>
- #include <QSqlRelationalTableModel>
- #include <QSqlRelationalDelegate>
- #include <QSqlRelation>
- /* Подлючаем заголовчный файл для работы с базой данных */
- #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;
- QSqlRelationalTableModel *modelMain;
- QSqlRelationalTableModel *modelDevice;
- private:
- /* Также присутствуют два метода, которые формируют модель
- * и внешний вид TableView
- * */
- void setupMainModel(const QString &tableName, const QStringList &headers);
- void setupDeviceModel(const QString &tableName, const QStringList &headers);
- void createUI();
- };
- #endif // MAINWINDOW_H
mainwindow.cpp
В основном исходном файле инициализируем модели представления данных и внешний вид главного окна приложения. В методе по инициализации основной таблицы будут установлены связи, по которым будут выбираться данные из таблицы устройств.
При запуске программы в базу данных вносится три устройства, а потом 9 записей со случайным назначением ID устройств от 1 до 3. При самом первом запуске программы создаётся база данных и в пустую таблицу устройств вносятся устройства с ID от одного до трех. При втором запуске ID уже будет отличных от 1, 2 или 3. но поскольку это учебный пример, то ограничимся одним первым запуском приложения.
- #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 и tableViewDevice
- * */
- for(int i = 1; i < 4; i++){
- QVariantList data;
- data.append("Device " + QString::number(i));
- data.append("192.168.0." + 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);
- data.append(random);
- db->inserIntoMainTable(data);
- }
- /* Инициализируем модели для представления данных
- * с заданием названий колонок
- * */
- this->setupMainModel(TABLE,
- QStringList() << trUtf8("id")
- << trUtf8("Дата")
- << trUtf8("Время")
- << trUtf8("Имя хоста")
- << trUtf8("IP адрес")
- );
- this->setupDeviceModel(DEVICE,
- QStringList() << trUtf8("id")
- << trUtf8("Имя хоста")
- << trUtf8("IP адрес")
- );
- /* Инициализируем внешний вид таблицы с данными
- * */
- this->createUI();
- }
- MainWindow::~MainWindow()
- {
- delete ui;
- }
- /* Метод для инициализации модели представления данных
- * */
- void MainWindow::setupMainModel(const QString &tableName, const QStringList &headers)
- {
- /* Производим инициализацию модели представления данных
- * с установкой имени таблицы в базе данных, по которому
- * будет производится обращение в таблице
- * */
- modelMain = new QSqlRelationalTableModel(this);
- modelMain->setTable(tableName);
- /* Устанавливаем связи с таблицей устройств, по которым будет производится
- * подстановка данных
- * В метода setRelation указывается номер колонки, в которой будет
- * производится подстановка, а также с помощью класса
- * QSqlRelation указывается имя таблицы,
- * параметр, по которому будет произведена выборка строки
- * и колонка, из которой будут взяты данные
- * */
- modelMain->setRelation(3, QSqlRelation(DEVICE, "id", DEVICE_HOSTNAME));
- modelMain->setRelation(4, QSqlRelation(DEVICE, "id", DEVICE_IP));
- /* Устанавливаем названия колонок в таблице с сортировкой данных
- * */
- for(int i = 0, j = 0; i < modelMain->columnCount(); i++, j++){
- modelMain->setHeaderData(i,Qt::Horizontal,headers[j]);
- }
- // Устанавливаем сортировку по возрастанию данных по нулевой колонке
- modelMain->setSort(0,Qt::AscendingOrder);
- modelMain->select(); // Делаем выборку данных из таблицы
- }
- void MainWindow::setupDeviceModel(const QString &tableName, const QStringList &headers)
- {
- /* Производим инициализацию модели представления данных
- * с установкой имени таблицы в базе данных, по которому
- * будет производится обращение в таблице
- * */
- modelDevice = new QSqlRelationalTableModel(this);
- modelDevice->setTable(tableName);
- /* Устанавливаем названия колонок в таблице с сортировкой данных
- * */
- for(int i = 0, j = 0; i < modelDevice->columnCount(); i++, j++){
- modelDevice->setHeaderData(i,Qt::Horizontal,headers[j]);
- }
- // Устанавливаем сортировку по возрастанию данных по нулевой колонке
- modelDevice->setSort(0,Qt::AscendingOrder);
- modelDevice->select(); // Делаем выборку данных из таблицы
- }
- 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->setItemDelegate(new QSqlRelationalDelegate(ui->tableView));
- ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
- ui->tableView->horizontalHeader()->setStretchLastSection(true);
- modelMain->select(); // Делаем выборку данных из таблицы
- 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->setItemDelegate(new QSqlRelationalDelegate(ui->tableViewDevice));
- ui->tableViewDevice->setEditTriggers(QAbstractItemView::NoEditTriggers);
- ui->tableViewDevice->horizontalHeader()->setStretchLastSection(true);
- modelDevice->select();
- }
database.h
По сравнению с примером про QSqlTableModel в данном примере добавляется два новых метода, связанных с таблицей устройств, а именно создание таблицы createDeviceTable() и вставкой записи в таблицу устройств insertIntoDeviceTable(). А также добавляются новые директивы define для таблицы устройств и перерабатываются директивы для основной таблицы.
- #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 "MainTable"
- #define TABLE_DATE "Date"
- #define TABLE_TIME "Time"
- #define TABLE_IP "IP"
- #define TABLE_HOSTNAME "Hostname"
- #define DEVICE "DeviceTable"
- #define DEVICE_IP "IP"
- #define DEVICE_HOSTNAME "Hostname"
- class DataBase : public QObject
- {
- Q_OBJECT
- public:
- explicit DataBase(QObject *parent = 0);
- ~DataBase();
- /* Методы для непосредственной работы с классом
- * Подключение к базе данных и вставка записей в таблицу
- * */
- void connectToDataBase();
- bool inserIntoMainTable(const QVariantList &data);
- bool inserIntoDeviceTable(const QVariantList &data);
- private:
- // Сам объект базы данных, с которым будет производиться работа
- QSqlDatabase db;
- private:
- /* Внутренние методы для работы с базой данных
- * */
- bool openDataBase();
- bool restoreDataBase();
- void closeDataBase();
- bool createMainTable();
- bool createDeviceTable();
- };
- #endif // DATABASE_H
database.cpp
В данном файле подверглись изменениям лишь часть методов, а также добавились два новых метода по сравнению с предыдущей статьей .
- #include "database.h"
- DataBase::DataBase(QObject *parent) : QObject(parent)
- {
- }
- DataBase::~DataBase()
- {
- }
- /* Методы для подключения к базе данных
- * */
- void DataBase::connectToDataBase()
- {
- /* см. статью про QSqlTableModel */
- }
- /* Методы восстановления базы данных
- * */
- bool DataBase::restoreDataBase()
- {
- if(this->openDataBase()){
- if((!this->createMainTable()) || (!this->createDeviceTable())){
- return false;
- } else {
- return true;
- }
- } else {
- qDebug() << "Не удалось восстановить базу данных";
- return false;
- }
- return false;
- }
- /* Метод для открытия базы данных
- * */
- bool DataBase::openDataBase()
- {
- /* см. статью про QSqlTableModel */
- }
- /* Методы закрытия базы данных
- * */
- void DataBase::closeDataBase()
- {
- db.close();
- }
- /* Метод для создания основной таблицы в базе данных
- * */
- 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_HOSTNAME " INTEGER NOT NULL,"
- TABLE_IP " INTEGER NOT NULL"
- " )"
- )){
- qDebug() << "DataBase: error of create " << TABLE;
- qDebug() << query.lastError().text();
- return false;
- } else {
- return true;
- }
- return false;
- }
- /* Метод для создания таблицы устройств в базе данных
- * */
- bool DataBase::createDeviceTable()
- {
- /* В данном случае используется формирование сырого SQL-запроса
- * с последующим его выполнением.
- * */
- QSqlQuery query;
- if(!query.exec( "CREATE TABLE " DEVICE " ("
- "id INTEGER PRIMARY KEY AUTOINCREMENT, "
- DEVICE_HOSTNAME " VARCHAR(255) NOT NULL,"
- DEVICE_IP " VARCHAR(16) NOT NULL"
- " )"
- )){
- qDebug() << "DataBase: error of create " << DEVICE;
- 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_HOSTNAME ", "
- TABLE_IP " ) "
- "VALUES (:Date, :Time, :Hostname, :IP )");
- query.bindValue(":Date", data[0].toDate());
- query.bindValue(":Time", data[1].toTime());
- query.bindValue(":Hostname", data[2].toInt());
- query.bindValue(":IP", data[3].toInt());
- // После чего выполняется запросом методом exec()
- if(!query.exec()){
- qDebug() << "error insert into " << TABLE;
- qDebug() << query.lastError().text();
- return false;
- } else {
- return true;
- }
- return false;
- }
- /* Метод для вставки записи в таблицу устройств
- * */
- bool DataBase::inserIntoDeviceTable(const QVariantList &data)
- {
- /* Запрос SQL формируется из QVariantList,
- * в который передаются данные для вставки в таблицу.
- * */
- QSqlQuery query;
- /* В начале SQL запрос формируется с ключами,
- * которые потом связываются методом bindValue
- * для подстановки данных из QVariantList
- * */
- query.prepare("INSERT INTO " DEVICE " ( " DEVICE_HOSTNAME ", "
- DEVICE_IP " ) "
- "VALUES (:Hostname, :IP )");
- query.bindValue(":Hostname", data[0].toString());
- query.bindValue(":IP", data[1].toString());
- // После чего выполняется запросом методом exec()
- if(!query.exec()){
- qDebug() << "error insert into " << DEVICE;
- qDebug() << query.lastError().text();
- return false;
- } else {
- return true;
- }
- return false;
- }
Итог
В результате проделанной работы Приложение будет содержать две таблицы. Основную таблицу, из которой в которой будут содержаться записи ссылающиеся на таблицу устройств, и вторую таблицу, которая будет содержать записи об устройствах соответственно.
Приложение QSqlRelationTableModel
Небольшое дополнение, столкнулся вот. При вызове setFilter к модели созданной на основе QSqlRelationalTableModel нужно указывать полный фильтр
Ну да. Приходится уточнять конкретную таблицу, к которой применяется фильтр.
Хотя для QSqlTableModel можно и без такой конкретизации обойтись.
Вставки в базу выругались "" Parameter count mismatch""
Показывайте код, что там писали в коде вставки и т.д. Ну и какая структура таблиц базы данных.
https://www.dropbox.com/sh/vhxcx0iyq0j4578/AACwgWPnZwNqGBndKESiXfFqa?dl=0
В классе DataBase указывается путь к базе данных. В данном случае C:/example/ и т.д. Так вот, у вас есть каталог example ?
Есть, там была база. Я ее удалил, теперь DeviceTable отрабатывает. На остальное матерится error insert into MainTable " Parameter count mismatch"
Изменяли колонки? Названия колонок? Обычно такая ошибка возникает в том случае, если в запросе на добавление не хватает информации, или есть излишняя информация. Не совпадает количество колонок и т.д.
Есть таблица инредиенты с dishesId которая через setRelation получает название блюд из dishesTable, есть productId которая через setRelation получает название продукта из productTable, и столбец количества.
Вопрос: как добавлять в модель ещё один столбец с единицей измерения количества, хочется productId связать второй раз через setRelation с measureId в productTable? Надо в дальнейшем применять setFilter.
Из прешедшего в голову добавлять ещё один столбец в model и копировать в него productId и setRelation на него.
Добавление столбца и setData на него не сработала, хотя в уже имевшиеся стобцы работает.
Ещё вопрос как удалять строки в QSqlRelationalTableModel? Например работает QSqlTableModel, но не в QSqlRelationalTableModel:
У вас единицы измерения храняться где-то отдельно в базе данных? Если так то можете через setRelation также попытаться сделать.
Но если честно, я в итоге всегда приходил к QSqlQueryModel. Она конечно readOnly и потом приходилось реализовывать все редактирования и удаления. Но по крайней мере за счёт формирования запроса вручную можно было достаточно сложные выборки реализовывать.
Вот пример с QSqlQueryModel
Видите ли, думаю, что здесь ограничение из-за нескольких связей, и не удаётся правильно обработать удаление.
Вы можете сделать удаление вручную через SQL запрос, а потом просто обновить выборку в таблице.
Вот пример такого запроса в комментарии под одной из статей
Единицы измерения лежат там же где и названия продуктов. Просто в таблице ингредиенты нет ещё одного столбца, на который можно было бы установить setRelation. Я в итоге в базе создал ещё один пустой столбец и его редактировал c setData в который получал инфу через QSqlQueryModel.
Похоже всё проще делать через QSqlQueryModel.
Да вы правы. На самом деле проще через QSqlQueryModel, сколько не пытался использовать эти дженерики типо QSqlTableModel и QSqlRelationalTableModel, то всегда упирался в какие-то их ограничения. В итоге проще написать недостающий функционал для QSqlQueryModel для манипуляции с данными (поскольку она сама по себе readOnly), чем пытаться обойти какие-то ограничениях в дженерик-моделях. Они недостаточно гибкие.
Большое спасибо за разъяснения!
А можно ли сделать связную таблицу для QTableWidget?
Нет. Используйте QTableView
А как там тогда checkbox сделать? Через BLOB?
BLOB здесь вообще не при чём. Если вам нужен checkbox, то почитайте статью про делегаты
Для чего нужны в коде вот эти строки?
Хочу поделится, мы сделали свой вариант QSqlRelationalTableModel и заодно к нему новое развитие QTableView, и получилась готовая таблица PblTableDlg, у которой реализованы внешние связи, комбобоксы, чекбоксы, основные кнопки, поиск и чего там уже только нет... Настраивается все элементарно.
Открытый проект github