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

  Опросы

Какие курсы вы бы выбрали для себя?  

Очные
Онлайновые
Платные
Бесплатные
Я и так все знаю

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

1001 и 1 книга  
20.12.2019г.
Просмотров: 5400
Комментарии: 0
Dr.Web: всё под контролем

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

04.12.2019г.
Просмотров: 6594
Комментарии: 0
Особенности сертификаций по этичному хакингу

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

28.05.2019г.
Просмотров: 7877
Комментарии: 2
Анализ вредоносных программ

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

28.05.2019г.
Просмотров: 8169
Комментарии: 1
Микросервисы и контейнеры Docker

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

28.05.2019г.
Просмотров: 7165
Комментарии: 0
Django 2 в примерах

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

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

 Организуем доступ к базам данных при разработке кроссплатформенных приложений на 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-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru