2009年5月8日星期五

转贴 MFC到wxWidgets移植笔记

1#
打印
字体大小: tT
发表于 2008-12-11 13:34 | 只看该作者
转贴 MFC到wxWidgets移植笔记
MFC到wxWidgets移植笔记(1)――工程设置
最近在捣腾wxWidgets,学习的最好办法是做个实在东西。以前做过一个个人财物管理的软件MyMoney,功能不太多,与系统关联不大,界面的东西多,还有一点数据库的内容,是学习wxWidgets的绝佳对象。以后将会不断的贴出移植的笔记。

首先准备开发环境。从http://www.wxwidgets.org/downloads/下载wxWidgets 的wxMSW版本,我们一步步来,先在MSW平台上移植成功,再到Linux上测试。当前wxWidgets版本为2.8.7,解压到e:\wx,打开 e:\wx\build\msw\wx.dsw,我用的是VC2005,将工程全部转换成新的工程文件wx.sln。wx有20个工程,其中的core和 base最基础,需要编译,其它的工程以后用到再说。不需要编译所有的解决方案配置,因为我们的工程是单工程,字符集为Unicode,因此只需编译 Unicode Debug和Unicode Release就行了。

接下来修改MyMoney工程设置。右键点击工程,在属性页中修改以下属性(Debug版本,Release版本类似): Use of MFC Use Standard WIndows Libraries
Additional Include Directories E:\wx\include;E:\wx\lib\vc_lib\mswd
Preprocessor Definitions WIN32;_DEBUG;__WXMSW__;__WXDEBUG__;_WINDOWS
Additional Library Directories E:\wx\lib\vc_lib
Additional Dependencies wxmsw28d_core.lib wxbase28d.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib wxregexd.lib wxexpatd.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib odbc32.lib

打开stdafx.h,清空,再写入下面两行代码:
#pragma once
#include <wx/wx.h>

至此,工程设置完成。

MFC到wxWidgets移植笔记(2)――移植CDoubleEdit
移植从最简单、最小的模块开始,MyMoney中有几个控件类比较独立,首先移植他们。
CDoubleEdit是一个只能输入double的文本控件,其小数位置最多允许两位,在MyMoney中提供金钱输入的功能。
在MFC中,CDoubleEdit从CEdit派生,为响应消息EN_CHANGE,实现了消息函数OnEnChange,当文本控件内容发生改变后,判断是否是合法值,如果不是则恢复成旧值m_content。以下是CDoubleEdit在MFC中的声明:

class CDoubleEdit : public CEdit
{
public:
CDoubleEdit();
virtual ~CDoubleEdit();

protected:
DECLARE_MESSAGE_MAP()

public:
afx_msg void OnEnChange();

private:
std::wstring m_content;
};

wxWidgets提供了一个wxTextCtrl类,这个类相当于CEdit,同样wxWidgets也有消息映射机制,实现了消息函数OnText,响应消息wxEVT_COMMAND_TEXT_UPDATE。以下是CDoubleEdit在wxWidgets中的声明:

class CDoubleEdit : public wxTextCtrl
{
public:
CDoubleEdit(wxWindow *parent,
wxWindowID id = wxID_ANY,
const wxString& value = wxT("0"),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize);
virtual ~CDoubleEdit();

public:
void OnText(wxCommandEvent& event);

private:
wxString m_content;
DECLARE_EVENT_TABLE()
};


我不喜欢用MFC的CString,所以以前的代码用std::wstring比较多,虽然这种类型也可以跨平台,但wxString似乎更好用一点,因此移植后将所有的std::wstring改成了wxString。

wxWidgets的宏wxT可以根据工程字符集来转换字符串。

wxPoint和wxSize类似于MFC中的CPoint和CSize。

MFC用DECLARE_MESSAGE_MAP()声明消息映射表,wxWidgets中为DECLARE_EVENT_TABLE()。



再改cpp文件。将MFC中

BEGIN_MESSAGE_MAP(CDoubleEdit, CEdit)
ON_CONTROL_REFLECT(EN_CHANGE, OnEnChange)
END_MESSAGE_MAP()

换成适合wxWidgets的

BEGIN_EVENT_TABLE(CDoubleEdit, wxTextCtrl)
EVT_TEXT(wxID_ANY, CDoubleEdit::OnText)
END_EVENT_TABLE()

其它的工作,就是替换部分成员函数的体力活了。 GetWindowText GetValue
SetWindowText SetValue
SetSel SetSelection
GetSafeHwnd GetHandle


另外提一笔wxString,很好用,比如他提供了Format函数:

double d = 5.6f;

m_content = wxString::Format(L"%.2f", d);

和类型转换函数:

double d;

m_content.ToDouble(&d);

MFC到wxWidgets移植笔记(3)--移植CDailyButton
CDailyButton显示简单的每日支出,点击此Button可以看到详细情况并做支出操作。由于要显示的内容比较多,需要自定义绘制。下图是简单示意:


CDailyButton从wxButton派生。可能其它操作系统没提供自绘功能,wxButton没有自己的自绘机制,还好基于Windows操作系统,WxButton的MSW版本提供了MSWOnDraw函数,这个函数相当于Windows的DrawItem函数。另外如果要激发 MSWOnDraw函数,必须在CDailyButton的构造函数中调用函数SetBackgroundColour,此函数调用 MakeOwnerDrawn,设置OWNERDRAW属性。

通过这个类的移植,我发现wxWidgets并不是无所不能的,有些时候还是需要做与平台相关的实现。

MFC到wxWidgets移植笔记(4)――前两篇移植笔记的补充

年前写的移植笔记,就突然中断了,没有别的原因,因为当时就发现走了很多弯路,代码改了又改,最后一直到真正的移植到Ubuntu,编译通过,是几个月后的事情了。因为把这些东西都弄清楚了,才开始继续这个笔记,其中又学到了不少的东西。当初的想法果然是对的,只有找个小小的项目练练手,才可能学到不少好东西。
回头看看前三篇笔记,第一篇没必要修改,第2、3篇的内容发生了不少改变,其实主要是重构,以前的路子还是正确的。
第二篇中的CDoubleEdit,类名称做了修改。首先是去掉了前面的C。类命名时在前面加C,是传统的匈牙利命名法,这种方法没什么问题,但是好像 Linux下的代码不买这个帐,因此我也去掉了。顺带的,后面所有的类,都去掉了以C开头的命名方式。其次以Double为名,没有反映出表示钱的意思,因此将类名改成了MoneyTextCtrl。TextCtrl是为了符合wxWidgets的命名标准。
第三篇中的CDailyButton,同样换了一个名字,新名字为DailyPanel。Ubuntu下面,没有自绘的Button一说,因此新的 DailyPanel直接从wxWindow派生,然后实现wxWindow的EVT_PAINT消息,同样实现了自绘功能。当然,因为不再是 Button,此时需要自己实现一些Button的功能了,在EVENT_TABLE中,增加几个消息响应。主要是当DailyButton获得和失去焦点,或者按键、鼠标点击的时候,触发他的刷新消息就行了:
BEGIN_EVENT_TABLE(DailyPanel, wxWindow)
EVT_PAINT(DailyPanel::OnPaint)
EVT_ERASE_BACKGROUND(DailyPanel::OnErase)
EVT_SET_FOCUS(DailyPanel::OnSetFocus)
EVT_KILL_FOCUS(DailyPanel::OnKillFocus)
EVT_CHAR(DailyPanel::OnChar)
EVT_MOUSE_EVENTS(DailyPanel::OnMouseEvent)
END_EVENT_TABLE()
另外,在有RETURN和SPACE按键或者鼠标点击时,需要向上级窗口发送一个wxEVT_COMMAND_BUTTON_CLICKED消息,模拟Button消息:
void DailyPanel::OnChar(wxKeyEvent& event)
{
if (WXK_RETURN == event.GetKeyCode()
|| WXK_SPACE == event.GetKeyCode())
{
wxCommandEvent et;
et.SetEventType(wxEVT_COMMAND_BUTTON_CLICKED);
et.SetId(GetId());
GetParent()->GetEventHandler()->rocessEvent(et);
}
}
这样的实现,可保证DailyPanel的平台移植性了。

MFC到wxWidgets移植笔记(5)――移植CSortListCtrl
记帐类的软件,是不可能没有Grid的。我这个小东西,还用不上那么麻烦,一个小小的list就够显示数据了,不过排序功能不可少,因此有了这个 SortListCtrl(MFC版本中叫做CSortListCtrl)。当用户点击list的Column时,将触发排序功能。
先看MFC的定义:

class CSortListCtrl : public CListCtrl
{

public:
CSortListCtrl();
virtual ~CSortListCtrl();

public:
afx_msg void OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult);

protected:
DECLARE_MESSAGE_MAP()

private:
static int CALLBACK CompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
};

wxWidgets中的定义:

class SortListCtrl : public wxListCtrl
{
DECLARE_EVENT_TABLE()
public:
SortListCtrl(wxWindow *parent, wxWindowID id, wxSize sz = wxDefaultSize);
virtual ~SortListCtrl();

public:
void OnColumnclick(wxListEvent& event);

private:
static int wxCALLBACK CompareProc(long lParam1, long lParam2, long lParamSort);
};

以前就有评论说了,wxWidgets的类结构非常像MFC,通过这个类的移植,能非常清楚的反映出来。

MFC到wxWidgets移植笔记(6)――移植对话框CClauseDlg
控件只有简单的三个,下面就是移植对话框了。对话框将近20个 。我找了一个最简单,又具有比较完备功能的对话框ClauseDlg说明一下。



图片是在Ubuntu下面截的,说老实话,简单的截图然后裁剪一下,Ubuntu还真不是很好用。

这个对话框很简单,新增一个收支项目,除了指定项目名称,还要设定是支出还是收入,最后点击确定增加。

同理,首先将CClauseDlg改成ClauseDlg。下面是类声明:
class ClauseDlg : public wxDialog
{
DECLARE_EVENT_TABLE()
public:
ClauseDlg(wxWindow* pParent);
virtual ~ClauseDlg();
public:
virtual bool TransferDataToWindow();
virtual bool TransferDataFromWindow();
protected:
void CreateControl();
void OnBnClickedOk(wxCommandEvent& event);
public:
wxString m_strClause;
bool m_bin;
};
注:为了在此简单说明,我将成员变量的访问属性改成了public,去掉了get/set函数。
这个类与原MFC类有以下几个不同:
1.基类从CDialog改成wxDialog
2.消息映射表声明从DECLARE_MESSAGE_MAP改成DECLARE_EVENT_TABLE
3.对话框不再使用资源文件,其初始化的所有过程,都在CreateControl函数中。
4.重载TransferDataToWindow和TransferDataFromWindow。这两个函数实现了成员变量与控件的数据同步,类似于MFC的DoDataExchange。

再看看实现:

ClauseDlg::ClauseDlg(wxWindow* pParent)
: wxDialog(pParent, wxID_ANY, wxT("收支项目"), wxDefaultPosition)
, m_strClause(wxEmptyString)
, m_bin(false)
{
CreateControl();
}

ClauseDlg::~ClauseDlg()
{
}

BEGIN_EVENT_TABLE(ClauseDlg, wxDialog)
EVT_BUTTON(wxID_OK, ClauseDlg::OnBnClickedOk)
END_EVENT_TABLE()

// ClauseDlg message handlers
enum {wxID_TEXT_CLAUSE, wxID_RADIO_OUT, wxID_RADIO_IN};

void ClauseDlg::CreateControl()
{
wxBoxSizer* clause = new wxBoxSizer(wxHORIZONTAL);
clause->Add(new wxStaticText(this, wxID_ANY, wxT("收支项目")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10);
clause->Add(new wxTextCtrl(this, wxID_TEXT_CLAUSE, m_strClause), 1, wxEXPAND);

wxBoxSizer* outin = new wxBoxSizer(wxHORIZONTAL);
outin->Add(new wxRadioButton(this, wxID_RADIO_OUT, wxT("支出"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP), 1, wxEXPAND);
outin->Add(new wxRadioButton(this, wxID_RADIO_IN, wxT("收入")), 1, wxEXPAND);

wxStdDialogButtonSizer* button = new wxStdDialogButtonSizer();
button->AddButton(new wxButton(this, wxID_OK, wxT("确定")));
button->AddButton(new wxButton(this, wxID_CANCEL, wxT("取消")));
button->Realize();
((wxButton*)FindWindow(wxID_OK))->SetDefault();

wxBoxSizer* root = new wxBoxSizer(wxVERTICAL);
root->Add(clause, 0, wxEXPAND | wxALL, 5);
root->Add(outin, 0, wxEXPAND | wxALL, 5);
root->Add(button, 0, wxALL, 5);
SetSizerAndFit(root);
}

void ClauseDlg::OnBnClickedOk(wxCommandEvent&)
{
if (m_strClause.Length() > 0)
{
EndDialog(wxID_OK);
}
}

bool ClauseDlg::TransferDataToWindow()
{
((wxTextCtrl*)FindWindow(wxID_TEXT_CLAUSE))->SetValue(m_strClause);
((wxRadioButton*)FindWindow(wxID_RADIO_IN))->SetValue(m_bin);

return wxDialog::TransferDataToWindow();
}

bool ClauseDlg::TransferDataFromWindow()
{
m_strClause = ((wxTextCtrl*)FindWindow(wxID_TEXT_CLAUSE))->GetValue();
((wxRadioButton*)FindWindow(wxID_RADIO_IN))->GetValue();

return wxDialog::TransferDataFromWindow();
}
这个实现中包含了很多东西,一点点的来说。

1.构造函数在构造时首先调用了基类的构造函数,并传递了适当的信息。基类的构造函数,实际上已经完成了对话框的创建过程,此时窗口已经被创建出来。不过此时的对话框还是一个光秃秃的东西。然后调用CreateControl来构造对话框的所有控件。

2.在函数CreateControl前,定义了一个enum,它定义了对话框中绑定数据的控件ID。通过FindWindow和这个ID,就能找到控件相对应的类,并对其做相关操作。

3.CreateControl创建控件,同时还构造了一个布局。wxWidgets使用了和Java一样的布局方式,设定好控件的相对位置,加入到 BoxSizer中,然后调用SetSizerAndFit,将一切交给BoxSizer来计算他们的实际位置。关于布局的具体说明,参考《使用 wxWidgets进行跨平台程序开发》。

4.CreateControl除了可以创建控件,还可以做其它的任何初始化的操作,有点相当于MFC中的OnInitialDlg函数。

5.关闭模式对话框,应该使用EndDialog,并传递一个ID作为参数。外部通过函数ShowModal调用这个对话框的时候,这个ID作用ShowModal的返回值返回。

6.查找控件用FindWindow,找到会返回一个wxWindow的对象指针,可以强制转换成对应的类指针。

7.TransferDataToWindow将成员变量转到窗口控件上,TransferDataFromWindow从窗口控件获得数据。类似于DDX_Control和DDX_Text一类的东西。

8.__super这个关键字好像g++不支持,因此在调用基类的时候,必须明确指出基类的名称。

最后记一笔这个Dialog的用法:

ClauseDlg dlg(this, this);
if (dlg.ShowModal() == wxID_OK)
{
...
}

MFC到wxWidgets移植笔记(7)――数据库sqlite3
在Windows平台上,最好的小型数据库,可能非Access莫属了。小巧,独立,数据库功能齐全,具备一定的安全性,真可谓是居家旅行,必备之数据库了。

可惜的是,Access只能适用于Windows平台。想通吃其它平台,我找了很久,最后发现了sqlite3。

sqlite3也是拥有独立的数据库文件,文件小巧,易使用,提供了标准的C函数库文件。能提供大多数数据库操作,由于他支持的数据库特性太多,所以他的网站上只列出不能支持的特性:

外键约束(FOREIGN KEY constraints)
外键约束会被解析但不会被执行。
完整的触发器支持(Complete trigger support)
现在有一些触发器的支持,但是还不完整。 缺少的特性包括 FOR EACH STATEMENT 触发器(现在所有的触发器都必须是 FOR EACH ROW ), 在表上的 INSTEAD OF 触发器(现在 INSTEAD OF 触发器只允许在视图上),以及递归触发器――触发自身的触发器。
完整的 ALTER TABLE 支持(Complete ALTER TABLE support)
只支持 ALTER TABLE 命令的 RENAME TABLE 和 ADD COLUMN。 其他类型的 ALTER TABLE 操作如 DROP COLUMN,ALTER COLUMN,ADD CONSTRAINT 等等均被忽略。
嵌套事务(Nested transactions)
现在的实现只允许单一活动事务。
RIGHT 和 FULL OUTER JOIN(RIGHT and FULL OUTER JOIN)
LEFT OUTER JOIN 已经实现,但还没有 RIGHT OUTER JOIN 和 FULL OUTER JOIN。
可写视图(Writing to VIEWs)
SQLite 中的视图是只读的。无法在一个视图上执行 DELETE,INSERT,UPDATE。 不过你可以创建一个试图在视图上 DELETE,INSERT,UPDATE 时触发的触发器,然后在触发器中完成你所需要的工作。
GRANT 和 REVOKE(GRANT and REVOKE)
由于 SQLite 读和写的是一个普通的磁盘文件, 因此唯一可以获取的权限就是操作系统的标准的文件访问权限。一般在客户机/服务器架构的关系型数据库系统上能找到的 GRANT 和 REVOKE 命令对于一个嵌入式的数据库引擎来说是没有意义的,因此也就没有去实现。
其实上述问题,要么有其它办法解决(比如外键约束,可心用触发器实现),要么就用不上(比如视图),所以对于大多数需要跨平台的小型数据库的应用来说,sqlite3完全够用了。

官方的sqlite3只提供了一套C函数库文件(sqlite3.c,sqlite3.h,sqlite3ext.h),要想用可视化的操作方式,可以用第三方的Sqlite Developer。

MFC到wxWidgets移植笔记(8)――数据库设计
据我实验感觉,sqlite3的内部数据,实际上都是二进制。其实所有的数据库,内部都是以二进制保存。只不过通过那些数据库的访问接口,必须根据正确的类型来获得数据。sqlite3在这方面做得更开放一些,或者说更不严格。虽然在创建表的时候可以指定字段的类型和长度,实际存储或者读取的时候,是可以用其它类型的,并且不会出错。

但是即便如此,我们也还是要设计一个完善的数据库定义,这样对于以后数据的移植也方便得多。

以下是我用Sqlite Developer导出的表user的定义:

Drop Table If Exists [user];
CREATE TABLE IF NOT EXISTS 'user'(
[id] integer PRIMARY KEY
,[name] varchar(32) UNIQUE NOT NULL
,[pass] varchar(128) NOT NULL
,[last_time_login] datetime
);

可以看到,大部分的sql关键字都支持,语法非常通用。

再看表bank的定义:

Drop Table If Exists [bank];
CREATE TABLE IF NOT EXISTS 'bank'(
[id] integer PRIMARY KEY
,[user_id] integer NOT NULL
,[name] varchar(32) NOT NULL
,[last_time_record] datetime
);

字段user_id是一个外键,与user中的id具有删除关联。在前面提到过,sqlite3不支持外键,可以用触发器实现:

Drop Trigger If Exists [tgrDeleteUser];
CREATE TRIGGER IF NOT EXISTS [tgrDeleteUser] AFTER DELETE On [user] FOR EACH ROW
begin
delete from bank where user_id=old.id;
end;

Drop Trigger If Exists [tgrInsertBank];
CREATE TRIGGER IF NOT EXISTS [tgrInsertBank] BEFORE INSERT On [bank] FOR EACH ROW
begin
select raise(rollback, 'insert invalid')
where (select id from user where id=new.user_id) is null;
end;

MFC到wxWidgets移植笔记(9)――数据库访问
sqlite3提供了一套C代码,用来访问sqlite3数据。这套代码包含了三个文件,sqlite3.h,sqlite3.c以及 sqlite3ext.h。我将sqlite3的源代码放到整个工程中,因此用不上sqlite3ext.h。我们只需要在工程中加入sqlite3.h 和sqlite3.c就行了。

前面说过sqlite3的内部完全是二进制,我们可以仅管把所有读取的数据当作二进制看待。因此,只要把中文全部转换成UTF8,这样就能完美的解决中文读取和存储的问题了。

下面是一些常用到的函数:

1.打开关闭数据库:

SQLITE_API int sqlite3_open(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb /* OUT: SQLite db handle */
);

SQLITE_API int sqlite3_close(sqlite3 *);

sqlite3_open的第二个参数返回sqlite3*的指针。这个指针指向了一个sqlite3的对象,我们不用关注它的具体内容是什么,就当成一个Windows系统中的句柄就行了。

sqlite3的函数通常都返回一个int表示函数执行结果,一般来说返回SQLITE_OK,也就是0,就表示成功了。

2.执行一个命令

SQLITE_API int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluted */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);

基本上,所有的sql操作都是通过这条语句来执行的,以下是一个例子,只处理那种非查询的sql语句:

extern sqlite3* sqlite3DB;

bool DBDatabase::Execute(const wxString& sql)

{

char* errMsg = NULL;
bool ret = (SQLITE_OK == sqlite3_exec(sqlite3DB, sql.ToUTF8(), NULL, NULL, &errMsg));
if (errMsg != NULL)
{
if (! ret)
{
m_err = wxString::From8BitData(errMsg);
m_err += wxT("\r\n");
}
sqlite3_free(errMsg);
}
return ret;

}

当处理非查询sql语句时,sqlite3_exec的第三、四个参数为空。

注意,如果执行结果有错,错误信息保存在errMsg中,这段内存需要通过sqlite3_free释放,否则有内存泄露的问题。

3.事务操作

非常简单,看代码:

void DBDatabase::BeginTrans()
{
Execute(wxString(wxT("begin transaction")));
}

void DBDatabase::CommitTrans()
{
Execute(wxString(wxT("commit transaction")));
}

void DBDatabase::RollbackTrans()
{
Execute(wxString(wxT("rollback transaction")));
}

4.查询操作

当执行"select"语句时,需要为sqlite3_exec的第三个参数指定一个回调函数:

int DBDatabase::sqlite3_callback(void* para, int n_column, char** column_value, char** column_name)
{
DBRecordSet* set = (DBRecordSet*)para;
set->AddRecord();
for(int i = 0 ; i < n_column; i ++ )
{
wxString field(column_name, wxMBConvUTF8());
wxString value(column_value, wxMBConvUTF8());
set->AddField(field, value);
}
return SQLITE_OK;
}

bool DBDatabase:ueryRecordSet(const wxString& sql, DBRecordSet* set)
{
set->Clear();
char* errMsg = NULL;
bool ret = (SQLITE_OK == sqlite3_exec(sqlite3DB, sql.ToUTF8(), DBDatabase::sqlite3_callback, (void*)set, &errMsg));
if (errMsg != NULL)
{
if (! ret)
{
m_err = wxString::From8BitData(errMsg);
m_err += wxT("\r\n");
}
sqlite3_free(errMsg);
}
return ret;
}

MFC到wxWidgets移植笔记(10)――数据访问接口
上一篇谈到了对sqlite3的封装,下面完整的描述数据访问接口。

数据访问接口模仿ADO设计,保存了查询结果。接口的最小的单位为Field,记录字段数据。其次为Record,描述一条记录。再就是RecordSet,是记录集合。数据库封装类为Database。所有的数据库访问接口都以DB开头。

MID表示主键类型,定义如下:

typedef unsigned long MID;

MTIME是对时间的封装类:

struct MTIME{
long year;
long month;
long day;

};

以下是数据结构定义:

class DBField
{
public:
DBField();
virtual ~DBField();

public:
virtual void SetField(const wxString& field, const wxString& value);
virtual const wxString& Field()const;
virtual const wxString& Value()const;

private:
wxString m_field;
wxString m_value;
};

class DBRecord
{
public:
DBRecord();
virtual ~DBRecord();

public:
virtual void AddField(const wxString& field, const wxString& value);
virtual bool GetField(const wxString& field, wxString& value)const;

private:
typedef std::vector<DBField> Fields;
Fields m_fields;
};

class DBRecordSet
{
public:
DBRecordSet();
virtual ~DBRecordSet();

public:
virtual void Clear();
virtual long RecordCount()const;

virtual void MoveFirst();
virtual bool MoveNext();
virtual bool GetField(const wxString& field, wxString& value)const;
virtual bool GetField(const wxString& field, MTIME& tm)const;
virtual bool GetField(const wxString& field, MID& id)const;
virtual bool GetField(const wxString& field, long& l)const;
virtual bool GetField(const wxString& field, double& d)const;
virtual bool GetField(const wxString& field, bool& b)const;

virtual void AddRecord();
virtual void AddField(const wxString& field, const wxString& value);
virtual void AddField(const wxString& field, const MTIME& tm);
virtual void AddField(const wxString& field, MID id);
virtual void AddField(const wxString& field, long l);
virtual void AddField(const wxString& field, double d);
virtual void AddField(const wxString& field, bool b);

private:
typedef std::vector<DBRecord> Records;
Records m_records;
Records::size_type m_pos;
};

interface IDatabase
{
virtual void BeginTrans()=0;
virtual void CommitTrans()=0;
virtual void RollbackTrans()=0;
virtual bool Execute(const wxString& sql)=0;
virtual bool QueryRecordSet(const wxString& sql, DBRecordSet* set)=0;
virtual wxString GetLastError()const=0;
};

struct sqlite3;

class DBDatabase : public IDatabase
{
public:
DBDatabase();
virtual ~DBDatabase();
static wxString GetDBFile();

public:
bool InitConnection();

virtual void BeginTrans();
virtual void CommitTrans();
virtual void RollbackTrans();
virtual bool Execute(const wxString& sql);
virtual bool QueryRecordSet(const wxString& sql, DBRecordSet* set);
virtual wxString GetLastError()const;

private:
static int sqlite3_callback(void* para, int n_column, char** column_value, char** column_name);

private:
sqlite3* sqlite3DB;
wxString m_err;
};

MFC到wxWidgets移植笔记(11)――表访问类
有了上节基本的数据访问接口,可以实现具体的表访问类了。以下以user表为例:

interface IDatabase;
class MDUser
{
public:
MDUser(IDatabase* pDB);
virtual ~MDUser(void);

public:
virtual void GetUser(wxArrayString& vUserNames);
virtual MID AppendUser(const wxString& name, const wxString& pass);
virtual bool DeleteUser(MID id);
virtual bool UpdateUser(MID id, const wxString& name, const wxString& pass);
virtual bool GetUserID(const wxString& name, const wxString& pass, MID& id);
virtual bool GetUserName(MID id, wxString& name);
virtual wxString EncryptPass(const wxString& pass)const;
virtual void UpdateLogin(MID id)const;

private:
IDatabase* db;
};

以两个函数实现为例:

MID MDUser::AppendUser(const wxString& name, const wxString& pass)
{
MID iduser = eMID;
if (db->Execute(wxString::Format(wxT("insert into user(name,pass) values(\'%s\',\'%s\')"),
name.c_str(), EncryptPass(pass).c_str())))
{
DBRecordSet set;
if (db->QueryRecordSet(wxT("select max(id) as max_id from user"), &set))
{
if (set.RecordCount() == 1)
{
set.MoveFirst();
set.GetField(wxT("max_id"), iduser);
}
}
}
return iduser;
}

bool MDUser:eleteUser(MID id)
{
return db->Execute(wxString::Format(wxT("delete from user where id=\'%d\'"), id));
}

MFC到wxWidgets移植笔记(12)――XML操作
作为当前最流行的通用格式xml,wxWidgets同样提供了非常方便的xml类库。由于用起来实在是太舒服了,以至于让我完全兴不起重新封装的念头。

要使用xml类库,除了编译xml模块外,还要先编译expat模块,xml模块必须用到此模块。expat没有在wxWidgets的默认工程中,需要自己手动编译。找到src下的expat,打开并分别编译debug和release版本,会分别生成expat.lib和expatd.lib。然后再编译xml模块。

要在工程中使用xml,首先是将expat.h拷贝到include目录中,在stdafx.h中添加#include <expat.h>,然后在工程设置中为lib中添加wxbase28u_xml.lib和expat.lib(Debug版本为 wxbase28ud_xml.lib和expatd.lib)。

以下是使用方式,先说读(只用过DOM模式读xml文档)。DOM的根类是wxXmlDocument:

wxXmlDocument doc;
doc.Load(file);

打开文件后,就可以直接取子节点了:

wxXmlNode *child = doc.GetRoot()->GetChildren();
while (child)
{
if (child->GetName() == wxT("store"))
{
ImportBank(id, child);
}
child = child->GetNext();
}

GetRoot()获得根节点,wxXmlNode是所有节点的基类,函数GetChildren()获得最左边的子孩子,函数GetNext()获得右孩子结点,函数GetName()获得节点名称,函数GetPropVal()获得属性。

以下是写的一个例子:

wxXmlDocument doc;

doc.SetVersion(wxT("1.0"));
doc.SetFileEncoding(wxT("UTF-8"));



wxXmlNode* root = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("wallet"));
root->AddProperty(wxT("user"), name);
root->AddProperty(wxT("pass"), pass);
root->AddProperty(wxT("version"), wxT("1.0"));
doc.SetRoot(root);


wxXmlNode* cash = new wxXmlNode(root, wxXML_ELEMENT_NODE, wxT("cash"));


doc.Save(file);

MFC到wxWidgets移植笔记(13)――多语言实现
多语言实现是大部分软件需要考虑的问题,解决的办法也很多,也没有什么规范性的做法。比较有名的算是微软的支持多语言的资源文件文件了,虽然用起来很方便,但同样存在着编辑困难,编译后无法修改的问题。在我这个软件里,使用了最最简单的做法,用一个xml文件存储字符串,通过简单英文(也可以是数字)标记词汇。然后在代码中用一个语言类来管理。

以下是这个xml文件(只列出一部分):

<?xml version="1.0"?>
<language_package code="chinese" name="简体中文">
<node field="S_CONNECT_FAIL" content="连接数据库失败!" />
<node field="bank" content="银行" />
<node field="bankname" content="银行名称" />
<node field="import_success" content="导入成功!" />
<node field="import_fail" content="导入失败!" />
</language_package>

然后是类实现。在类的构造函数中,打开xml文件,读出所有的数据,并存到一个map中:

LanguageRes:anguageRes()
{
wxStandardPaths stdPath;
wxFileName fn(stdPath.GetExecutablePath());
wxString szFile = fn.GetPath();
szFile += wxFILE_SEP_PATH;
szFile += wxT("chs.lng");

wxXmlDocument doc;
doc.Load(szFile);
wxXmlNode *root = doc.GetRoot();
ASSERT(root->GetName() == wxT("language_package"));
wxString code = root->GetPropVal(wxT("code"), wxEmptyString);
ASSERT(code == wxT("chinese"));
wxXmlNode *child = root->GetChildren();
while (child)
{
if (child->GetName() == wxT("node"))
{
wxString field = child->GetPropVal(wxT("field"), wxEmptyString);
wxString content = child->GetPropVal(wxT("content"), wxEmptyString);
m_map.insert(LANGUAAGE_MAP::value_type(field, content));
}
child = child->GetNext();
}
}
然后用了singleton模式,返回一个唯一的全局变量:

LanguageRes* LanguageRes::res = NULL;

LanguageRes* LanguageRes::GetLanguageRes()
{
if (res == NULL)
{
res = new LanguageRes;
}
return res;
}

注:最近面试时我会问一个问题,如果是个多线程程序,如何确保singleton的线程安全性。让人失望的是,让大家说线程的同步有几种方式时,基本上都能答上来,但灵活应用到具体的问题上时却答不上来了,sign

MFC到wxWidgets移植笔记(14)――消除代码中的平台差异
此系列的大纲是早就拟好的,写到这一篇还真有点犯难了。因为当初从Windows平台移到Linux平台下编译时,遇到了很多代码中的问题,可是没有记下来,现在再回想,有点无处下笔了。只能凑合着写个一两点:

1.gcc编译器好像只能处理ansi版本的代码文件。如果是unicode或者utf8,都会出现编译问题。并且,他不会根据你当前操作系统的默认语言识别代码文件中非ansi字符(比如中文)。所以,如果代码中,是绝对不能有中文的。这种问题有两个办法解决,一是参考我的前一篇笔记,将所有的中文字符移到非代码文件中,二是用Asicii编码来代替。比如:

wchar_t *t = L"中文";

就改成了:

byte t[] = {0x2d, 0x4e, 0x87, 0x65, 0, 0};
wprintf(L"%s\n", (wchar_t*)t);

2.所有的代码文件必须在文件结尾有一个空行。这是Linux编译器的要求,很怪异

3.Linux下面没有工程文件,所以某些配置需要在预定义头文件中用代码加上。比如如果编译带调试信息的,要加上:

#ifndef DEBUG
#define DEBUG