Qt QSqlRelationalTableModel пайдалана алады, ол алдыңғы мақалада талқыланған QSqlTableModel сыныбының жетілдірілген нұсқасы болып табылады.
Бұл класс кестелер арасында байланыс орнатуға және көріністегі құрылған кестенің мәндерін қатысты кестелердегі мәндермен ауыстыруға мүмкіндік береді.
Бұл мақалада екі кестенің нұсқасы қарастырылады. Кестелердің бірінде құрылғылар (хост атауы және IP мекенжайы) туралы ақпарат бар, ал екінші кестеде осы құрылғылардың идентификаторлары бар, олар арқылы құрылғылар үшін екінші кестеде Хост атауы мен IP мекенжайы сәйкесінше ауыстырылады.
QSqlRelationalTableModel үшін жоба құрылымы
Жоба құрылымы Осы сабақтың жобасы алдыңғы мақаланың қайта қаралған нұсқасы болып табылады және өзгеріссіз қалады.
mainwindow.ui
Пішінді құру кезінде құрылғылар көрсетілетін қосымша кесте қосылады. Кесте атаулары келесідей:
- кесте көрінісі
- 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
Негізгі бастапқы файлда біз деректерді ұсыну үлгілерін және негізгі қолданба терезесінің көрінісін инициализациялаймыз. Негізгі кестені инициализациялау әдісінде сілтемелер орнатылады, оларға сәйкес деректер құрылғы кестесінен таңдалады.
Бағдарлама іске қосылған кезде деректер қорына үш құрылғы енгізіледі, содан кейін 1-ден 3-ке дейінгі құрылғы идентификаторларының кездейсоқ тағайындалуымен 9 жазба енгізіледі. Бағдарламаның ең бірінші іске қосылуында деректер базасы құрылады және біреуден идентификаторы бар құрылғылар үшеуі құрылғының бос кестесіне енгізіледі. Екінші іске қосу кезінде идентификатор 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(); }
дерекқор.h
QSqlTableModel мысалымен салыстырғанда, бұл мысал құрылғы кестесіне қатысты екі жаңа әдісті қосады, атап айтқанда createDeviceTable() кестесін жасау және құрылғы кестесіне жазбаны енгізу insertIntoDeviceTable(). Және сонымен қатар құрылғылар кестесіне жаңа анықтау директивалары қосылды және негізгі кестеге арналған директивалар қайта өңделеді.
#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