Evgenii Legotckoi
Evgenii Legotckoi15 августа 2015 г. 12:31

Qt/C++ - Урок 005. QSqlRelationalTableModel - Работаем со связными таблицами

В 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

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Ruslan Polupan
  • 9 февраля 2017 г. 8:32

Небольшое дополнение, столкнулся вот. При вызове setFilter к модели созданной на основе QSqlRelationalTableModel нужно указывать полный фильтр

myModel->setFilter("tableName.fielsName>100");
Evgenii Legotckoi
  • 10 февраля 2017 г. 1:23

Ну да. Приходится уточнять конкретную таблицу, к которой применяется фильтр.

Хотя для QSqlTableModel можно и без такой конкретизации обойтись.

z
  • 21 мая 2017 г. 13:11

Вставки в базу выругались "" Parameter count mismatch""

Evgenii Legotckoi
  • 21 мая 2017 г. 13:26

Показывайте код, что там писали в коде вставки и т.д. Ну и какая структура таблиц базы данных.

z
  • 21 мая 2017 г. 15:55

https://www.dropbox.com/sh/vhxcx0iyq0j4578/AACwgWPnZwNqGBndKESiXfFqa?dl=0

Evgenii Legotckoi
  • 23 мая 2017 г. 2:26

В классе DataBase указывается путь к базе данных. В данном случае C:/example/ и т.д. Так вот, у вас есть каталог example ?

z
  • 27 мая 2017 г. 4:14

Есть, там была база. Я ее удалил, теперь DeviceTable отрабатывает. На остальное матерится error insert into MainTable " Parameter count mismatch"

Evgenii Legotckoi
  • 28 мая 2017 г. 9:23

Изменяли колонки? Названия колонок? Обычно такая ошибка возникает в том случае, если в запросе на добавление не хватает информации, или есть излишняя информация. Не совпадает количество колонок и т.д.

JS
  • 11 марта 2019 г. 5:46
  • (ред.)

Есть таблица инредиенты с dishesId которая через setRelation получает название блюд из dishesTable, есть productId которая через setRelation получает название продукта из productTable, и столбец количества.

Вопрос: как добавлять в модель ещё один столбец с единицей измерения количества, хочется productId связать второй раз через setRelation с measureId в productTable? Надо в дальнейшем применять setFilter.
Из прешедшего в голову добавлять ещё один столбец в model и копировать в него productId и setRelation на него.

Добавление столбца и setData на него не сработала, хотя в уже имевшиеся стобцы работает.

JS
  • 12 марта 2019 г. 5:29
  • (ред.)

Ещё вопрос как удалять строки в QSqlRelationalTableModel? Например работает QSqlTableModel, но не в QSqlRelationalTableModel:

    QItemSelectionModel *ingredientSelectionModel = ui->ingredientsView->selectionModel();
    if (!ingredientSelectionModel->currentIndex().isValid())
    {
        qDebug() << "ingredientSelectionModel->currentIndex().isValid() error";
        return;
    }
    ingredientsModel->removeRows(ingredientSelectionModel->currentIndex().row(), 1);
    ingredientsModel->select();
Evgenii Legotckoi
  • 12 марта 2019 г. 5:41

У вас единицы измерения храняться где-то отдельно в базе данных? Если так то можете через setRelation также попытаться сделать.

Но если честно, я в итоге всегда приходил к QSqlQueryModel. Она конечно readOnly и потом приходилось реализовывать все редактирования и удаления. Но по крайней мере за счёт формирования запроса вручную можно было достаточно сложные выборки реализовывать.

Вот пример с QSqlQueryModel

Evgenii Legotckoi
  • 12 марта 2019 г. 5:42

Видите ли, думаю, что здесь ограничение из-за нескольких связей, и не удаётся правильно обработать удаление.

Вы можете сделать удаление вручную через SQL запрос, а потом просто обновить выборку в таблице.

Вот пример такого запроса в комментарии под одной из статей

JS
  • 12 марта 2019 г. 5:47

Единицы измерения лежат там же где и названия продуктов. Просто в таблице ингредиенты нет ещё одного столбца, на который можно было бы установить setRelation. Я в итоге в базе создал ещё один пустой столбец и его редактировал c setData в который получал инфу через QSqlQueryModel.
Похоже всё проще делать через QSqlQueryModel.

Evgenii Legotckoi
  • 12 марта 2019 г. 5:51

Да вы правы. На самом деле проще через QSqlQueryModel, сколько не пытался использовать эти дженерики типо QSqlTableModel и QSqlRelationalTableModel, то всегда упирался в какие-то их ограничения. В итоге проще написать недостающий функционал для QSqlQueryModel для манипуляции с данными (поскольку она сама по себе readOnly), чем пытаться обойти какие-то ограничениях в дженерик-моделях. Они недостаточно гибкие.

JS
  • 12 марта 2019 г. 6:19

Большое спасибо за разъяснения!

s
  • 3 августа 2019 г. 7:33

А можно ли сделать связную таблицу для QTableWidget?

Evgenii Legotckoi
  • 3 августа 2019 г. 7:51

Нет. Используйте QTableView

s
  • 3 августа 2019 г. 9:44

А как там тогда checkbox сделать? Через BLOB?

Evgenii Legotckoi
  • 5 августа 2019 г. 4:54

BLOB здесь вообще не при чём. Если вам нужен checkbox, то почитайте статью про делегаты

IscanderChe
  • 6 декабря 2019 г. 4:29

Для чего нужны в коде вот эти строки?

    ui->tableView->setItemDelegate(new QSqlRelationalDelegate(ui->tableView));
...
    ui->tableViewDevice->setItemDelegate(new QSqlRelationalDelegate(ui->tableViewDevice));
Павел Дорофеев
  • 31 января 2023 г. 3:46

Хочу поделится, мы сделали свой вариант QSqlRelationalTableModel и заодно к нему новое развитие QTableView, и получилась готовая таблица PblTableDlg, у которой реализованы внешние связи, комбобоксы, чекбоксы, основные кнопки, поиск и чего там уже только нет... Настраивается все элементарно.

Открытый проект github

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 1:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 1:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 11:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 14:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 8:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 7:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 11:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
m
moogo22 ноября 2024 г. 7:17
Mosquito Spray System Effective Mosquito Systems for Backyard | Eco-Friendly Misting Control Device & Repellent Spray - Moogo ; Upgrade your backyard with our mosquito-repellent device! Our misters conce…
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 6:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 3:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…

Следите за нами в социальных сетях