Организуем доступ к базам данных при разработке кроссплатформенных приложений на C++/wxWidgets::Журнал СА 6.2007
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6193
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6902
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4185
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 2989
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3794
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3805
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6297
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3152
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3446
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7261
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10628
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12349
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 13980
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9109
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7064
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5374
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4603
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3413
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3144
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3391
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 3012
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Организуем доступ к базам данных при разработке кроссплатформенных приложений на C++/wxWidgets

Архив номеров / 2007 / Выпуск №6 (55) / Организуем доступ к базам данных при разработке кроссплатформенных приложений на C++/wxWidgets

Рубрика: Программирование /  Разработка

Владимир Тряпичко

Организуем доступ к базам данных при разработке кроссплатформенных приложений на 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

Рисунок 1. Окно конфигурирования сборки в Visual Studio 2005

Рисунок 2. Список библиотек, необходимых для сборки проекта

Рисунок 2. Список библиотек, необходимых для сборки проекта

Рисунок 3. Главное окно приложения

Рисунок 3. Главное окно приложения

Рисунок 4. Настройка связей между таблицами базы данных в wxARG

Рисунок 4. Настройка связей между таблицами базы данных в wxARG

Рисунок 5. Главное окно приложения с загруженным списком контактов

Рисунок 5. Главное окно приложения с загруженным списком контактов

Рисунок 6. Создание конфигурации запуска приложения в Eclipse

Рисунок 6. Создание конфигурации запуска приложения в Eclipse

Рисунок 7. Внешний вид главного окна приложения в Linux

Рисунок 7. Внешний вид главного окна приложения в Linux

Заключение

Как видите, ничего сложного в разработке кроссплатформенных приложений нет, но, как и любой труд, кроссплатформенная разработка программного обеспечения требует опыта и сноровки. Надеюсь, материал послужит толчком для того, чтобы начать немного по-другому смотреть на свои проблемы и на способы их решения. Исходный код к статье можно загрузить отсюда:http://wxwidgets.info/files/SQLiteTest.7z.

  1. http://wxwidgets.info – статьи, руководства, видео-уроки по программированию на C++/wxWidgets на русском. Единственный русскоязычный ресурс, посвященный кроссплатформенной разработке ПО с библиотекой wxWidgets.
  2. http://wxcode.sourceforge.net/components/activerecord/tutorial/wxARG-tutorial.php – руководство по использованию библиотеки DatabaseLayer.
  3. http://wxcommunity.com – дополнительные компоненты wxWidgets.
  4. http://wxforum.shadonet.com – форум библиотеки wxWidgets.

Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru