To display the data in a widget with an arbitrary use QDataMapperWidget class. To work with this widget is still required model for presentation.
For example, QSqlTableModel or QSqlRelationalTableModel , but the data is not already substituted QTableView , and various arbitrary objects. For example QLineEdit or QComboBox . Or in the dialog box to add records with which to work in this article.
Thus, the problem is as follows. Write a program that displays a list of computers in the table, and each computer has three data fields: Имя Хоста, IP-адрес и MAC-адрес. There should also be a button to display the dialog box, through which we can add a new device to the table. Also, we should be able to edit the records through the same dialog box.
Project structure of QDataWidgetMapper
The project is created as an application Qt Widgets, where the following files are created:
- QDataMapperWidget.pro ;
- mainwindow.h - header file of the main application window;
- mainwindow.cpp - source code of window;
- main.cpp - the main source file from which the application starts;
- mainwindow.ui - form of the main application window;
- database.h - header file of helper class to be used for information that is placed in a database;
- database.cpp - source code of helper class file to be used for information that is placed in a database;
- dialogadddevice.h - header file dialog to add or edit entries;
- dialogadddevice.cpp - source file dialog box to add and edit entries;
- dialogadddevice.ui
Note. Most of the interface is created in the designer, so as not to clutter up the main logic code superfluous information. In fact it is only a matter of taste and habit.
mainwindow.ui
Form of the main window is simple. And you will use from this form the two objects:
- addDeviceButton
- deviceTableView
dialogadddevice.ui
In the form of the dialog box there are three fields a QLineEdit, two buttons and one ButtonBox, which is part of the default class that inherits from QDialog class.
We have the following items:
- HostnameLineEdit
- IPAddressLineEdit
- MACLineEdit
- buttonBox
- nextButton
- previousButton
QDataMapperWidget.pro
The profile of the project need to add a directive that declares the use of libraries of SQL.
#------------------------------------------------- # # Project created by QtCreator 2015-08-16T23:58:29 # #------------------------------------------------- QT += core QT += gui QT += sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = QDataMapperWidget TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ database.cpp \ dialogadddevice.cpp HEADERS += mainwindow.h \ database.h \ dialogadddevice.h FORMS += mainwindow.ui \ dialogadddevice.ui
main.cpp
The file used in the project, being created by default.
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
The header of the main window define slots to start the dialogue add entries, update the model and start dialogue editing entries. There are also methods for initializing the appearance of the window, in addition to the basic settings made in Qt Designer interface, as well as the method of initialization data representation model.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QSqlTableModel> #include <database.h> #include <dialogadddevice.h> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void on_addDeviceButton_clicked(); void slotUpdateModels(); void slotEditRecord(QModelIndex index); private: Ui::MainWindow *ui; DataBase *db; QSqlTableModel *modelDevice; private: void setupModel(const QString &tableName, const QStringList &headers); void createUI(); }; #endif // MAINWINDOW_H
mainwindow.cpp
The source code of the main window initialization file data tables, as was done in a previous article, for example by working with QSqlTableModel . And also prescribes the logic behavior of the application by clicking the Add button, which causes the addition of recording the dialogue table. Also, this dialog is called by double-clicking on an entry in the data table. In this case, information transmitted in a dialogue on which recording has been pressed and its data are substituted into the field for editing.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); this->setWindowTitle("QDataWidgetMapper Example"); /* The first step is to create an object for the database * and initialize the database connection * */ db = new DataBase(); db->connectToDataBase(); /* Initialize the model to represent the data indicating the names of the columns * */ this->setupModel(DEVICE, QStringList() << trUtf8("id") << trUtf8("Имя хоста") << trUtf8("IP адрес") << trUtf8("MAC-адрес") ); this->createUI(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::setupModel(const QString &tableName, const QStringList &headers) { modelDevice = new QSqlTableModel(this); modelDevice->setTable(tableName); modelDevice->select(); /* Set the columns names in a table with sorted data * */ for(int i = 0, j = 0; i < modelDevice->columnCount(); i++, j++){ modelDevice->setHeaderData(i,Qt::Horizontal,headers[j]); } } void MainWindow::createUI() { ui->deviceTableView->setModel(modelDevice); ui->deviceTableView->setColumnHidden(0, true); ui->deviceTableView->setSelectionBehavior(QAbstractItemView::SelectRows); ui->deviceTableView->setSelectionMode(QAbstractItemView::SingleSelection); ui->deviceTableView->resizeColumnsToContents(); ui->deviceTableView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->deviceTableView->horizontalHeader()->setStretchLastSection(true); connect(ui->deviceTableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotEditRecord(QModelIndex))); } /* Method for activating dialogue of adding records * */ void MainWindow::on_addDeviceButton_clicked() { /* Create a dialogue and connect it to signal the completion * of the form slot refresh data representation model * */ DialogAddDevice *addDeviceDialog = new DialogAddDevice(); connect(addDeviceDialog, SIGNAL(signalReady()), this, SLOT(slotUpdateModels())); addDeviceDialog->setWindowTitle(trUtf8("Добавить Устройство")); addDeviceDialog->exec(); } /* Slot of update data representation model * */ void MainWindow::slotUpdateModels() { modelDevice->select(); } /* Method for activating dialogue adding entries to the edit mode * with the transmission of the selected row index * */ void MainWindow::slotEditRecord(QModelIndex index) { DialogAddDevice *addDeviceDialog = new DialogAddDevice(index.row()); connect(addDeviceDialog, SIGNAL(signalReady()), this, SLOT(slotUpdateModel())); addDeviceDialog->setWindowTitle(trUtf8("Редактировать Устройство")); addDeviceDialog->exec(); }
dialogadddevice.h
Header File dialog for adding and editing entries. As can be seen on the header file, there is also used a model to represent the data, but it does not broadcast data to a QTableView , such as in the tutorial on QSqlRelationalModel or MainWindow class of this article, and in QDataWidgetMapper object class. There is also overridden by accept() method, as before, close the window, you must make sure that the data is filled in correctly. In this project, the criterion of correctness of filling of data is the lack of duplicates.
#ifndef DIALOGADDDEVICE_H #define DIALOGADDDEVICE_H #include <QDialog> #include <QSqlTableModel> #include <QDataWidgetMapper> #include <QMessageBox> #include <database.h> namespace Ui { class DialogAddDevice; } class DialogAddDevice : public QDialog { Q_OBJECT public: explicit DialogAddDevice(int row = -1, QWidget *parent = 0); ~DialogAddDevice(); signals: void signalReady(); private slots: void on_buttonBox_accepted(); void updateButtons(int row); private: Ui::DialogAddDevice *ui; QSqlTableModel *model; QDataWidgetMapper *mapper; private: void setupModel(); void createUI(); void accept(); }; #endif // DIALOGADDDEVICE_H
dialogadddevice.cpp
In this file the logic of class was implemented. Checking the correctness of the entered data is carried out when you press the OK button. When properly filled fields produced record insert in the table and edit it. Validation of data in the IP адрес and MAC адрес is performed using a validator that writing has been described in a previous article . Also implemented on the verification record the existence of similar data.
#include "dialogadddevice.h" #include "ui_dialogadddevice.h" DialogAddDevice::DialogAddDevice(int row, QWidget *parent) : QDialog(parent), ui(new Ui::DialogAddDevice) { ui->setupUi(this); /* Method for initializing the model, from which data will be transmitted * */ setupModel(); /* If the string is not specified, that is equal to -1, * then the dialog works on the principle of creating a new record. * Namely, in the model of the new line is inserted and the work is done with it. * */ if(row == -1){ model->insertRow(model->rowCount(QModelIndex())); mapper->toLast(); /* Otherwise, the dialogue is tuned to a given record * */ } else { mapper->setCurrentModelIndex(model->index(row,0)); } createUI(); } DialogAddDevice::~DialogAddDevice() { delete ui; } void DialogAddDevice::setupModel() { model = new QSqlTableModel(this); model->setTable(DEVICE); model->setEditStrategy(QSqlTableModel::OnManualSubmit); model->select(); /* Initialize mapper and bind data fields to objects QLineEdit * */ mapper = new QDataWidgetMapper(); mapper->setModel(model); mapper->addMapping(ui->HostnameLineEdit, 1); mapper->addMapping(ui->IPAddressLineEdit, 2); mapper->addMapping(ui->MACLineEdit, 3); /* Manual confirmation of the change via the mapper * */ mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); /* Connect "Connects" from the scroll buttons to prilistyvaniyu data model mapper * */ connect(ui->previousButton, SIGNAL(clicked()), mapper, SLOT(toPrevious())); connect(ui->nextButton, SIGNAL(clicked()), mapper, SLOT(toNext())); /* When you change the index mapper to change the state of the buttons * */ connect(mapper, SIGNAL(currentIndexChanged(int)), this, SLOT(updateButtons(int))); } /* The method for installing the validator to enter the IP and MAC addresses * */ void DialogAddDevice::createUI() { QString ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"; QRegExp ipRegex ("^" + ipRange + "\\." + ipRange + "\\." + ipRange + "\\." + ipRange + "$"); QRegExpValidator *ipValidator = new QRegExpValidator(ipRegex, this); ui->IPAddressLineEdit->setValidator(ipValidator); QString macRange = "(?:[0-9A-Fa-f][0-9A-Fa-f])"; QRegExp macRegex ("^" + macRange + "\\:" + macRange + "\\:" + macRange + "\\:" + macRange + "\\:" + macRange + "\\:" + macRange + "$"); QRegExpValidator *macValidator = new QRegExpValidator(macRegex, this); ui->MACLineEdit->setValidator(macValidator); } void DialogAddDevice::on_buttonBox_accepted() { /* SQL-query to check for the existence of records with the same credentials. * If a record does not exist or is only editable index is currently recording, * the dialog box allows you to record in a data table * */ QSqlQuery query; QString str = QString("SELECT EXISTS (SELECT " DEVICE_HOSTNAME " FROM " DEVICE " WHERE ( " DEVICE_HOSTNAME " = '%1' " " OR " DEVICE_IP " = '%2' )" " AND id NOT LIKE '%3' )") .arg(ui->HostnameLineEdit->text(), ui->IPAddressLineEdit->text(), model->data(model->index(mapper->currentIndex(),0), Qt::DisplayRole).toString()); query.prepare(str); query.exec(); query.next(); /* If a record exists, the dialog is a warning message * */ if(query.value(0) != 0){ QMessageBox::information(this, trUtf8("Ошибка хоста"), trUtf8("Хост с таким именем или IP-адресом уже существует")); /* Otherwise, you are inserting new data into the table and the dialogue ends * with the transmission of a signal to update the table in the main window * */ } else { mapper->submit(); model->submitAll(); emit signalReady(); this->close(); } } void DialogAddDevice::accept() { } void DialogAddDevice::updateButtons(int row) { /* In that case, if we reach one of the extremes (the first or the last) of the indices * in the data table, then we change the state * of a corresponding button on the inactive state * */ ui->previousButton->setEnabled(row > 0); ui->nextButton->setEnabled(row < model->rowCount() - 1); }
database.h
Helper class to work with the database is a modified variant of the same class from the previous lessons.
#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 DEVICE "DeviceTable" #define DEVICE_HOSTNAME "Hostname" #define DEVICE_IP "IP" #define DEVICE_MAC "MAC" class DataBase : public QObject { Q_OBJECT public: explicit DataBase(QObject *parent = 0); ~DataBase(); void connectToDataBase(); bool inserIntoDeviceTable(const QVariantList &data); private: QSqlDatabase db; private: bool openDataBase(); bool restoreDataBase(); void closeDataBase(); bool createDeviceTable(); }; #endif // DATABASE_H
database.cpp
#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->createDeviceTable()){ return false; } else { return true; } } else { qDebug() << "Failed to restore the database"; 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::createDeviceTable() { 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," DEVICE_MAC " VARCHAR(18) NOT NULL" " )" )){ qDebug() << "DataBase: error of create " << DEVICE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; } bool DataBase::inserIntoDeviceTable(const QVariantList &data) { QSqlQuery query; query.prepare("INSERT INTO " DEVICE " ( " DEVICE_HOSTNAME ", " DEVICE_IP ", " DEVICE_MAC " ) " "VALUES (:Hostname, :IP, :MAC )"); query.bindValue(":Hostname", data[0].toString()); query.bindValue(":IP", data[1].toString()); query.bindValue(":MAC", data[2].toString()); if(!query.exec()){ qDebug() << "error insert into " << DEVICE; qDebug() << query.lastError().text(); return false; } else { return true; } return false; }
Result
Archive with source: QDataWidgetMapper
The result it should look like as follows:
Вопрос: Как записать данные в столбец который не привязан к элементу ввода в форме? Например в таблице
привязаны к lineedit все поля кроме object_id и brend_id. object id это автоинкремент с ним все понятно подставляется значение по умолчанию. Как с используя QDataWidgetMapper записывать определенное значение в brend_id? При редактировании записей никаких вопросов нет а вот при попытке добавления выдает ошибку.Либо добавить в форум соответствующий виджет и сделать его скрытым через setVisible(false) , либо написать собственный маппер, в котором некоторые поля будут обрабатываться внутри маппера по заданному алгоритму. Я изучал вопрос маппера в QML, вот статья на эту тему , но там не для QSqlTableModel , а просто для модели данных.
Если делать со скрытым виджетом, то следует отслеживать сигнал currentIndexChanged и уже по нему обрабатывать требуемые скрытые виджеты так, как требуется. В документации нет и намёка на работу со скрытыми полями каким-либо документированным способом, поэтому вариант со скрытыми виджетами видится мне наиболее адекватным.
Про скрытый виджет идея пришла сразу, но думал что есть другие варианты.
Увы, но других вариантов я не вижу. QDataWidgetMapper явно подходит только для этого, иначе был бы какой-нибудь класс держатель значений. Но тот же самый QVariant, что наиболее близкое по смыслу, здесь явно не подходит. Так что... только скрытый виджет.
Успешно заработало :-)
Ну и отлично :-)
Как сделать так, что бы когда я открываю диалог, я не могу ничего делать в программе, пока я не закрою сам диалог? (Я не заметил(плохо прочел код))
Очень долго искал подобное описание.Спасибо. Только подскажи пожалуйста, как можно реализовать удаление строки с помощью кнопки
Нужно навешать слот на клик кнопки
И еще, как можно из диалогового окна, из QTextEdit , передать данные в таблицу?
Спасибо большое, но немного не пойму, что именно нужно прописать в слоте, что бы получать id и удалять через кнопку
Вставка данных в таблицу базы данных производится через INSERT SQL запрос. Пример такого запроса есть в этой статье
Можете в Mapper`e прикрутить ещё одно поле ввода, которое будет выводить id записи. Поле ввода можете настроить в режим readOnly, чтобы его нельзя было редактировать. Тогда сможете забирать через метод text() или value() данный id записи.
Хорошо, спасибо большое за помощь
Evileg, Здравствуйте. Можете пожалуйста пояснить, вот есть метод:
Добрый день!
Так получилось, что конкретно этот метод не используется в данном примере.
А где находится связь между нажатием на кнопку "ok" и выполнением слота
Спасибо!
Здравствуйте! В этой статье и нескольких Ваших других заметил метод closeDataBase(), но не нашел чтобы он использовался. И вообще во многих источниках видел исходники без закрытия БД. В php обычно (всегда) после запроса(ов) БД закрывается, и выходит здесь (в qt c++) также такое предусмотрено, но не совсем ясно понял когда и где его нужно (целесообразно) использовать? Как быть, если программа обращается к БД редко единичным образом или же когда все время требуется соединение и частые обращения?
В Qt После создания соединения с БД оно хранится на протяжения работы с приложением, но не постоянно. Необходимости закрывать соединение особо нет.
Добрый день!
@EVILEG can you kindly provide a response? Please
If you want to show two columns from your model in combobox, then, I think, you can try to set Model in QComboBox and use custom ItemDelegate for QComboBox. Try to research information about delegates in official documentation.
А как можно добавить картинку в БД через QDataWidgetMapper? Это делается методом addMapping? Статью про изображение в базе данных я читал.
Добрый день. Я не проверял mapping изображений. Тем более, что вопрос в том, на что именно их маппить. В той статье делается на QLabel. А это нередактируемый виджет, как например QLineEdit. Тем более, что такой специфичный функционал как изображения вряд ли подготовливался для маппинга.
Так что здесь скорее при выборе записи нужно или делать дополнительный запрос к базе данных или смотреть, содержит ли текущая запись информацию об изображении и помещать его на QLabel. То есть прямой маппинг не заработает, по моему мнению.
Мне просто надо изображение из QLabel'a, расположеннго на диалоговом окне, вставить в БД. Значит, всё-таки это лучше сделать через запрос?
Лучше через запрос. Видите ли, если вы будете вставлять изображение в базу данных, там Вам придётся преобразовать данные изображения в BLOB. Как в той статье. А в QDataWidgetMapper такая логика просто отсутсвует. Так что здесь или наследоваться от QDataWidgetMapper и писать свой, или просто через запрос.
Через запрос проще и быстрее. Через наследование уже больше мороки, но в крупных проектах это окупается (В ОЧЕНЬ КРУПНЫХ)
Реализовал поиск через QSortFilterProxyModel, но при выборе результата в Mapper передаются значения из tableView по порядку, а не полученные в результате поиска значения. Такая же проблема при сортировке данных в tableView.
Как правильно реализовать поиск?
Для фильтрации QSqlTableModel нужно использовать метод setFilter у этой самой модели QSqlTableModel. Туда передаётся SQL запрос для where
Начал делать что-то похожее, но без sql.
Не очень понимаю, что делает
Вернее не понимаю вот что: откуда он берёт данные, которые затем подставляет в диалог?
Моё собственное решение (делаю через QStandartItemModel) — передать в конструктор диалога все соответствующие поля из моей таблицы, которые затем через setText() прописать в QLineEdit.
Работать — работает (значения, разумеется, подставляются), но правильно ли так делать?
Понял сам так - происходит через setTable() "Sets the database table on which the model operates to tableName. Does not select data from the table, but fetches its field information", а затем через select() "Populates the model with data from the table that was set via setTable(), using the specified filter and sort condition, and returns true if successful; otherwise returns false".
Hi EVILEG,
Thnaks for example.
With this example code i am not getting header data. Any idea? I am new to QT and C++.
Can someone please help.