Рубрика:
Программирование /
Разработка
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
Владимир Тряпичко
Организуем доступ к базам данных при разработке кроссплатформенных приложений на C++/wxWidgets
Очень часто на форумах появляются вопросы, связанные с организацией доступа к базам данных для приложений на C++. Тема актуальная и очень интересная, хотя у новичков зачастую вызывает трудности. Но написание кроссплатформенных приложений на C++, использующих для своей работы базы данных, не является чем-то непосильным, кроссплатформенная разработка может оказаться увлекательным занятием.
Речь пойдет о разработке приложений, использующих для своей работы базы данных SQLite. Учиться кросс-платформенной разработке на C++ мы будем на примере простейшего приложения, работающего с базами данных – адресной книги.
Инструментарий
Для работы нам понадобятся:
Windows
В качестве среды разработки будем использовать Microsoft Visual Studio 2005. Для создания кросс-платформенных приложений подойдет любая редакция Visual Studio, включая бесплатную Visual Studio 2005 Express Edition. В случае использования Visual Studio 2005 Express Edition необходимо дополнительно установить Microsoft Platform SDK.
Настройка проекта в Visual Studio 2005
Для начала создадим новый проект (Win32 Project) в Visual Studio 2005 и назовем его SQLiteTest.
Затем распакуем архив с исходным кодом библиотеки DatabaseLayer в папку с нашим решением (Solution). Добавляем в Solution проект databaselayer_databaselayer_sqlite.dsp из дистрибутива DatabaseLayer.
Теперь нам необходимо указать зависимости проектов. Наше приложение должно быть собрано после сборки библиотеки DatabaseLayer, поэтому в Solution Explorer жмем правой кнопкой на названии проекта SQLiteTest, выбираем пункт меню «Project Dependencies...», в списке проектов ставим маркер на названии проекта databaselayer_sqlite и жмем «OK».
Далее выбираем пункт меню «Build -> Configuration Manager» и указываем конфигурации каждого проекта для отладочной и финальной версии решения. При написании статьи использовалась статическая раздельная Unicode-сборка wxWidgets, поэтому для Debug-версии решения была выбрана конфигурация Static Unicode Debug Multilib проекта databaselayer_sqlite и конфигурация Debug проекта SQLiteTest. Такие же действия необходимо выполнить и для Release-версии, заменив все вхождения слова Debug в названиях конфигураций на Release.
После этого распаковываем архив sqlite-source-x_y_z.zip в папку databaselayer/sqlite/include и архив sqlitedll-x_y_z.zip в папку databaselayer/sqlite/lib. В результате в папке databaselayer/sqlite/lib должны появиться файлы sqlite3.dll и sqlite3.def.
Всё это очень хорошо, но для использования динамической библиотеки в нашем проекте неплохо было бы иметь .lib-файл, которого у нас сейчас нет. Для того чтобы он у нас был, создаем два batch-скрипта:
Файл setupvars.bat
"C:/Program Files/Microsoft Visual Studio 8/VC/bin/vcvars32.bat"
Файл export.bat
lib.exe /def:sqlite3.def /machine:x86 /out:sqlite3.lib
Запускаем командную строку, переходим в папку databaselayer/sqlite/lib и по очереди запускаем два batch-файла:
setupvars
export
После выполнения этих нехитрых действий у нас должны появиться два файла: sqlite3.exp и sqlite3.lib.
Отлично, теперь создаем в нашем проекте SQLiteTest четыре новых файла: SQLiteTestApp.h, SQLiteTestApp.cpp, SQLiteTestMainFrame.h и SQLiteTestMainFrame.cpp.
Теперь открываем свойства проекта, переходим в раздел C/C++ и в настройках «Additional Include Directories» добавляем две новые записи, содержащие пути к заголовочным файлам DatabaseLayer и SQLite:
$(ProjectDir)../databaselayer/sqlite/include
$(ProjectDir)../databaselayer/include
Переходим в раздел Linker и в настройках «Additional Library Directories» добавляем записи, содержащие пути к статическим библиотекам DatabaseLayer и SQLite:
$(ProjectDir)../databaselayer/sqlite/lib
$(ProjectDir)../databaselayer/lib
В разделе «Linker -> Input» в настройках «Additional Dependencies» добавляем записи:
advapi32.lib
comctl32.lib
uuid.lib
rpcrt4.lib
wxbase28ud.lib
wxmsw28ud_core.lib
wxmsw28ud_adv.lib
wxpngd.lib
wxcode_msw28ud_databaselayer_sqlite.lib
sqlite3.lib
Не забывайте, что суффикс d в названиях библиотек wxWidgets указывает на то, что используется отладочная версия библиотеки. Release-версии библиотек не имеют в названии этого суффикса.
Далее в разделе «Linker -> General» для всех конфигураций указываем параметр Output File равным «../bin/$(ProjectName).exe» и в разделе Debugging – параметр Working Directory равным «../bin». Это позволит генерировать исполняемый файл в отдельную папку.
Наконец, предварительная настройка завершена, и можно приступать к написанию кода.
Пишем код
SQLiteTestMainFrame.h – заголовочный файл с описанием класса главного окна приложения:
#ifndef _SQLITE_TEST_MAINFRAME_H
#define _SQLITE_TEST_MAINFRAME_H
#include <wx/wx.h>
class SQLiteTestMainFrame : public wxFrame
{
void CreateControls();
public:
SQLiteTestMainFrame();
bool Create(wxWindow * parent,
wxWindowID id, const wxString & title);
DECLARE_EVENT_TABLE()
void OnExit(wxCommandEvent & event);
};
#endif
SQLiteTestMainFrame.cpp – реализация класса главного окна приложения:
#include "SQLiteTestMainFrame.h"
#include "SQLiteTestApp.h"
BEGIN_EVENT_TABLE(SQLiteTestMainFrame, wxFrame)
EVT_MENU(wxID_EXIT, SQLiteTestMainFrame::OnExit)
END_EVENT_TABLE()
SQLiteTestMainFrame::SQLiteTestMainFrame()
{
Create(NULL, wxID_ANY, _("SQLite Addressbook"));
}
bool SQLiteTestMainFrame::Create(wxWindow * parent,
wxWindowID id, const wxString & title)
{
bool res = wxFrame::Create(parent, id, title,
wxDefaultPosition, wxSize(700, 500));
if(res)
{
CreateControls();
}
return res;
}
void SQLiteTestMainFrame::CreateControls()
{
wxMenuBar * menuBar = new wxMenuBar;
SetMenuBar(menuBar);
wxMenu * fileMenu = new wxMenu;
fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));
menuBar->Append(fileMenu, _("File"));
CreateStatusBar(2);
Centre();
}
void SQLiteTestMainFrame::OnExit(wxCommandEvent & event)
{
wxUnusedVar(event);
Close();
}
SQLiteTestApp.h – заголовочный файл с описанием класса приложения:
#ifndef _SQLITE_TEST_APP_H
#define _SQLITE_TEST_APP_H
#include <wx/wx.h>
#include <Databaselayer.h>
#include <SqliteDatabaseLayer.h>
class SQLiteTestApp : public wxApp
{
DatabaseLayer * m_Database;
public:
virtual bool OnInit();
virtual int OnExit();
bool ConnectToDatabase();
DatabaseLayer * GetDatabase();
};
DECLARE_APP(SQLiteTestApp)
#endif
SQLiteTestApp.cpp – реализация класса приложения:
#include <wx/image.h>
#include <DatabaseLayerException.h>
#include "SQLiteTestApp.h"
#include "SQLiteTestMainFrame.h"
IMPLEMENT_APP(SQLiteTestApp)
bool SQLiteTestApp::OnInit()
{
if(!ConnectToDatabase())
{
wxFAIL_MSG(_("Error connecting to database!"));
return false;
}
wxImage::AddHandler(new wxPNGHandler);
wxImage::AddHandler(new wxJPEGHandler);
SQLiteTestMainFrame * frame = new SQLiteTestMainFrame;
SetTopWindow(frame);
frame->Show();
return true;
}
int SQLiteTestApp::OnExit()
{
if(m_Database)
{
if(m_Database->IsOpen())
{
m_Database->Close();
}
wxDELETE(m_Database);
}
return wxApp::OnExit();
}
bool SQLiteTestApp::ConnectToDatabase()
{
m_Database = new SqliteDatabaseLayer();
wxString db_filename(wxT("addressbook.db"));
PreparedStatement * pStatement(NULL);
bool bCreate = !wxFileExists(db_filename);
if(bCreate)
{
wxMessageBox(_("Database does not exist... recreating."));
}
try
{
m_Database->Open(db_filename);
// Try to recreate tables
try
{
m_Database->RunQuery(
wxT("CREATE TABLE groups(id integer primary key, \
name varchar(128) not null,\
description varchar(512));"));
}
catch(DatabaseLayerException & e) {wxUnusedVar(e);}
try
{
m_Database->RunQuery(wxT("CREATE TABLE persons(\
id integer primary key,\
groupid integer not null,\
first_name varchar(64) not null,\
last_name varchar(64) not null,\
gender boolean not null,\
address varchar(128) not null,\
city varchar(64) not null,\
country varchar(32) not null,\
phone varchar(32),\
email varchar(128),\
website varchar(260));"));
}
catch(DatabaseLayerException & e) {wxUnusedVar(e);}
if(bCreate)
{
m_Database->RunQuery(
wxT("INSERT INTO groups(name, description) \
VALUES ('Friends', 'My friends')"));
pStatement = m_Database->PrepareStatement(
wxT("INSERT INTO persons(groupid, \
first_name,last_name,gender,address,city,\
country,phone) VALUES \
(?,?,?,?,?,?,?,?)"));
if (pStatement)
{
pStatement->SetParamInt(1, 1);
pStatement->SetParamString(2, _("John"));
pStatement->SetParamString(3, _("Doe"));
pStatement->SetParamBool(4, 1);
pStatement->SetParamString(5,
_("SomeStreet st., 123/45"));
pStatement->SetParamString(6, _("Some City"));
pStatement->SetParamString(7, _("Some Country"));
pStatement->SetParamString(8, _("+000-00-000-00-00"));
pStatement->RunQuery();
m_Database->CloseStatement(pStatement);
pStatement = NULL;
}
}
}
catch(DatabaseLayerException & e)
{
if(pStatement)
{
m_Database->CloseStatement(pStatement);
pStatement = NULL;
}
wxFAIL_MSG(e.GetErrorMessage());
return false;
}
return true;
}
DatabaseLayer * SQLiteTestApp::GetDatabase()
{
return m_Database;
}
Итак, что же мы сделали: мы создали класс приложения SQLiteTestApp, содержащий указатель на объект класса DatabaseLayer, который и будет обеспечивать связь с нашей базой данных. При запуске приложения проверяется наличие файла addressbook.db, и в случае если файл не найден, он создается. Вместе с базой данных создаются две таблицы: groups и persons и заполняются тестовыми данными.
Главная форма приложения содержит строку меню и строку статуса и пока ничего не делает.
После успешной сборки приложения и его запуска в папке bin должен появиться файл базы данных addressbook.db.
Отлично, теперь можно приступать к работе с базой данных.
Генерирование классов автоматизации доступа к базам данных с помощью wxActiveRecordGenerator
Устанавливаем утилиту wxARG, копируем из папки, куда была установлена wxARG, два файла wxActiveRecord.h и wxActiveRecord.cpp в папку SQLiteTest/wxActiveRecord. В настройках проекта SQLiteTest в разделе «C/C++ -> General» в настройках «Additional Include Directories» добавляем новую запись:
./wxActiveRecord
Далее запускаем утилиту wxARG и выбираем пункт меню «File -> Connect to database».
В появившемся диалоговом окне «Database Connection» указываем тип базы данных SQLite3 Database и путь к файлу addressbook.db, созданному нашим тестовым приложением. Жмем «Connect».
В диалоговом окне «Tables» выбираем таблицы groups и persons. Жмем «OK».
После этого в левой части главного окна wxARG появится список с выбранными таблицами.
Каждая таблица базы данных имеет ключевое поле id, поэтому на вкладке «Properties» в поле ID Field мы указываем поле id.
В нашей базе данных между таблицами groups и persons существует связь один ко многим, т.е. каждая группа может содержать множество контактов адресной книги.
Переходим на вкладку «Relations». Для таблицы groups:
- жмем кнопку «Has Many»;
- в диалоговом окне «Relation Properties» указываем название таблицы со стороны «многие», в нашем случае это таблица persons;
- указываем поле внешнего ключа в таблице persons, в нашем случае это groupid;
- значения остальных параметров можно оставить без изменений.
Для таблицы persons:
- жмем кнопку «Belongs To»;
- в диалоговом окне «Relation Properties» указываем название таблицы со стороны «один», в нашем случае это таблица groups;
- указываем поле внешнего ключа в таблице persons, в нашем случае это groupid;
- значения остальных параметров можно оставить без изменений.
В поле «Output Dir» указываем папку SQLiteTest и жмем «Generate».
После всех проделанных операций в папке с проектом SQLiteTest должны появиться файлы: Group.h, Group.cpp, Person.h и Person.cpp. Эти файлы нам необходимо добавить в проект.
Теперь можно приступать к внесению изменений в исходный код. Для начала в файле wxActiveRecord.h необходимо раскомментировать строку с макросом AR_USE_SQLITE, который указывает, что мы используем SQLite. В файле wxActiveRecord.cpp нужно исправить путь к заголовочному файлу wxActiveRecord.h.
В файле wxActiveRecord.h вносим изменения, необходимые для организации работы с базами данных SQLite:
...
// COMMENT OUT THE ONES YOU DON'T USE
//#define AR_USE_POSTGRESQL
#define AR_USE_SQLITE
//#define AR_USE_MYSQL
//#define AR_USE_FIREBIRD
...
В файле wxActiveRecord.cpp необходимо исправить путь к заголовочному файлу:
...
#include "wxActiveRecord.h"
...
Если посмотреть на исходный код файлов, сгенерированных утилитой wxARG, то можно заметить, что каждая пара .h/.cpp-файлов содержит описание и реализацию трех классов (где XXX – название таблицы):
- XXX – класс таблицы;
- XXXRow – класс типизированной записи таблицы;
- XXXRowSet – типизированный список записей.
И после всего сказанного, вооружившись полученными знаниями, продолжаем наш «happy coding».
В файле wxSQLiteTestApp.h добавляем методы доступа к таблицам базы данных в класс приложения:
...
#include <SqliteDatabaseLayer.h>
#include "Group.h"
#include "Person.h"
class SQLiteTestApp : public wxApp
{
Group * m_GroupTable;
Person * m_PersonTable;
...
public:
...
Group * GetGroupTable();
Person * GetPersonTable();
};
...
wxSQLiteTestApp.cpp – загрузка данных при запуске приложения и освобождение памяти при завершении работы:
...
int SQLiteTestApp::OnExit()
{
wxDELETE(m_PersonTable);
wxDELETE(m_GroupTable);
...
}
bool SQLiteTestApp::ConnectToDatabase()
{
...
try
{
m_GroupTable =
new Group(wxGetApp().GetDatabase(), wxT("groups"));
m_PersonTable =
new Person(wxGetApp().GetDatabase(), wxT("persons"));
}
catch(DatabaseLayerException & e)
{
wxActiveRecord::ProcessException(e);
}
return true;
}
...
Итак, мы добавили новые члены класса SQLiteTestApp, обеспечивающие доступ к таблицам базы данных, и теперь можно приступать к созданию GUI.
Графический интерфейс пользователя
Дистрибутив wxWidgets содержит несколько десятков свободно распространяемых иконок, которые мы можем использовать в нашем приложении. Создадим папку SQLiteTest/art и скопируем в нее файлы:
$(WXWIN)/art/addbookm.xpm
$(WXWIN)/art/delbookm.xpm
$(WXWIN)/art/new.xpm
$(WXWIN)/art/delete.xpm
В свойствах проекта в разделе «C/C++ -> General» в настройках «Additional Include Directories» добавим запись:
./art
wxSQLiteTestMainFrame.h – добавляем элементы управления на форму:
...
class SQLiteTestMainFrame : public wxFrame
{
wxListBox * m_GroupsListBox;
wxListView * m_PersonsListView;
wxHtmlWindow * m_PersonInfoPanel;
wxToolBar * CreateToolBar();
...
};
#endif
wxSQLiteTestMainFrame.cpp – создание элементов управления:
...
#include <wx/splitter.h>
#include "new.xpm"
#include "delete.xpm"
#include "addbookm.xpm"
#include "delbookm.xpm"
...
void SQLiteTestMainFrame::CreateControls()
{
...
wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
SetSizer(sizer);
wxSplitterWindow * splitter =
new wxSplitterWindow(this, wxID_ANY,
wxDefaultPosition, wxSize(500, 400), wxSP_3DSASH);
splitter->SetMinimumPaneSize(100);
sizer->Add(splitter, 1, wxEXPAND);
m_GroupsListBox =
new wxListBox(splitter, ID_GROUPS_LISTBOX,
wxDefaultPosition, wxDefaultSize);
wxSplitterWindow * personsplitter =
new wxSplitterWindow(splitter, wxID_ANY,
wxDefaultPosition, wxSize(500, 400), wxSP_3DSASH);
personsplitter->SetMinimumPaneSize(100);
m_PersonsListView =
new wxListView(personsplitter, ID_PERSONS_LISTCTRL,
wxDefaultPosition, wxDefaultSize, wxLC_REPORT);
m_PersonsListView->InsertColumn(0, _("First Name"),
wxLIST_FORMAT_LEFT, 120);
m_PersonsListView->InsertColumn(1, _("Last Name"),
wxLIST_FORMAT_LEFT, 120);
...
personsplitter->SetSashGravity(1.0)
splitter->SplitVertically(m_GroupsListBox,
personsplitter, 160);
personsplitter->SplitHorizontally(m_PersonsListView,
m_PersonInfoPanel,
personsplitter->GetSize().GetHeight()-180);
SetToolBar(CreateToolBar());
...
}
wxToolBar * SQLiteTestMainFrame::CreateToolBar()
{
wxToolBar * toolBar = new wxToolBar(this, wxID_ANY,
wxDefaultPosition, wxDefaultSize, wxTB_FLAT|wxTB_TEXT);
toolBar->AddTool(ID_ADD_GROUP, _("Add Group"),
wxBitmap(addbookm_xpm));
toolBar->AddTool(ID_DELETE_GROUP, _("Remove Group"),
wxBitmap(delbookm_xpm));
toolBar->AddSeparator();
...
toolBar->Realize();
return toolBar;
}
...
Для работы с классом wxHtmlWindow в свойствах проекта в разделе «Linker -> Input» в настройках «Additional Dependencies» нам необходимо добавить библиотеку wxmsw28ud_html.lib для конфигурации Debug и wxmsw28u_html.lib для конфигурации Release.
Отлично, интерфейс приложения готов, теперь можно приступить к реализации функционала.
Реализация функционала
RecordIDClientData.h – класс для хранения данных, ассоциированных с элементом списка:
...
#include <wx/clntdata.h>
class RecordIDClientData : public wxClientData
{
int m_ID;
public:
RecordIDClientData(int id) : m_ID(id) {}
int GetID() {return m_ID;}
...
};
...
wxSQLiteTestMainFrame.cpp – работа с базой данных, загрузка и отображение контактов из адресной книги:
void SQLiteTestMainFrame::FillGroupsList()
{
m_GroupsListBox->Freeze();
m_GroupsListBox->Clear();
GroupRowSet * allGroups =
wxGetApp().GetGroupTable()->All();
for(unsigned long i = 0; i < allGroups->Count(); ++i)
{
m_GroupsListBox->Append(allGroups->Item(i)->name,
new RecordIDClientData(allGroups->Item(i)->id));
}
if(m_GroupsListBox->GetCount())
{
m_GroupsListBox->SetSelection(0);
RecordIDClientData * data = (RecordIDClientData *)
m_GroupsListBox->GetClientObject(
m_GroupsListBox->GetSelection());
if(data)
{
FillPersonsList(data->GetID());
}
}
m_GroupsListBox->Thaw();
}
void SQLiteTestMainFrame::FillPersonsList(int groupid)
{
m_PersonsListView->Freeze();
m_PersonsListView->DeleteAllItems();
GroupRow * thisGroup =
wxGetApp().GetGroupTable()->Id(groupid);
if(thisGroup)
{
PersonRowSet * allPersons = thisGroup->GetPersons();
long item(0);
for(unsigned long i = 0; i < allPersons->Count(); ++i)
{
item = m_PersonsListView->InsertItem(item,
allPersons->Item(i)->first_name);
m_PersonsListView->SetItem(item, 1,
allPersons->Item(i)->last_name);
m_PersonsListView->SetItem(item, 2,
allPersons->Item(i)->email);
m_PersonsListView->SetItem(item, 3,
allPersons->Item(i)->phone);
m_PersonsListView->SetItemData(item,
(long)allPersons->Item(i)->id);
}
if(m_PersonsListView->GetItemCount())
{
m_PersonsListView->Select(0);
}
}
m_PersonsListView->Thaw();
}
void SQLiteTestMainFrame::
OnGroupListBoxSelected(wxCommandEvent & event)
{
RecordIDClientData * data =
(RecordIDClientData *)event.GetClientObject();
if(data)
{
FillPersonsList(data->GetID());
}
}
void SQLiteTestMainFrame::
OnPersonListViewSelected(wxListEvent & event)
{
long personid = event.GetData();
PersonRow * person =
wxGetApp().GetPersonTable()->Id((int)personid);
if(person)
{
m_PersonInfoPanel->SetPage(wxString::Format(
wxT("<html><body><h3>%s %s</h3></body></html>"),
person->first_name, person->last_name));
m_PersonInfoPanel->AppendToPage(wxString::Format(
wxT("<b>Gender:</b> %s"),
(person->gender?wxT("Male"):wxT("Female"))));
m_PersonInfoPanel->AppendToPage(wxT("<hr>"));
...
}
else
{
m_PersonInfoPanel->SetPage(
_("<html><body><h3>Can't find info about \
selected person</h3></body></html>"));
}
}
void SQLiteTestMainFrame::
OnPersonInfoPanelLinkClicked(wxHtmlLinkEvent & event)
{
#if defined(__WXMSW__)
ShellExecute(NULL, NULL,
event.GetLinkInfo().GetHref().GetData(),
NULL, NULL, SW_SHOW);
#else
wxExecute(event.GetLinkInfo().GetHref());
#endif
}
Список в левой части окна содержит названия групп. Каждый элемент списка содержит код группы. Привязка данных к элементам списка реализована с помощью объектов класса RecordIDClientData, каждый объект которого содержит код группы. Класс RecordIDClientData является производным от wxClientData, и его использование обеспечивает автоматическую очистку памяти при удалении элемента списка. Доступ к данным, ассоциированным с элементом списка групп, производится посредством метода GetClientObject класса wxListBox. При выборе записи в списке групп список контактов в правой части окна заполняется данными контактов выбранной группы.
При выборе элемента списка контактов, информационное поле в нижней части окна заполняется данными контакта, связанного с выбранным элементом списка. Каждый элемент списка контактов содержит в поле данных код контакта. Доступ к полю данных производится посредством метода GetItemData класса wxListView.
При нажатии на ссылку с адресом электронной почты в информационном поле создается новое письмо в почтовом клиенте, назначенном по умолчанию.
С отображением данных мы закончили. Теперь нужно реализовать добавление новой записи в таблицу и удаление записей из таблицы.
Для начала создадим диалоговые окна ввода данных.
EditGroupDialog.h – описание класса диалогового окна редактирования группы:
#ifndef _EDIT_GROUP_DIALOG_H
#define _EDIT_GROUP_DIALOG_H
#include <wx/wx.h>
class EditGroupDialog : public wxDialog
{
wxString m_GroupName;
wxString m_GroupDescription;
void CreateControls();
public:
EditGroupDialog(wxWindow * parent);
bool Create(wxWindow * parent,
wxWindowID id,
const wxString title);
const wxString & GetGroupName();
const wxString & GetGroupDescription();
};
#endif
EditGroupDialog.cpp – класс диалогового окна редактирования группы, создание GUI:
...
void EditGroupDialog::CreateControls()
{
wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
SetSizer(sizer);
wxStaticText * nameLabel =
new wxStaticText(this, wxID_ANY, _("Name:"));
wxStaticText * descriptionLabel =
new wxStaticText(this, wxID_ANY,
_("Description:"));
wxTextCtrl * nameEdit =
new wxTextCtrl(this, ID_EGD_NAME_TEXTCTRL,
wxEmptyString);
wxTextCtrl * descriptionEdit =
new wxTextCtrl(this, ID_EGD_DESCRIPTION_TEXTCTRL,
wxEmptyString, wxDefaultPosition,
wxSize(-1, 150), wxTE_MULTILINE);
nameEdit->SetValidator(
wxGenericValidator(&m_GroupName));
descriptionEdit->SetValidator(
wxGenericValidator(&m_GroupDescription));
wxFlexGridSizer * fg_sizer =
new wxFlexGridSizer(2, 2, 0, 0);
fg_sizer->Add(nameLabel, 0,
wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
fg_sizer->Add(nameEdit, 1,
wxEXPAND|wxALIGN_CENTER_VERTICAL|wxALL, 5);
fg_sizer->Add(descriptionLabel, 0,
wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
fg_sizer->Add(descriptionEdit, 1,
wxEXPAND|wxALIGN_CENTER_VERTICAL|wxALL, 5);
fg_sizer->AddGrowableCol(1);
sizer->Add(fg_sizer, 1,
wxEXPAND|wxTOP|wxLEFT|wxRIGHT, 5);
sizer->Add(CreateButtonSizer(wxID_OK|wxID_CANCEL),
0, wxALIGN_RIGHT|wxALL, 5);
}
...
EditPersonDialog.h – описание класса диалогового окна редактирования контакта:
#ifndef _EDIT_PERSON_DIALOG_H
#define _EDIT_PERSON_DIALOG_H
#include <wx/wx.h>
class EditPersonDialog : public wxDialog
{
wxString m_FirstName;
wxString m_LastName;
wxString m_Address;
wxString m_City;
wxString m_Country;
wxString m_Email;
wxString m_Phone;
void CreateControls();
public:
EditPersonDialog(wxWindow * parent);
bool Create(wxWindow * parent,
wxWindowID id, const wxString title);
const wxString & GetFirstName();
const wxString & GetLastName();
const wxString & GetAddress();
const wxString & GetCity();
const wxString & GetCountry();
const wxString & GetEmail();
const wxString & GetPhone();
};
#endif
EditPersonDialog.cpp – класс диалогового окна редактирования контакта, создание GUI:
...
void EditPersonDialog::CreateControls()
{
wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
SetSizer(sizer);
wxStaticText * firstnameLabel =
new wxStaticText(this, wxID_ANY, _("First Name:"));
wxStaticText * lastnameLabel =
new wxStaticText(this, wxID_ANY, _("Last Name:"));
...
wxTextCtrl * firstnameEdit =
new wxTextCtrl(this, ID_EPD_FIRSTNAME_TEXTCTRL,
wxEmptyString);
wxTextCtrl * lastnameEdit =
new wxTextCtrl(this, ID_EPD_LASTNAME_TEXTCTRL,
wxEmptyString);
...
firstnameEdit->SetMinSize(wxSize(150,-1));
firstnameEdit->SetValidator(
wxGenericValidator(&m_FirstName));
lastnameEdit->SetValidator(
wxGenericValidator(&m_LastName));
...
wxFlexGridSizer * fg_sizer =
new wxFlexGridSizer(2, 2, 0, 0);
fg_sizer->Add(firstnameLabel, 0,
wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
fg_sizer->Add(firstnameEdit, 1,
wxEXPAND|wxALIGN_CENTER_VERTICAL|wxALL, 5);
...
fg_sizer->AddGrowableCol(1);
sizer->Add(fg_sizer, 1, wxEXPAND|wxTOP|wxLEFT|wxRIGHT, 5);
sizer->Add(CreateButtonSizer(wxID_OK|wxID_CANCEL), 0,
wxALIGN_RIGHT|wxALL, 5);
sizer->Fit(this);
}
...
Теперь можно приступать к написанию обработчиков событий от кнопок на панели инструментов.
SQLiteTestMainFrame.cpp – добавляем возможность создания и редактирования контактов:
...
void SQLiteTestMainFrame::OnAddGroup(wxCommandEvent & event)
{
EditGroupDialog * dlg = new EditGroupDialog(this);
if(dlg->ShowModal() == wxID_OK)
{
GroupRow * newGroup = wxGetApp().GetGroupTable()->New();
newGroup->name = dlg->GetGroupName();
newGroup->description = dlg->GetGroupDescription();
newGroup->Save();
FillGroupsList();
}
dlg->Destroy();
}
void SQLiteTestMainFrame::
OnRemoveGroup(wxCommandEvent & event)
{
int selection = m_GroupsListBox->GetSelection();
RecordIDClientData * data = (RecordIDClientData *)
m_GroupsListBox->GetClientObject(selection);
if(data)
{
GroupRow * thisGroup =
wxGetApp().GetGroupTable()->Id(data->GetID());
if(thisGroup && (wxMessageBox(
_("Do you really want to delete this group?"),
_("Delete group"), wxYES_NO) == wxYES))
{
PersonRowSet * thisPersons = thisGroup->GetPersons();
for(unsigned long i = 0; i < thisPersons->Count(); ++i)
{
thisPersons->Item(i)->Delete();
}
thisGroup->Delete();
m_GroupsListBox->Delete(selection);
if(m_GroupsListBox->GetCount())
{
m_GroupsListBox->SetSelection(selection <
m_GroupsListBox->GetCount() ? selection : 0);
data = (RecordIDClientData *)
m_GroupsListBox->GetClientObject(
m_GroupsListBox->GetSelection());
if(data)
{
FillPersonsList(data->GetID());
}
}
}
}
}
void SQLiteTestMainFrame::
OnAddPerson(wxCommandEvent & event)
{
RecordIDClientData * data = (RecordIDClientData *)
m_GroupsListBox->GetClientObject(
m_GroupsListBox->GetSelection());
if(data)
{
GroupRow * thisGroup =
wxGetApp().GetGroupTable()->Id(data->GetID());
if(thisGroup)
{
EditPersonDialog * dlg = new EditPersonDialog(this);
if(dlg->ShowModal() == wxID_OK)
{
PersonRow * newPerson =
wxGetApp().GetPersonTable()->New();
newPerson->groupid= thisGroup->id;
newPerson->first_name = dlg->GetFirstName();
newPerson->last_name = dlg->GetLastName();
...
newPerson->Save();
FillPersonsList(thisGroup->id);
}
dlg->Destroy();
}
}
}
void SQLiteTestMainFrame::
OnRemovePerson(wxCommandEvent & event)
{
long selection = m_PersonsListView->GetFirstSelected();
PersonRow * thisPerson = wxGetApp().GetPersonTable()->Id(
(int)m_PersonsListView->GetItemData(selection));
if(thisPerson && (wxMessageBox(
_("Do you really want to delete this record?"),
_("Delete person"), wxYES_NO) == wxYES))
{
int groupid = thisPerson->groupid;
thisPerson->Delete();
m_PersonsListView->DeleteItem(selection);
if(m_PersonsListView->GetItemCount())
{
m_PersonsListView->Select(wxMin(selection,
m_PersonsListView->GetItemCount()-1));
}
}
}
void SQLiteTestMainFrame::
OnRemoveGroupUpdateUI(wxUpdateUIEvent & event)
{
event.Enable(m_GroupsListBox->GetSelection() >= 0);
}
void SQLiteTestMainFrame::
OnAddPersonUpdateUI(wxUpdateUIEvent & event)
{
event.Enable(m_GroupsListBox->GetSelection() >= 0);
}
void SQLiteTestMainFrame::
OnRemovePersonUpdateUI(wxUpdateUIEvent & event)
{
event.Enable(m_PersonsListView->GetFirstSelected() >= 0);
}
...
Как видно, кнопка удаления группы и кнопка добавления контакта активны только в случае, если есть активная запись в списке групп. Кнопка удаления контакта становится активной только в случае, если есть активная запись в списке контактов. При удалении группы удаляются и все связанные с ней контакты.
Ну вот. Мы закончили. Теперь собираем Release-версию программы и тестируем. Можно переходить к созданию проекта под Linux.
Linux
Для сборки нашего проекта в Linux нам необходимо наличие в системе установленной библиотеки wxWidgets. Для большинства распространенных дистрибутивов wxWidgets доступна в виде готовых пакетов, но лучше всего будет не использовать готовые пакеты, а загрузить исходный код wxWidgets с официального сайта и собрать библиотеку вручную. Кроме исходного кода, дистрибутив wxWidgets содержит довольно большое количество примеров, которые могут оказаться очень полезными для начинающих. В качестве среды разработки в Linux мы будем использовать Eclipse с установленным C/C++ Development Tooling (CDT).
Собираем SQLite
Для корректной работы нашего приложения необходим файл статической библиотеки libsqlite3.a, а также заголовочные файлы SQLite. В случае если в системе эти файлы отсутствуют, необходимо загрузить исходный код SQLite (sqlite-x.y.z.tar.gz), собрать его и установить:
./configure –enable-static
make
make instal
Создание и настройка проекта в Eclipse
После сборки SQLite необходимо убедиться, что путь к файлам libsqlite3.so.* находится в списке путей поиска библиотек. Я же просто создал ссылки на эти файлы в каталоге /lib. Теперь, если это еще не сделано, нужно удалить все .h и .cpp-файлы, не относящиеся к проекту databaselayer_sqlite из папок databaselayer/src и databaselayer/include, а также, папку databaselayer/tests.
После того как предварительная подготовка выполнена, запускаем Eclipse и в папке databaselayer создаем новый проект. Назовем его SqliteDatabaseLayer.
Далее идем в настройки проекта и в разделе «C/C++ Build -> Miscellaneous» в поле Other flags дописываем:
`wx-config --cxxflags`
Заметьте, кавычки обратные, кнопка с тильдой («~») на клавиатуре.
После того как настройка библиотеки DatabaseLayer завершена, нужно создать проект для тестового приложения. Создаем новый проект Executable (Gnu) в папке SQLiteTest. В мастере создания проекта на вкладке настройки зависимостей необходимо указать проект библиотеки SqliteDatabaseLayer. В свойствах проекта в разделе «C/C++ Build -> Directories» добавляем новые записи:
{ProjDirPath}/art
{ProjDirPath}/wxActiveRecord
{ProjDirPath}/../databaselayer/include
Заметьте, скобки фигурные, в отличие от настроек проекта в Visual Studio.
В разделе «C/C++ Build -> Miscellaneous» в поле Other flags дописываем:
`wx-config --cxxflags`
В разделе «C/C++ Build -> Libraries» добавляем новые записи:
SqliteDatabaseLayer
sqlite3
Библиотеки должны быть добавлены именно в указанной последовательности, т.к. библиотека libSqliteDatabaseLayer.a зависит от libsqlite3.a.
И в списке Library search path добавляем запись:
{ProjDirPath}/../databaselayer/Debug
В разделе «C/C++ Build -> GCC C++ Linker -> Miscellaneous» в поле Linker flags дописываем:
`wx-config --libs`
Собираем проект с помощью «Project -> Build Project».
Запуск приложения и отладка
После успешной сборки неплохо было бы запустить наше приложение и проверить его на работоспособность. Выбираем пункт меню «Run -> Run...» В диалоговом окне «Run» жмем правой кнопкой на элементе «C/C++ Local Application», выбираем пункт меню «New» (см. рис. 6). После этого у нас должна появиться новая конфигурация запуска с названием SQLiteTest Жмем кнопку «Search Project». В окне «Program Selection» выбираем наше приложение. Жмем «OK». На вкладке «Debugger» в списке Debugger выбираем «GDB Debugger». Жмем «Run». Вот и результат. Получилось довольно опрятно (см. рис. 7).
Рисунок 1. Окно конфигурирования сборки в Visual Studio 2005
Рисунок 2. Список библиотек, необходимых для сборки проекта
Рисунок 3. Главное окно приложения
Рисунок 4. Настройка связей между таблицами базы данных в wxARG
Рисунок 5. Главное окно приложения с загруженным списком контактов
Рисунок 6. Создание конфигурации запуска приложения в Eclipse
Рисунок 7. Внешний вид главного окна приложения в Linux
Заключение
Как видите, ничего сложного в разработке кроссплатформенных приложений нет, но, как и любой труд, кроссплатформенная разработка программного обеспечения требует опыта и сноровки. Надеюсь, материал послужит толчком для того, чтобы начать немного по-другому смотреть на свои проблемы и на способы их решения. Исходный код к статье можно загрузить отсюда:http://wxwidgets.info/files/SQLiteTest.7z.
- http://wxwidgets.info – статьи, руководства, видео-уроки по программированию на C++/wxWidgets на русском. Единственный русскоязычный ресурс, посвященный кроссплатформенной разработке ПО с библиотекой wxWidgets.
- http://wxcode.sourceforge.net/components/activerecord/tutorial/wxARG-tutorial.php – руководство по использованию библиотеки DatabaseLayer.
- http://wxcommunity.com – дополнительные компоненты wxWidgets.
- http://wxforum.shadonet.com – форум библиотеки wxWidgets.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|