QtУчим Qt вместе. Part 3

Ну вот я снова с Вами и с новой статьёй о QT =). Сегодня поговорим об интересной штуке… о том как можно получить доступ к БД и вывести данные в таблицу на форме.
Для доступа к БД мы будем использовать QtSql — набор классов для работы с базами данных используя язык структурированных запросов SQL. Основные классы данного модуля:
*QSqlDatabase — класс для предоставления соединения с базой, для работы с какой-нибудь конкретной базой данных требует объект, унаследованный от класса.
*QSqlQuery — реализует интерфейс между Qt и базами данных SQL.
*QSqlDriver — абстрактный класс, который реализуется для конкретной базы данных и может требовать для компиляции SDK базы данных. Например, для сборки драйвера под базу данных FireBird/InterBase требует .h файлы и библиотеки статической линковки, входящие в комплект поставки данной БД.

Ну думаю что всем понятно что для начала нужно создать новый проект:

demo03.pro
QT += gui
QT += sql
TARGET = demo03
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
    mainwindow.cpp
HEADERS += mainwindow.h


main.cpp
#include <QApplication>
#include <QMainWindow>

#include "mainwindow.h"
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow *mw = new MainWindow();
    mw->setGeometry(200,200,600,400);
    mw->show();
    return app.exec();
}


mainwindow.h
#include <QDateTime>
#include <QFileDialog>
 
class MainWindow : public QMainWindow
{
Q_OBJECT
 
public:
     MainWindow();
     ~MainWindow();
 
private:
     QAction *actionExit;
     QAction *actionAbout;
     QMenu *menuFile;
     QMenu *menuHelp;
     QLabel *labelMenu;
     QPushButton *butAdd;
     QPushButton *butDelete;
     QLineEdit *lineEdit;
     QSpinBox *spinBox;
     QVBoxLayout *vlayout;
     QHBoxLayout *hlayout;
     QWidget *mainWidget;
     QTableWidget *tableWidget;
 
     QSqlDatabase db;
 
     void RefreshTable();
 
private slots:
     void slotAdd();
     void slotDelete();
     void slotAbout();
};
 
#endif // MAINWINDOW_H


mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow()
{
     QTextCodec *codec = QTextCodec::codecForName("UTF8");
     QTextCodec::setCodecForTr(codec);
 
     actionExit = new QAction(tr("В&ыход"), this);
     actionExit->setStatusTip(tr("Выход из программы"));
     actionExit->setShortcut(tr("Ctrl+Q"));
     connect(actionExit, SIGNAL(triggered()), qApp, SLOT(quit()));
 
     actionAbout = new QAction(tr("&О программе"), this);
     actionAbout->setStatusTip(tr("Сведения о программе"));
     connect(actionAbout, SIGNAL(triggered()), this, SLOT(slotAbout()));
 
     menuFile = menuBar()->addMenu(tr("&Файл"));
     menuFile->addAction(actionExit);
     menuFile = menuBar()->addMenu(tr("&Справка"));
     menuFile->addAction(actionAbout);
 
     labelMenu = new QLabel(statusBar());
 
     statusBar()->setSizeGripEnabled(true);
     statusBar()->addWidget(labelMenu, 1);
 
     mainWidget = new QWidget();
     setCentralWidget(mainWidget);
 
     tableWidget = new QTableWidget();
     tableWidget->setColumnCount(4);
     tableWidget->setColumnWidth(0,200);
     tableWidget->setColumnWidth(1,200);
     tableWidget->setColumnWidth(2,50);
     tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem(tr("ID")));
     tableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem(tr("DateTime")));
     tableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem(tr("String")));
     tableWidget->setHorizontalHeaderItem(3, new QTableWidgetItem(tr("Integer")));
     tableWidget->setShowGrid(true);
     tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
     tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
     tableWidget->setColumnHidden(0, true);
 
     butAdd = new QPushButton(tr("Добавить"));
     butAdd->setStatusTip(tr("Дабавить данные"));
     connect(butAdd, SIGNAL(clicked()), this, SLOT(slotAdd()));
 
     butDelete = new QPushButton(tr("Удалить"));
     butDelete->setStatusTip(tr("Удалить данные"));
     connect(butDelete, SIGNAL(clicked()), this, SLOT(slotDelete()));
 
     lineEdit = new QLineEdit();
 
     spinBox = new QSpinBox();
 
     vlayout = new QVBoxLayout;
     hlayout = new QHBoxLayout;
 
     vlayout->addWidget(tableWidget);
     hlayout->addWidget(lineEdit);
     hlayout->addWidget(spinBox);
     hlayout->addStretch(1);
     hlayout->addWidget(butAdd);
     hlayout->addWidget(butDelete);
     vlayout->addLayout(hlayout);
     mainWidget->setLayout(vlayout);
 
     db = QSqlDatabase::addDatabase("QSQLITE");
     db.setDatabaseName(QFileDialog::getOpenFileName(0, tr("Открыть файл"), QDir::currentPath()));
     if (!db.open())
     {
         QMessageBox::warning( 0 , "Ошибка!", db.lastError().databaseText());
     }
 
     RefreshTable();
}
 
MainWindow::~MainWindow()
{
     db.close();
     db.removeDatabase(db.connectionName());
 
     delete(hlayout);
     delete(vlayout);
     delete(spinBox);
     delete(lineEdit);
     delete(butDelete);
     delete(butAdd);
     delete(tableWidget);
     delete(mainWidget);
     delete(labelMenu);
     delete(actionAbout);
     delete(actionExit);
}
 
void MainWindow::RefreshTable()
{
     int n = tableWidget->rowCount();
     for( int i = 0; i < n; i++ ) tableWidget->removeRow( 0 );
 
     QSqlQuery query;
     query.exec("SELECT * FROM tabMain;");
 
     while (query.next())
     {
          tableWidget->insertRow(0);
          tableWidget->setItem(0, 0, new QTableWidgetItem(query.value(0).toString()));
          tableWidget->setItem(0, 1, new QTableWidgetItem(query.value(1).toDateTime().toString()));
          tableWidget->setItem(0, 2, new QTableWidgetItem(query.value(2).toString()));
          tableWidget->setItem(0, 3, new QTableWidgetItem(query.value(3).toString()));
          tableWidget->setRowHeight(0, 20);
     }
}
 
void MainWindow::slotAdd()
{
     QSqlQuery query;
     query.prepare("INSERT INTO tabMain VALUES (null , :datetime, :string, :int);");
     query.bindValue(":datetime", QDateTime::currentDateTime());
     query.bindValue(":string", lineEdit->text());
     query.bindValue(":int", spinBox->value());
     query.exec();
 
     RefreshTable();
}
 
void MainWindow::slotDelete()
{
     if(tableWidget->currentIndex().row() >= 0)
     {
          QSqlQuery query;
          query.prepare("DELETE FROM tabMain WHERE ID = :id;");
          query.bindValue(":id", tableWidget->item(tableWidget->currentIndex().row(), 0)->text());
          query.exec();
 
          RefreshTable();
     }
}

void MainWindow::slotAbout()
{
     QMessageBox::about( 0 , tr("О программе..."), tr("©2009 Dmitry <a href='http://example.com'>http://example.com</a>
Специально для <a href='http://open-life.org'>OpenLife</a>"));
}


Разбор полёта

QTableWidget

Создаем таблицу QTableWidget с 4-мя колонками:
tableWidget = new QTableWidget();
tableWidget->setColumnCount(4);

Устанавливаем размер 3-х колонок(4-ю колонку мы спрячем и будем использовать для дополнительной информации):
tableWidget->setColumnWidth(0,200);
tableWidget->setColumnWidth(1,200);
tableWidget->setColumnWidth(2,50);

Называем наши колонки:
tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem(tr("ID")));
tableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem(tr("DateTime")));
tableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem(tr("String")));
tableWidget->setHorizontalHeaderItem(3, new QtableWidgetItem(tr("Integer")));

Показываем сетку(ИХМО более приятней для глаз), выключаем MultiSelect и разрешаем выделять только строки:
tableWidget->setShowGrid(true);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);

Скрвыаем колонку с индексом 0:
tableWidget->setColumnHidden(0, true);

Добавляен новую строку и вносим данные:
tableWidget->insertRow(0);
tableWidget->setItem(0, 0, new QTableWidgetItem(query.value(0).toString()));
tableWidget->setItem(0, 1, new QTableWidgetItem(query.value(1).toDateTime().toString()));
tableWidget->setItem(0, 2, new QTableWidgetItem(query.value(2).toString()));
tableWidget->setItem(0, 3, new QTableWidgetItem(query.value(3).toString()));

Устанавливаем высоту строки:
tableWidget->setRowHeight(0, 20);


QtSql

Модуль QtSql использует плагины драйверов для взаимодействия с API различных баз данных. Так как API SQL модуля не зависит от баз данных, код, специфичный для определенной БД, содержится в этих драйверах. Некоторые драйвера поставляются с Qt, а другие могут быть добавлены.

Соединение с базой данных

Прежде, чем получить доступ к базе данных с помощью классов QSqlQuery или QSqlQueryModel, вы должны установить с базой данных хотя бы одно соединение.
Соединения с базой данных идентифицируются с помощью произвольных строк. QSqlDatabase также поддерживает концепцию соединения по умолчанию, которое используется классом Qt SQL, если никакое другое соединение не указано. Этот механизм очень удобен для приложений, использующих только одно соединение с базой данных.

Пример кода устанавливающего соединение с базой данных SQLite:
QSqlDatabase  db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(Path);
db.open()

Пример кода устанавливающего соединение с базой данных MySQL(используются дополнительные параметры):
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("myBase");
db.setUserName("root");
db.setPassword("pass");
db.open();
QSqlDatabase::addDatabase() — это имя драйвера. Для получения списка драйверов, смотрите документацию addDatabase(). Для инициализации данных соединения мы вызываем setHostName(), setDatabaseName(), setUserName() и setPassword().
Для удаления соединения с базой данных, сначала закройте базу данных с помощью QSqlDatabase::close(), а затем, удалите ее с помощью статического метода QSqlDatabase::removeDatabase().

Выполнение запроса

Для выполнения SQL запросов, просто создают объект QSqlQuery и вызывают QSqlQuery::exec(). QSqlQuery предоставляет единовременный доступ к результирующей выборке одного запроса. После вызова exec(), внутренний указатель QSqlQuery указывает на позицию перед первой записью. Мы должны вызвать метод QSqlQuery::next() один раз, чтобы переместить указатель к первой записи, затем снова повторять вызов next(), чтобы получать доступ к другим записям, до тех пор пока он не вернет false.
Функция QSqlQuery::value() возвращает значение поля текущей записи. Поля задаются индексами, начиная с нуля. Функция QSqlQuery::value() возвращает значение типа QVariant, который может хранить значения различных типов C++ и ядра Qt, такие как int, QString и QByteArray. Различные типы значений базы данных автоматически приводятся к ближайшему эквиваленту в Qt.
Вы можете перемещаться взад и вперед по выборке, используя функции QSqlQuery::next(), QSqlQuery::previous(), QSqlQuery::first(), QSqlQuery::last() и QSqlQuery::seek(). Текущий номер строки можно получить с помощью QSqlQuery::at(), а общее количество строк в выборке, если это поддерживается базой данных, возвращается функцией QSqlQuery::size().

Пример(В нижеприведенном примере мы не указываем соединение, поэтому используется соединение по умолчанию.):
QSqlQuery query;
query.exec("SELECT * FROM tabMain;");

while (query.next()) {
    tableWidget->insertRow(0);
    tableWidget->setItem(0, 0, new QTableWidgetItem(query.value(0).toString()));
    tableWidget->setItem(0, 1, new QTableWidgetItem(query.value(1).toDateTime().toString()));
    tableWidget->setItem(0, 2, new QTableWidgetItem(query.value(2).toString()));
    tableWidget->setItem(0, 3, new QTableWidgetItem(query.value(3).toString()));
    tableWidget->setRowHeight(0, 20);
}
Если возникает ошибка, exec() возвращает false. Доступ к ошибке можно получить с помощью QSqlQuery::lastError().

Вставка, изменение и удаление записей

Вставляя запись в таблицу, используя INSERT, можно одновременно вставить множество записей. Но зачастую эффективней отделить запрос от реально вставляемых значений. Это можно сделать с помощью вставки значений через параметры.

В следующем примере показана вставка с помощью поименованного параметра:
QSqlQuery query;
query.prepare("INSERT INTO tabMain VALUES (null , :datetime, :string, :int);");
query.bindValue(":datetime", QDateTime::currentDateTime());
query.bindValue(":string", lineEdit->text());
query.bindValue(":int", spinBox->value());
query.exec();
Помимо удобства выполнения, вставка через параметры имеет еще и то преимущество, что вы избавлены от необходимости заботиться о преобразовании специальных символов.

Изменение записей очень похоже на вставку в таблицу:
QSqlQuery query;
query.prepare("UPDATE  tabMain SET String = ':string', Int = :int WHERE ID = :id;");
query.bindValue(":string", lineEdit->text());
query.bindValue(":int", spinBox->value());
query.bindValue(":id", iID);
query.exec();

И наконец приведем пример выражения DELETE:
QSqlQuery query;
query.prepare("DELETE FROM tabMain WHERE ID = :id;");
query.bindValue(":id", tableWidget->item(tableWidget->currentIndex().row(), 0)->text());
query.exec();


Ну вот и плод этих стараний =)
  • +12
  • DmitryG
  • 21 июля 2009, 14:18

Комментарии (11)

Почему никто для gtk или какой-нибудь легковесной бибилиотеки такое не ишет =(
Автор, лови +
Спасибо. Насчёт GTK когда то побывал её но отсутствие всяких классов(типа QtSQL, QtNetwork и т.п.) меня отпугнуло.
Не вижу в модуле QtSQL такой уж большой необходимости. Работу с базой данных можно реализовать средствами используемого языка.
Работу с базой данных можно реализовать средствами используемого языка.
Как?
В С++ есть встроенная работа с SQL?
Или Вы будете в каждой новой программе будете заново реализовывать HTTP, FTP через Сокеты сами???
GTK сама по себе является не столь всеобъемлющей и занимается всё-таки в основном GUI. Векторное рисование выделено в библиотеку Cairo, кросс-платформенная базовая библиотека — в библиотеку GLib, объектная система — в библиотеку GObject, работа с HTTP/FTP — libsoup. Вот картинка неплохая. Чем дальше от GUI, тем меньше вероятность того, что библиотека будет иметь какую-либо связь с GTK. В лучшем случае будет использовать GLib и/или GObject. Не знаю, какие библиотеки есть для работы с SQL, и имеют ли они отношение к GTK/GLib/GObject. Думаю, если глянуть на зависимости GTK'шных программ, работающих с SQL, то станет ясно.
круто, круто, круто. Осталось начать учить =)
Qt вещь хорошая…
Для тех кто собирается изучать — crossplatform.ru/
На этом ресурсе есть переводы документации, уроки, примеры, форум. На форуме весьма отзывчивые люди сидят. Ни раз выручали, за что им искренне спасибо. :)
автор, а где же деструктор? о_О
Оооо! А я думаю что то забыл! Щас допишем!
могу помочь со статьями
Спасибо за статью, жду продолжения.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.