Работа с базами данных в Wxwidgets

Источник: wxWidgets programming" by t_rex

Вот... выкладываю статью о работе с базами данных в wxWidgets. Честно говоря, первый раз пробовал пользоваться встроенными средствами wxWidgets для работы с базами данных... Как оказалось, я не много потерял от того, что не пользовался ими раньше. Что хотелось бы отметить... Первое, это осутствие каких-либо визуальных компонентов для работы с базами данных... ну не берем в счет wxGrid, в которой можно отобразить результат выборки по SELECT. Все равно синхронизацию с базой wxGrid не обеспечивает... а жаль... а так хотелось %)

Примерчик получился так-себе, но основные моменты все-таки мне удалось выявить (я, по крайней мере, на это очень надеюсь).

Как пользоваться примером...
Создаем новый источник данных ODBC, в качестве провайдера БД будем использовать MS Jet (СУБД MS Access).
В Access'овском файле создаем табличку

Positions
+ID -> АВТОИНКРЕМЕНТ
*PositionName -> ТЕКСТОВОЕ

Создаем проект, настройки смотрим в предыдущих моих постах... список прилинковываемых библиотек:
Release - wxbase26.lib wxmsw26_core.lib comctl32.lib uuid.lib rpcrt4.lib ws2_32.lib
Debug - wxbase26d.lib wxmsw26d_core.lib comctl32.lib uuid.lib rpcrt4.lib ws2_32.lib

Добавляем файлики (см. ниже)

Что же делает наш пример... в окошке есть список доступных источников данных ODBC. Жмакаем на созданном ранее источнике данных - инфа из таблицы отображается внизу в wxGrid. Также есть кнопочка "Добавить запись". Жмакаем ее, выскакивают диалоговые окошки на ввод значений (после ввода новых значений табличка не обновляется... ну влом мне было делать...) перезапускаем приложение, опять коннектимся, смотрим результат.
Что хотелось бы добавить... Почему-то у меня получалось создавать записи с одинаковыми значениями автоинкрементного поля... знакомый мой клялся-божился что это глюк MS Access при работе через ODBC... поверил на слово... на других СУБД не тестировал. У кого будет желание протестировать, расскажите плз. потом о результатах.

И еще... я как-то писал библиотечку для работы с MySQL... называется wxMySQL. Она невизуальная, надстройка над MySQL'овским API. Кому не понравится юзать ODBC, можете попробовать wxMySQL (она бесплатная, сырцы в комплекте, примеры - тоже в комплекте). Сайт проекта: http://www.ttanalytics.nm.ru/wxcomponents/wxMySQL/index.html

Файл "Headers.h"

<!--c1-->

CODE
<!--ec1-->
#ifndef _HEADERS_H
#define _HEADERS_H

#include <wx/wx.h>
#include <wx/db.h>
#include <wx/grid.h>
#include <wx/sizer.h>
#include <wx/dbgrid.h>
#include <wx/numdlg.h>
#include <wx/dbtable.h>

#endif
<!--c2-->
<!--ec2-->

Файл "Structures.h"

<!--c1-->
CODE
<!--ec1-->
#ifndef _STRUCTURES_H
#define _STRUCTURES_H

#include "Headers.h"

class ConnectionParams : public wxObject
{
public:
    wxChar    ODBCSource[SQL_MAX_DSN_LENGTH+1];
    wxChar    UserName[SQL_MAX_USER_NAME_LEN+1];
    wxChar    Password[SQL_MAX_AUTHSTR_LEN+1];
    wxChar    DirPath[MAX_PATH+1];
    ConnectionParams();
};

class DB_Position : public wxObject
{
public:
    int m_PositionID;
    wxChar m_PositionName[50+1];    
};

WX_DECLARE_OBJARRAY(DB_Position, DB_Positions);

#endif
<!--c2-->
<!--ec2-->

Файл "Structures.cpp"

<!--c1-->
CODE
<!--ec1-->
#include "Structures.h"
#include <wx/arrimpl.cpp>

WX_DEFINE_OBJARRAY(DB_Positions);

ConnectionParams::ConnectionParams()
{
    ODBCSource[0] = 0;
    UserName[0] = 0;
    Password[0] = 0;
    DirPath[0] = 0;
}
<!--c2-->
<!--ec2-->

Файл "Application.h"

<!--c1-->
CODE
<!--ec1-->
#ifndef _APPLICATION_H
#define _APPLICATION_H

#include "Headers.h"
#include "Structures.h"

class DBTestApp : public wxApp
{
    ConnectionParams m_ConnectionParams;
    wxDbConnectInf * m_DbConnectInf;
    wxDb * m_Db;
public:
    virtual bool OnInit();
    virtual int OnExit();
    void ApplyParameters();
    ConnectionParams & GetConnectionParams() {return m_ConnectionParams;}
    wxDbConnectInf * GetConnectInf() {return m_DbConnectInf;}
    bool OpenDB();
    wxDb * GetDB() {return m_Db;}
};

DECLARE_APP(DBTestApp);

#endif
<!--c2-->
<!--ec2-->

Файл "Application.cpp"

<!--c1-->
CODE
<!--ec1-->
#include "Application.h"
#include "MainFrame.h"
#include <wx/image.h>

IMPLEMENT_APP(DBTestApp)

bool DBTestApp::OnInit()
{
    m_Db = NULL;
    m_DbConnectInf = NULL;
    wxImage::AddHandler(new wxXPMHandler());
    wxImage::AddHandler(new wxPNGHandler());

    // Создаем объект "Параметры соединения с БД"
    m_DbConnectInf = new wxDbConnectInf(NULL, m_ConnectionParams.ODBCSource, m_ConnectionParams.UserName,
                                      m_ConnectionParams.Password, m_ConnectionParams.DirPath);
    if (!m_DbConnectInf || !m_DbConnectInf->GetHenv())
    {
        wxMessageBox(wxT("Unable to define data source connection info."));
        wxDELETE(m_DbConnectInf);
    }
    MainFrame * frame = new MainFrame(NULL);
    SetTopWindow(frame);
    frame->Show();
    return TRUE;
}

int DBTestApp::OnExit()
{
    // Удаляем объект "Параметры соединиения с БД"
    if(m_DbConnectInf) wxDELETE(m_DbConnectInf);
    // Если база открыта
    if(m_Db)
    {
        //Закрываем соединение
        if(m_Db->IsOpen()) m_Db->Close();
        wxDbCloseConnections();
    }
    return 0;
}

void DBTestApp::ApplyParameters()
{
    // Применяем параметры соединения
    m_DbConnectInf->SetDsn(m_ConnectionParams.ODBCSource);
    m_DbConnectInf->SetUserID(m_ConnectionParams.UserName);
    m_DbConnectInf->SetPassword(m_ConnectionParams.Password);
    m_DbConnectInf->SetDefaultDir(m_ConnectionParams.DirPath);
}

bool DBTestApp::OpenDB()
{
    // Создаем объект "База данных"
    m_Db = wxDbGetConnection(m_DbConnectInf);
    if (m_Db == NULL)
    {        
        return false;
    }
    return true;
}
<!--c2-->
<!--ec2-->

Файл "MainFrame.h"

<!--c1-->
CODE
<!--ec1-->
#ifndef _MAIN_FRAME_H
#define _MAIN_FRAME_H

#include "Headers.h"
#include "Structures.h"

class MainFrame : public wxFrame
{        
    wxListBox * m_DBListBox;
    wxTextCtrl * m_UserNameTextCtrl;
    wxTextCtrl * m_PasswordTextCtrl;
    wxGrid * m_PositionsGrid;
    wxDbTable * m_Positions;
    DB_Position m_CurPosition;
    wxButton * m_AddPositionButton;
public:
    MainFrame(wxWindow * parent);
    ~MainFrame();
    void CreateTables();
    DECLARE_EVENT_TABLE()        
    void OnExit(wxCommandEvent & event);    
    void OnToggleStatusBar(wxCommandEvent & event);
    void OnConnect(wxCommandEvent & event);
    void OnDBListBoxDClick(wxCommandEvent & event);
    void OnAddPosition(wxCommandEvent & event);
};

#endif
<!--c2-->
<!--ec2-->

Файл "MainFrame.cpp"

<!--c1-->
CODE
<!--ec1-->
#include "MainFrame.h"
#include "Application.h"

enum
{
    ID_TOGGLE_STATUSBAR = 10001,    
    ID_CONNECT,
    ID_DB_LIST_BOX,
    ID_USER_NAME_TEXT_CTRL,
    ID_PASSWORD_TEXT_CTRL,
    ID_POSITIONS_GRID,
    ID_WORKERS_GRID,
    ID_ADD_POSITION,

    // Unused
    ID_UNUSED
};

BEGIN_EVENT_TABLE(MainFrame, wxFrame)
EVT_MENU(wxID_EXIT, MainFrame::OnExit)
EVT_MENU(ID_TOGGLE_STATUSBAR, MainFrame::OnToggleStatusBar)
EVT_MENU(ID_CONNECT, MainFrame::OnConnect)
EVT_LISTBOX_DCLICK(ID_DB_LIST_BOX, MainFrame::OnDBListBoxDClick)
EVT_BUTTON(ID_ADD_POSITION, MainFrame::OnAddPosition)
END_EVENT_TABLE()

MainFrame::MainFrame(wxWindow * parent)
: wxFrame(parent, -1, _("Test"), wxDefaultPosition, wxSize(640,480))
{        
    /// Наша строка меню... создаем
    wxMenuBar * menuBar = new wxMenuBar();
    /// Ассоциируем строку меню с формой
    SetMenuBar(menuBar);
    
    wxMenu * fileMenu = new wxMenu;
    fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));

    wxMenu * viewMenu = new wxMenu;
    viewMenu->AppendCheckItem(ID_TOGGLE_STATUSBAR, _("StatusBar"), _("Shows/hides StatusBar"));
    viewMenu->Check(ID_TOGGLE_STATUSBAR, true);

    menuBar->Append(fileMenu, _("File"));
    menuBar->Append(viewMenu, _("View"));

    wxToolBar * toolBar = CreateToolBar(wxNO_BORDER|wxTB_HORIZONTAL|wxTB_FLAT);
    toolBar->AddTool(ID_ADD_POSITION, _("Добавить запись"),
        wxBitmap(wxT("new.xpm"), wxBITMAP_TYPE_XPM), _("Добавляет новую запись в таблицу"));
    toolBar->EnableTool(ID_ADD_POSITION, false);

    toolBar->Realize();

    wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
    SetSizer(sizer);

    m_UserNameTextCtrl = new wxTextCtrl(this, ID_USER_NAME_TEXT_CTRL, _(""));
    m_PasswordTextCtrl = new wxTextCtrl(this, ID_PASSWORD_TEXT_CTRL, _(""));
    wxBoxSizer * paramsizer = new wxBoxSizer(wxHORIZONTAL);
    paramsizer->Add(m_UserNameTextCtrl, 1, wxRIGHT, 5);
    paramsizer->Add(m_PasswordTextCtrl, 1);

    m_DBListBox = new wxListBox(this, ID_DB_LIST_BOX, wxDefaultPosition, wxSize(300, 100));

    // Сюда мы будем записывать названия источников данных ОДБЦ
    wxChar Dsn[SQL_MAX_DSN_LENGTH+1];
    wxChar DsDesc[254+1];
    wxSortedArrayString strArr;

    // Получаем параметры источников данных
    while (wxDbGetDataSource(wxGetApp().GetConnectInf()->GetHenv(), Dsn,
                             SQL_MAX_DSN_LENGTH, DsDesc, 254))
    {
        // Записываем результаты в ListBox
        m_DBListBox->Append(Dsn);
    }

    wxStaticBoxSizer * positionssizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Positions"));

    m_PositionsGrid = new wxGrid(this, ID_POSITIONS_GRID, wxDefaultPosition, wxSize(300, 120));
    m_AddPositionButton = new wxButton(this, ID_ADD_POSITION, _("Add"));

    positionssizer->Add(m_PositionsGrid, 1, wxEXPAND|wxALL, 5);
    positionssizer->Add(m_AddPositionButton, 0, wxGROW|wxLEFT|wxRIGHT|wxBOTTOM, 5);
    m_AddPositionButton->Enable(false);
    m_PositionsGrid->SetColLabelSize(20);
    m_PositionsGrid->SetRowLabelSize(20);
    m_PositionsGrid->SetDefaultColSize(130);
    m_PositionsGrid->SetDefaultRowSize(20);

    sizer->Add(paramsizer, 0, wxGROW|wxALL, 5);
    sizer->Add(m_DBListBox, 0, wxGROW|wxALL, 5);
    sizer->Add(positionssizer, 0, wxGROW|wxALL, 5);
    
    
    /// Создаем строку состояния
    CreateStatusBar();
    /// Подогнать размер формы до размеров, занимаемых компонентами (чтобы все компоненты были видны)
    sizer->Fit(this);
    /// Центрируем форму на экране
    Centre();
}

MainFrame::~MainFrame()
{
}

void MainFrame::OnToggleStatusBar(wxCommandEvent & event)
{
    /// Получаем указатель на строку состояния
    wxStatusBar * statusBar = GetStatusBar();
    /// Если у окна есть строка состояния, то...
    if(statusBar)
    {
        /// Говорим что ее нет...
        SetStatusBar(NULL);
        /// Удаляем ту, что была...
        statusBar->Destroy();
    }
    /// А если строки состояния не было...
    else
    {
        /// Пересоздаем...
        CreateStatusBar();
    }
    /// Говорим окну что необходимо обновить размещение дочерних контролов
    Layout();
}

void MainFrame::OnExit(wxCommandEvent & event)
{
    Close();
}



void MainFrame::OnConnect(wxCommandEvent & event)
{    
}

void MainFrame::OnDBListBoxDClick(wxCommandEvent & event)
{
    int item = event.GetSelection();
    // Если есть выделенный элемент в ListBox...
    if(item >= 0)
    {            
        // Получаем параметры соединения с БД (логин, пароль и имя источника данных)
        wxSprintf(wxGetApp().GetConnectionParams().UserName, m_UserNameTextCtrl->GetValue().GetData());
        wxSprintf(wxGetApp().GetConnectionParams().Password, m_PasswordTextCtrl->GetValue().GetData());
        wxSprintf(wxGetApp().GetConnectionParams().ODBCSource, m_DBListBox->GetString(item).GetData());
        // Применяем параметры... (см. исходник класса приложения)
        wxGetApp().ApplyParameters();
        // Пытаемся открыть БД (см. исходник класса приложения)
        if(!wxGetApp().OpenDB())
        {
            wxMessageBox(_("Connection Error"));
            Close();
        }        
        m_DBListBox->Enable(false);
        m_UserNameTextCtrl->Enable(false);
        m_PasswordTextCtrl->Enable(false);
        m_AddPositionButton->Enable(true);
        wxToolBar * toolbar = GetToolBar();
        if(toolbar)
        {
            toolbar->EnableTool(ID_ADD_POSITION, true);
        }
        CreateTables();
    }
}

void MainFrame::CreateTables()
{
    int numColumns = 2;
    // Создаем объект таблицы (из бд)
    m_Positions = new wxDbTable (wxGetApp().GetDB(), _("Positions"), numColumns);    
    // Устанавливаем буфер (куда записывать/получать результаты) для каждого поля
    m_Positions->SetColDefs(0, "ID", DB_DATA_TYPE_INTEGER, &m_CurPosition.m_PositionID,
            SQL_C_LONG, sizeof(m_CurPosition.m_PositionID), true);
    m_Positions->SetColDefs(1, "PositionName", DB_DATA_TYPE_VARCHAR, &m_CurPosition.m_PositionName,
            SQL_C_CHAR, sizeof(m_CurPosition.m_PositionName), false);

    // Здесь хранится информация о столбцах таблицы
    wxDbGridColInfo *columns;    
    // Заполняем информацию о столбцах таблицы
    columns = new wxDbGridColInfo(0, wxGRID_VALUE_LONG, "ID", NULL);    
    columns->AddColInfo (1, wxGRID_VALUE_STRING, "PositionName");
    
    // Пытаемся открыть таблицу БД
    m_Positions->Open();        
    m_Positions->SetRowMode(wxDbTable::WX_ROW_MODE_QUERY);    
    // Выполняем выборку записей из таблицы
    m_Positions->Query();
    // Этот объект нужен для заполнения wxGrid результатами выборки
    wxDbGridTableBase *dbgrid = new wxDbGridTableBase(m_Positions, columns, wxUSE_QUERY, true);
    delete columns;  
    // Заполняем wxGrid
    m_PositionsGrid->SetTable(dbgrid, true);  
    // Обновляем
    m_PositionsGrid->Refresh();
}

void MainFrame::OnAddPosition(wxCommandEvent & event)
{    
    // Ввести ID
    m_CurPosition.m_PositionID = wxGetNumberFromUser(_("ID"), _("Input ID:"), _("ID"), 0);
    // Ввести название
    wxSprintf(m_CurPosition.m_PositionName, wxGetTextFromUser(_("Position name:")).GetData());
    // добавить запись
    m_Positions->Insert();
    // Переоткрыть таблицу.
    m_Positions->Open();
}
<!--c2-->
<!--ec2-->

Вобще, в поставке wxWidgets есть пример работы с БД, но там тоже немного странновато сделано... без использования гридки, просто сделана форма ввода. Но, хотя работа с БД стандартными средствами организована довольно коряво, все-таки приложения, которые используют ODBC сохраняют портируемость и, при условии корректной настройки ODBC работают и в Линухе, хотя, еще раз повторюсь, мне лично не удавалось попробовать... мне всегда хватало моей библиотеки wxMySQL или родного MySQL'овского API.


Опубликовал admin
7 Дек, Среда 2005г.



Программирование для чайников.