Qt kann QSqlRelationalTableModel verwenden, eine erweiterte Version der Klasse QSqlTableModel , die in vorheriger Artikel beschrieben wurde.
Mit dieser Klasse können Sie Verknüpfungen zwischen Tabellen herstellen und die Werte der generierten Tabelle in der Präsentation durch Werte aus den verknüpften Tabellen ersetzen.
In diesem Artikel wird die Option von zwei Tabellen erläutert. Eine der Tabellen enthält Informationen zu den Geräten (Hostname und IP-Adresse), und die zweite Tabelle enthält die IDs dieser Geräte, durch die der Hostname bzw. die IP-Adresse in der zweiten Tabelle für die Geräte ersetzt werden.
Projektstruktur für QSqlRelationalTableModel
Projektstruktur Das Projekt für diese Lektion ist eine überarbeitete Version des vorherigen Artikels und bleibt unverändert.
mainwindow.ui
Beim Anlegen eines Werkzeugs wird eine zusätzliche Tabelle hinzugefügt, in der die Geräte angezeigt werden. Die Tabellennamen lauten wie folgt:
- Tabellenansicht
- tableViewDevice
DataBase.pro
Diese Datei bleibt unverändert wie im vorherigen Artikel.
main.cpp
Die Datei wird im Projekt verwendet und wird standardmäßig erstellt.
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
Anstelle der Bibliothek QSqlTableModel wird die Bibliothek QSqlRelationalTableModel angebunden. Und eine zusätzliche Methode wird hinzugefügt, die für die Initialisierung des Gerätetabellenansichtsmodells verantwortlich ist.
#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
In der Hauptquelldatei initialisieren wir die Datenansichtsmodelle und das Erscheinungsbild des Hauptanwendungsfensters. Bei der Methode zum Initialisieren der Haupttabelle werden Verknüpfungen hergestellt, durch die Daten aus der Gerätetabelle ausgewählt werden.
Beim Programmstart werden drei Geräte in die Datenbank eingetragen und danach 9 Datensätze mit zufälliger Zuordnung der Geräte-IDs von 1 bis 3. Beim allerersten Programmstart wird eine Datenbank angelegt und Geräte mit den IDs von eins bis drei werden in eine leere Gerätetabelle eingetragen. Beim zweiten Start unterscheidet sich die ID bereits von 1, 2 oder 3. Da es sich jedoch um ein Beispielbeispiel handelt, beschränken wir uns auf einen ersten Start der Anwendung.
#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
Im Vergleich zum QSqlTableModel -Beispiel fügt dieses Beispiel zwei neue Methoden in Bezug auf die Gerätetabelle hinzu, nämlich das Erstellen der createDeviceTable () -Tabelle und das Einfügen eines Eintrags in die Gerätetabelle insertIntoDeviceTable (). Außerdem werden neue Direktiven define für die Gerätetabelle hinzugefügt und Direktiven für die Haupttabelle überarbeitet.
#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
datenbank.cpp
In dieser Datei wurde nur ein Teil der Methoden geändert und im Vergleich zum vorherigen Artikel wurden zwei neue Methoden hinzugefügt.
#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; }
Ergebnis
Als Ergebnis der geleisteten Arbeit wird der Anhang zwei Tabellen enthalten. Die Haupttabelle, in der Datensätze enthalten sind, die sich auf die Gerätetabelle beziehen, und die zweite Tabelle, die Datensätze zu Geräten enthält.
QSqlRelationTableModel-Anwendung
Небольшое дополнение, столкнулся вот. При вызове 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