c++ stl & qt. Занятие 05
TRANSCRIPT
Темы лекции: Базы данных в Qt.
Практическое задание: Базы данных в Qt.
Тренер: Игорь Шкулипа, к.т.н.
С++ Библиотеки STL и Qt. Занятие 5
http://www.slideshare.net/IgorShkulipa 2
Адаптер
Паттерн Adapter, представляет собой программную обертку надсуществующими классами, преобразуя их интерфейсы к виду,пригодному для последующего использования.
Пусть класс, интерфейс которого нужно адаптировать к нужномувиду, имеет имя Adaptee. Для решения задачи преобразованияего интерфейса паттерн Adapter вводит следующую иерархиюклассов:
◦ Виртуальный базовый класс Target. Здесь объявляетсяпользовательский интерфейс подходящего вида. Только этотинтерфейс доступен для пользователя.
◦ Производный класс Adapter, реализующий интерфейс Target.В этом классе также имеется указатель или ссылка наэкземпляр Adaptee. Паттерн Adapter использует этотуказатель для перенаправления клиентских вызовов вAdaptee. Так как интерфейсы Adaptee и Target несовместимымежду собой, то эти вызовы обычно требуютпреобразования.
http://www.slideshare.net/IgorShkulipa 3
Пример. Преобразование строки в структуру
class InputStringFullName {
protected:
string _strText;
public:
InputStringFullName() {
_strText="";
}
void Input() {
cout<<"Input Full Name (Surname Name MiddleName): ";
char* cstr=new char;
cin.getline(cstr, 9999, '\n');
_strText=string(cstr);
}
string GetText(){return _strText;}
};
http://www.slideshare.net/IgorShkulipa 4
Класс структуры
class FullName {
private:
string _strName; string _strSurname; string _strMiddle;
public:
FullName(string strSurname, string strName, string strMiddleName) {
_strName=strName; _strSurname=strSurname; _strMiddle=strMiddleName;
}
void Print() {
cout<<"Name: "<<_strName<<"\n";
cout<<"Middle Name: "<<_strMiddle<<"\n";
cout<<"Surname: "<<_strSurname<<"\n";
}
string GetName() {return _strName;}
string GetSurname() {return _strSurname;}
string GetMiddle() {return _strMiddle;}
void SetName(string strText) {_strName=strText;}
void SetSurname(string strText) {_strSurname=strText;}
void SetMiddle(string strText) {_strMiddle=strText;}
};
http://www.slideshare.net/IgorShkulipa 5
Класс-адаптер
class FullNameAdapter: public InputStringFullName
{
public:
FullNameAdapter(InputStringFullName* stringFullName)
{
_strText=stringFullName->GetText();
}
FullName* GetFullName()
{
unsigned iFirstSpace=_strText.find_first_of(" ");
string strSurname=_strText.substr(0,iFirstSpace);
unsigned iSecondSpace=
_strText.substr(iFirstSpace+1).find_first_of(" ")+iFirstSpace+1;
string strName=_strText.substr(iFirstSpace, iSecondSpace-iFirstSpace);
string strMiddle=_strText.substr(iSecondSpace);
return new FullName(strSurname, strName, strMiddle);
}
};
http://www.slideshare.net/IgorShkulipa 6
Использование адаптера
int main()
{
InputStringFullName* isfn=new InputStringFullName();
isfn->Input();
FullNameAdapter* fna=new FullNameAdapter(isfn);
FullName* fn=fna->GetFullName();
fn->Print();
}
Результат:Input Full Name (Surname Name MiddleName): Ivanov Petr Sidorovich
Name: Petr
Middle Name: Sidorovich
Surname: Ivanov
http://www.slideshare.net/IgorShkulipa 7
Команда
Паттерн Command преобразовывает запрос на выполнениедействия в отдельный объект-команду. Такая инкапсуляцияпозволяет передавать эти действия другим функциям иобъектам в качестве параметра, приказывая им выполнитьзапрошенную операцию. Команда – это объект, поэтому надней допустимы любые операции, что и над объектом.
Команда используется, если:
◦ Система управляется событиями. При появлении такогособытия (запроса) необходимо выполнить определеннуюпоследовательность действий.
◦ Необходимо параметризировать объекты выполняемымдействием, ставить запросы в очередь или поддерживатьоперации отмены и повтора действий.
◦ Нужен объектно-ориентированный аналог функцииобратного вызова в процедурном программировании.
http://www.slideshare.net/IgorShkulipa 8
Пример. Интерфейс «Игра»
class IGame
{
public:
virtual void New()=0;
virtual void Start()=0;
virtual void Pause()=0;
virtual void Resume()=0;
virtual void Finish()=0;
virtual void Break()=0;
virtual void BreakAndFinish()=0;
virtual void BreakAndStart()=0;
};
http://www.slideshare.net/IgorShkulipa 9
Реализация интерфейса
class SomeGame: public IGame {
public:
virtual void New() {
cout<< "New Game.\n“; }
virtual void Start() {
cout<< "Game Started.\n“; }
virtual void Pause() {
cout<< "Game Paused.\n“; }
virtual void Resume() {
cout<< "Game Resumed.\n“; }
virtual void Finish() {
cout<< "Game Finished.\n“; }
virtual void Break() {
cout<< "Game Breaked.\n“; }
virtual void BreakAndFinish() {
this->Break(); this->Finish(); }
virtual void BreakAndStart() {
this->Break(); this->New(); this->Start(); }
};
http://www.slideshare.net/IgorShkulipa 10
Классы команд
class ICommand {
public:
ICommand(IGame* game){ _game=game; }
virtual void ExecuteCommand()=0;
virtual ~ICommand(){ delete _game;}
protected: IGame* _game;
};
class NewCommand: public ICommand {
public:
NewCommand(IGame* game):ICommand(game){}
virtual void ExecuteCommand(){
cout<<"Executing New Command...\n";
_game->New();
} };
class StartCommand: public ICommand {
public:
StartCommand(IGame* game):ICommand(game){}
virtual void ExecuteCommand() {
cout<<"Executing Start Command...\n";
_game->Start();
} };
http://www.slideshare.net/IgorShkulipa 11
Классы команд
class PauseCommand: public ICommand {
public:
PauseCommand(IGame* game):ICommand(game){}
virtual void ExecuteCommand() {
cout<<"Executing Pause Command...\n";
_game->Pause();
} };
class ResumeCommand: public ICommand {
public:
ResumeCommand(IGame* game):ICommand(game){}
virtual void ExecuteCommand() {
cout<<"Executing Resume Command...\n";
_game->Resume();
} };
class FinishCommand: public ICommand {
public:
FinishCommand(IGame* game) :ICommand(game){}
virtual void ExecuteCommand() {
cout<<"Executing Finish Command...\n";
_game->Finish();
} };
http://www.slideshare.net/IgorShkulipa 12
Классы командclass BreakCommand: public ICommand {
public:
BreakCommand(IGame* game):ICommand(game){}
virtual void ExecuteCommand() {
cout<<"Executing Break Command...\n";
_game->Break();
} };
class BreakAndFinishCommand: public ICommand {
public:
BreakAndFinishCommand(IGame* game):ICommand(game){}
virtual void ExecuteCommand() {
cout<<"Executing Break And Finish Command...\n";
_game->BreakAndFinish();
} };
class BreakAndStartCommand: public ICommand {
public:
BreakAndStartCommand(IGame* game) :ICommand(game){}
virtual void ExecuteCommand() {
cout<<"Executing Break And Start Command...\n";
_game->BreakAndStart();
} };
http://www.slideshare.net/IgorShkulipa 13
Использование команд
int main()
{
IGame* someGame=new SomeGame();
ICommand* command[10];
command[0]=new NewCommand(someGame);
command[1]=new StartCommand(someGame);
command[2]=new BreakCommand(someGame);
command[3]=new NewCommand(someGame);
command[4]=new StartCommand(someGame);
command[5]=new BreakAndStartCommand(someGame);
command[6]=new PauseCommand(someGame);
command[7]=new ResumeCommand(someGame);
command[8]=new PauseCommand(someGame);
command[9]=new BreakAndFinishCommand(someGame);
for (int i=0;i<10;i++)
{
command[i]->ExecuteCommand();
}
}
Результат:Executing New Command...
New Game.
Executing Start Command...
Game Started.
Executing Break Command...
Game Breaked.
Executing New Command...
New Game.
Executing Start Command...
Game Started.
Executing Break And Start Command...
Game Breaked.
New Game.
Game Started.
Executing Pause Command...
Game Paused.
Executing Resume Command...
Game Resumed.
Executing Pause Command...
Game Paused.
Executing Break And Finish Command...
Game Breaked.
Game Finished.
http://www.slideshare.net/IgorShkulipa 14
Подключение к базе
Класс QSqlDatabase предоставляет интерфейс для подключения к базеданных через соединение. Экземпляр класса QSqlDatabaseпредставляет соединение. Соединение предоставляет доступ к базеданных через один из поддерживаемых драйверов баз данных,которые унаследованы от QSqlDriver. В качестве альтернативы,можно создать класс-наследник от QSqlDriver для драйвера своей базыданных.
Создать соединение можно с помощью одной из статических функцийaddDatabase(), которой передается используемый драйвер или типдрайвера и имя соединения.
Соединение известно под своим собственным именем, а не по имени базыданных с которой оно соединяет. Можно иметь множество соединенийс одной и той же базой данных.
QSqlDatabase также поддерживает концепцию соединения по умолчанию,которое является неименованным (unnamed) соединением. Чтобысоздать соединения по умолчанию при вызове addDatabase() непередавайте аргумент с именем соединения.
http://www.slideshare.net/IgorShkulipa 15
Драйвера баз данных Qt
Тип драйвера Описание
QDB2 IBM DB2
QIBASE Драйвер Borland InterBase
QMYSQL Драйвер MySQL
QOCI Драйвер Oracle Call Interface
QODBC Драйвер ODBC (включая Microsoft SQL Server)
QPSQL Драйвер PostgreSQL
QSQLITE SQLite версии 3 или выше
QSQLITE2 SQLite версии 2
QTDS Драйвер Sybase Adaptive Server
http://www.slideshare.net/IgorShkulipa 16
Пример соединения
QSqlDatabase db = QSqlDatabase::addDatabase(“QMYSQL”);
db.setHostName(“somehost.com”);
db.setDatabaseName(“mydb”);
db.setUserName(“admin”);
db.setPassword(“pjgf”);
bool ok = db.open();
http://www.slideshare.net/IgorShkulipa 17
Выполнение запросовКласс QSqlQuery предоставляет возможность выполнения любой
инструкции SQL, поддерживаемой используемой базой данных.
Перемещения по записям осуществляется следующими методами:
• next()
• previous()
• first()
• last()
• seek()
Эти функции позволяют двигаться вперед, назад или произвольно позаписям, возвращаемым запросом.
Если вам нужно двигаться только вперед по результатам, можноиспользовать setForwardOnly(), что позволит сэкономитьзначительный объем памяти, снизить накладные расходы и повыситьпроизводительность на некоторых базах данных.
Если активный запрос находится на действительной записи, данныеможно получить с помощью value(). Все данные передаются извнутреннего интерфейса SQL с использованием QVariant.
http://www.slideshare.net/IgorShkulipa 18
Пример запроса
QSqlQuery query;
query.exec("SELECT u_name FROM users");
while (query.next()) {
QString name = query.value(0).toString();
doSomething(name);
}
http://www.slideshare.net/IgorShkulipa 19
Проверка ошибок запроса
Можно проверить наличие ошибок запроса, вызвав метод IsActive()
if (!query.isActive())
{
QMessageBox *mb=new QMessageBox();
mb->setText(query.lastError().text());
mb->show();
}
http://www.slideshare.net/IgorShkulipa 20
Запрос вставки данных
Запросы INSERT, UPDATE, DELETE и DROP выполняются так же, как иSELECT (передаются в текстовом виде).
QSqlQuery query;
query.exec(“INSERT INTO users VALUES(1, ‘User1’, ‘Password1’)”);
или
QSqlQuery query(“INSERT INTO users VALUES(1, ‘User1’, ‘Password1’)”);
После этого метод numRowsAffected() возвращает количество строк,которые были изменены инструкцией запроса или -1 в случае ошибки.
http://www.slideshare.net/IgorShkulipa 21
Вставка по позициям
Если необходимо избежать преобразования значений в строковыеданные, то можно воспользоваться методом prepare() для созданияшаблона запроса с последующим присваиванием данных.
QSqlQuery q;
q.prepare(
"INSERT INTO users (u_id, u_name, u_password)
VALUES (?,?,?)");
q.addBindValue(1);
q.addBindValue(“User1”);
q.addBindValue(“Password1”);
q.exec();
http://www.slideshare.net/IgorShkulipa 22
Пример. См. Предыдущую презентацию.
Дополнение к проекту «MVP»:
• Добавлен сигнал к IView - virtual void onRegister()=0;
• Добавлен слот к презентеру - void Register();
• Добавлен метод в модель –void InsertUser(QString user, QString password);
• Добавлено два приватный метода в модель –void insertQueryTextStyle(QString user, QString password);
void insertQueryPositionStyle(QString user, QString password);
• Добавлена кнопка регистрации на основное окно.
http://www.slideshare.net/IgorShkulipa 23
Класс-одиночка «Подключение к базе»
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlDriver>
class DBConnection
{
public:
static DBConnection* GetInstance(){
if (_connection==NULL) _connection= new DBConnection();
return _connection;
}
~DBConnection();
QSqlQuery execQuery(QString queryText);
private:
DBConnection();
static DBConnection* _connection;
QSqlDatabase _database;
};
http://www.slideshare.net/IgorShkulipa 24
Реализация подключения
#include "dbconnection.h"
#include <QtDebug>
#include <QSqlError>
DBConnection* DBConnection::_connection=NULL;
DBConnection::DBConnection(){
_database=QSqlDatabase::addDatabase("QODBC3");
_database.setHostName("localhost");
_database.setDatabaseName(
"Driver={Microsoft Access Driver (*.mdb, *.accdb)};
DSN='';DBQ=D:\\a\\qtdb.accdb");
_database.setUserName("");
_database.setPassword("");
if (!_database.open())
{qDebug() << _database.lastError().text();}
}
QSqlQuery DBConnection::execQuery(QString queryText){
return QSqlQuery(queryText);
}
DBConnection::~DBConnection(){
_database.close();
}
http://www.slideshare.net/IgorShkulipa 25
Новый конструктор модели
Model::Model()
{
DBConnection* db= DBConnection::GetInstance();
QSqlQuery q= db->execQuery
("SELECT u_id, u_name, u_password FROM users;");
if (!q.isActive())
{
qDebug() << q.lastError().text());
}
if (q.isActive())
{
while(q.next())
{
QString uname=q.value(1).toString();
QString upass=q.value(2).toString();
usersAndPasswords.insert(uname, upass);
}
}
}
http://www.slideshare.net/IgorShkulipa 26
Методы вставки
void Model::insertQueryPositionStyle(QString user, QString password) {
QSqlQuery q;
q.prepare("INSERT INTO users (u_name, u_password) VALUES (?,?)");
q.addBindValue(user);
q.addBindValue(password);
q.exec();
}
void Model::insertQueryTextStyle(QString user, QString password) {
QSqlQuery q
("INSERT INTO users (u_name, u_password) VALUES
(\'"+user+"\',\'"+password+"\');");
if (!q.isActive())
{
qDebug() << q.lastError().text());
}
}
void Model::InsertUser(QString user, QString password) {
insertQueryPositionStyle(user, password);
}
http://www.slideshare.net/IgorShkulipa 28
QSqlTableModel
Кроме QSqlQuery, Qt содержит класс QSqlTableModel – это интерфейсвысокого уровня, который позволяет не использовать SQL в чистомвиде для выполнения запросов.
QSqlTableModel model;
model.setTable(“users”);
model.setFilter(“id>10”);
model.select();
Просмотр результата запроса осуществляется с помощью метода record()и доступа к отдельным полям с помощью метода value().
Метод value() принимает в качестве аргумента либо индекс поля, либо егоимя.
QSqtring user=model.record().value(1).toString();
QSqtring password=model.record().value(“u_password”).toString();
http://www.slideshare.net/IgorShkulipa 29
Вставка данных
Для вставки данных можно воспользоваться методом insertRows() длясоздания новой пустой строки в таблице, а затем использоватьsetData() для установки значения каждого столбца.
QSqlTableModel model;
model.setTable(“users”);
int row=0;
model.insertRows(row, 1);
model.setData(model.index(row,0), 1);
model.setData(model.index(row,0), “User1”);
model.setData(model.index(row,0), “Password1”);
model.submitAll();
http://www.slideshare.net/IgorShkulipa 30
Шаблон интерфейса «master-detail»
Классическим примером для любого средства разработки интерфейса кБД является отражение связи "родитель-потомок" (Mater-Detail).
http://www.slideshare.net/IgorShkulipa 31
Пример. Основное окно
http://www.slideshare.net/IgorShkulipa 32
Пример. База данных
http://www.slideshare.net/IgorShkulipa 33
Заголовок окна#include <QMainWindow>
#include <QModelIndex>
#include <QSqlTableModel>
#include <QSqlRelationalTableModel>
#include <QSqlDatabase>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_tableView_clicked(const QModelIndex &index);
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QSqlRelationalTableModel* masterModel;
QSqlTableModel* detailModel;
};
http://www.slideshare.net/IgorShkulipa 34
Реализация окна#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QSqlError>
#include <QSqlRecord>
#include <QAbstractItemView>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
QSqlDatabase database=QSqlDatabase::addDatabase("QODBC3");
database.setHostName("localhost");
database.setDatabaseName
("Driver={Microsoft Access Driver (*.mdb, *.accdb)};
DSN='';DBQ=D:\\a\\qtdb.accdb");
database.setUserName(""); database.setPassword("");
if (!database.open()) {qDebug() << database.lastError().text();}
masterModel=new QSqlRelationalTableModel(this, database);
detailModel=new QSqlTableModel(this, database);
}
MainWindow::~MainWindow() {
delete ui;
delete masterModel;
delete detailModel;
}
http://www.slideshare.net/IgorShkulipa 35
Реализация окна
void MainWindow::on_tableView_clicked(const QModelIndex &index) {
detailModel->setTable("people");
detailModel->setFilter(QString("p_uid=%1").arg(index.row()+1));
detailModel->select();
QString text="";
if (detailModel->rowCount()>0) {
text+=detailModel->record(0).value(0).toString()+"\n";
text+=detailModel->record(0).value(1).toString()+"\n";
text+=detailModel->record(0).value(2).toString()+"\n";
text+=detailModel->record(0).value(3).toString()+"\n";
text+=detailModel->record(0).value(4).toString();
}
ui->textEdit->setText(text);
}
void MainWindow::on_pushButton_clicked() {
masterModel->setTable("users");
masterModel->setRelation(0, QSqlRelation("people", "p_id", "p_name"));
masterModel->setHeaderData(0, Qt::Horizontal, "Name");
masterModel->setHeaderData(1, Qt::Horizontal, "User");
masterModel->setHeaderData(2, Qt::Horizontal, "Password");
masterModel->select();
ui->tableView->setModel(masterModel);
}
http://www.slideshare.net/IgorShkulipa 37
Лабораторная работа №5. Базы данных
Добавить к тетрису возможность регистрации пользователей и веденияполученных очков.
Реализовать отображение пользователей, отсортированных по рейтингу,на основном окне приложения с применением шаблона «master-detail»