本文阐述了
wxWidgets
的由来,以及从
wxWidgets
的特点出发,说明了选择
wxWidgets
给我们带来的好处,并且通过一个典型的例子讨论了如何在
C++
中使用
wxWidgets
开发跨平台的软件。
什么是wxWidgets?
wxWidgets
是一个跨平台的软件开发包。它诞生于
1992
年,最初的名子是
wxWindows
,但由于
Microsoft
的抗议,在
2004
年改名为
wxWidgets
。它最初是被设计成跨平台的
GUI
软件开发包,但后来随着越来越多的人参与进来,为
wxWidgets
加入了许多非
GUI
的功能,如多线程
(MultiThread)
、网络
(Network)
等。并且从最初的只支持
C++
语言,逐渐发展成为支持数种语言(如
Python
、
Perl
、
C#
、
Basic
等)。因此,现在的
wxWidgets
已经不再是单纯的跨平台的
GUI
软件开发包,而是一个可以支持多种操作系统平台的能够在多种语言中使用的通用跨平台软件开发包。
由于
wxWidgets
最开始是为
C++
而设计的,因此,本文主要讨论了
wxWidgets
在
C++
中的使用。
为什么选择wxWidgets?
目前支持
C++
的软件开发包非常多,比较有名的除了
wxWidgets
外,还有一些其它的软件开发包,如
MFC
、
QT
、
ACE
等。即然有这么多开发包,那么我们为什么要使用
wxWidgets
呢?在给出答案之前,让我们首先来看一看上述的三种软件开发包的特性。
1.<span times="" new="" roman';="" font-size-adjust:="" none;="" font-stretch:="" normal"="" style="padding: 0px; margin: 0px;">
MFC
MFC
是
Microsoft
提供的软件开发包。
MFC
虽然十分强大,但它只能运行在
Windows
下运行。而且它是收费的。
2. QT
QT
是由Trolltech
公司开发的一套跨平台软件开发包。它和wxWidgets
类似,但是QT
只在linux
下免费,而在Windows
或Unix
下使用QT
要向Trolltech
公司支付版权费。
3. ACE
ACE
虽然是免费开源的,但是它没有提供
GUI
功能。
从以上三个软件开发包可以看出,它们虽然有各自的优势,但是它们或多或少地都会使开发受到限制。而使用wxWidgets将不会有以上所述的问题。wxWidgets和MFC、QT、 ACE的特性对比如表1所示。
从以上三个软件开发包可以看出,它们虽然有各自的优势,但是它们或多或少地都会使开发受到限制。而使用wxWidgets将不会有以上所述的问题。wxWidgets和MFC、QT、 ACE的特性对比如表1所示。
![](https://i-blog.csdnimg.cn/blog_migrate/8c78be96cc38f2ed83fda54e37b4657f.jpeg)
表1: wxWidgets和MFC、QT、ACE的特性对比表
注:其中免费中的“是
/
否”代表
QT
在
linux
平台上的
Free Edition
是免费的,而在
windows
和
unix
下使用
QT
是收费的。而开源中的“是
/
否”代表
QT
有一个基于
GPL
的开源版本,但要进行商业开发,需要使用它的商业版本。
使用wxWidgets编写程序
学习一种编程语言的最好方法就是用它去编写程序,学习
wxWidgets
也不例外。由于
wxWidgets
的主要功能是实现跨平台的
GUI
,因此,本文主要从
GUI
入手,讨论
wxWidgets
在
C++
中如何编写跨平台的应用程序
1. 应用程序类的建立
使用
wxWidgets
建立系统需要一个类来描述整个应用程序。这个类必须从
wxApp
类继承。
class
MyApp :
public
wxApp
//
应用程序类
{
public :
virtual bool OnInit(); // 在应用程序启动时调用,如果返回false,退出应用程序
};
{
public :
virtual bool OnInit(); // 在应用程序启动时调用,如果返回false,退出应用程序
};
这个类只覆盖了
wxApp
的一个虚方法
OnInit
。可以用这个方法在程序启动时做一些验证,如果验证失败,可以通过返回
false
退出应用程序。当然,由于这个函数是应用程序的入口点,所以建立主窗体的工作要在这个函数中完成。
2.
建立窗体类
wxWidgets
中关于窗体的类很多,如果要建立一般窗体的话,可以从
wxFrame
继承。
class
MyFrame :
public
wxFrame
//
窗体类
{
public :
MyFrame( const wxString & title); // 窗体的构造函数
};
{
public :
MyFrame( const wxString & title); // 窗体的构造函数
};
3.
向窗体中加入控件
在本文中向这个窗体加入了一个菜单条
(Menu Bar)
、一个状态条、一个
Panel
和一个按钮。一般我们会在主窗体的构造函数中加入这些控件。
MyFrame::MyFrame(
const
wxString
&
title) : wxFrame(NULL, wxID_ANY, title)
{
wxMenu * fileMenu = new wxMenu; // 建立“文件”菜单
wxMenu * helpMenu = new wxMenu; // 建立“帮助”菜单
// 向菜单中添加子项
helpMenu -> Append(wxID_ABOUT, _T( " 关于
\tF1
"
), _T(
"
显示关于对话框
"
));
fileMenu -> Append(wxID_EXIT, _T( " 退出\tAlt-X " ), _T( " 退出应用程序 " ));
wxMenuBar * menuBar = new wxMenuBar(); // 建立一个菜单条
menuBar -> Append(fileMenu, _T( " 文件 " )); // 将“文件”菜单加入到菜单条
menuBar -> Append(helpMenu, _T( " 帮助 " )); // 将“帮助”菜单加入到菜单条
SetMenuBar(menuBar); // 将菜单条放到窗体上
wxPanel * panel = new wxPanel( this ); // 建立一个Panel
wxButton * button = new wxButton(panel, wxID_ABOUT, " 关于 " , wxPoint( 20 , 20 ), wxSize( 50 , 30 )); // 建立一个Button
CreateStatusBar( 2 ); // 建立一个两栏的状态栏
SetStatusText(_T( " 欢迎使用wxWidgets! " )); // 设置状态栏的文本
{
wxMenu * fileMenu = new wxMenu; // 建立“文件”菜单
wxMenu * helpMenu = new wxMenu; // 建立“帮助”菜单
// 向菜单中添加子项
helpMenu -> Append(wxID_ABOUT, _T( " 关于
![](https://i-blog.csdnimg.cn/blog_migrate/9b8a8a44dd1c74ae49c20a7cd451974e.gif)
fileMenu -> Append(wxID_EXIT, _T( " 退出\tAlt-X " ), _T( " 退出应用程序 " ));
wxMenuBar * menuBar = new wxMenuBar(); // 建立一个菜单条
menuBar -> Append(fileMenu, _T( " 文件 " )); // 将“文件”菜单加入到菜单条
menuBar -> Append(helpMenu, _T( " 帮助 " )); // 将“帮助”菜单加入到菜单条
SetMenuBar(menuBar); // 将菜单条放到窗体上
wxPanel * panel = new wxPanel( this ); // 建立一个Panel
wxButton * button = new wxButton(panel, wxID_ABOUT, " 关于 " , wxPoint( 20 , 20 ), wxSize( 50 , 30 )); // 建立一个Button
CreateStatusBar( 2 ); // 建立一个两栏的状态栏
SetStatusText(_T( " 欢迎使用wxWidgets! " )); // 设置状态栏的文本
在数组
sample_xpm
中描述了
sample.ico
的属性和图标本身。如
X
代表红色;
o
代表黄色等。然后在源程序中通过
include “sample.xpm”
引用这个资源文件。要想从这个资源文件中装载图标。可使用
SetIcon(wxICON(sample)); wxICON
读取资源文件,而
SetIcon
将这个图标设置为
frame
的标题栏图标。要想将
ico
文件转换为这种资源文件,可使用一个免费软件XnView
进行转换。
5.
显示主窗体
显示主窗体非常简单,只需要将上面建立的
MyFrame
类实例化,并调用
wxFrame
的
Show
方法显示即可。这些代码可以写在
MyApp
类的
OnInit
方法中。
bool
MyApp::OnInit()
{
// 建立MyFrame类的实例
MyFrame * frame = new MyFrame(_T( " 第一个wxWidgets程序 " ));
frame -> Show( true ); // 显示主窗体
return true ; // 必须返回true,否则应用程序将退出
}
{
// 建立MyFrame类的实例
MyFrame * frame = new MyFrame(_T( " 第一个wxWidgets程序 " ));
frame -> Show( true ); // 显示主窗体
return true ; // 必须返回true,否则应用程序将退出
}
在以上代码中
Show
方法有一个参数,如果为
true
,则以模式窗口的形式显示,否则以非模式窗口的形式显示。
6.
向窗体中加入事件
到目前为止,这个程序的界面已经完成了,但还未响应任何事件,下面就详细阐述如何向这个应用程序中加入事件代码。
对于事件来说,一般都会由两部分组成。
对于事件来说,一般都会由两部分组成。
(1)
调用事件部分
当程序发生某个动作时,如点击按钮;选中某个控件,可能需要执行一段代码。而这段代码一般是由系统负责调用的,也就是说系统通过事件函数指针调用相应的代码。
(2)
事件函数本身
事件函数与普通函数一样,只不过它是在发生了事件之后,由系统调用的。
在
wxWidgets
中是通过事件哈希表
(Event Hash Table)
来进行事件处理的,即将相应的事件函数指针保存在一个哈希表中,然后当事件发生时,从这个哈希表中找到相应的事件函数指针,然后通过函数指针调用函数。在使用事件哈希表之前,必须定义它。由于定义哈希表非常复杂,而且每个需要处理事件的类都需要同样的代码,因此,
wxWidgets
为此定义了一个宏
DECLARE_EVENT_TABLE()
来定义哈希表。可将这个宏写在
MyFrame
类的任何位置。它相当于将以下语句放到了
MyFrame
类中。
private
:
static const wxEventTableEntry sm_eventTableEntries[];
protected :
static const wxEventTable sm_eventTable;
virtual const wxEventTable * GetEventTable() const ;
static wxEventHashTable sm_eventHashTable;
virtual wxEventHashTable & GetEventHashTable() const ;
static const wxEventTableEntry sm_eventTableEntries[];
protected :
static const wxEventTable sm_eventTable;
virtual const wxEventTable * GetEventTable() const ;
static wxEventHashTable sm_eventHashTable;
virtual wxEventHashTable & GetEventHashTable() const ;
其中静态数组变量
sm_eventTableEntries
保存了
MyFrame
类中的所有的事件信息。
上面的代码声明了处理事件哈希表的一些方法,即然声明了,就得实现。由于实现代码也都一样,因此,
wxWidgets
也为实现这些方法定义了一组宏。实现这些方法的宏如下所示。
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(wxID_EXIT, MyFrame::OnQuit)
EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
EVT_BUTTON(wxID_ABOUT, MyFrame::OnAbout)
END_EVENT_TABLE()
EVT_MENU(wxID_EXIT, MyFrame::OnQuit)
EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
EVT_BUTTON(wxID_ABOUT, MyFrame::OnAbout)
END_EVENT_TABLE()
其中
BEGIN_EVENT_TABLE(…)
实现了上面定义的方法,以及初始化了静态变量
sm_eventTable
。后面两个
EVT_MENU
和一个
EVT_BUTTON
宏初始化了静态变量
sm_eventTableEntries
,即将这两个事件函数的指针
(button
和
about
菜单使用一个事件函数
OnAbout)
和控件
ID
保存在
sm_eventTableEntries
中,最后的
END_EVENT_TABLE()
宏做为一个空的事件函数指针赋给了
sm_eventTableEntries
,这有些象
C
语言中处理字符串,将最后一个字符赋为
’"0’
,这样就可以知道哪是结尾了。
向窗体中加入事件的最后一步是声明和实现事件函数。在本例中声明了两个事件函数。
向窗体中加入事件的最后一步是声明和实现事件函数。在本例中声明了两个事件函数。
void
OnQuit(wxCommandEvent
&
event
);
void OnAbout(wxCommandEvent & event );
void OnAbout(wxCommandEvent & event );
可以将这两个函数声明放到
MyFrame
中的任何位置。下面是它们的实现代码。
void
MyFrame::OnQuit(wxCommandEvent
&
WXUNUSED(
event
))
{
Close( true );
}
void MyFrame::OnAbout(wxCommandEvent & WXUNUSED( event ))
{
wxString msg;
msg.Printf( _T( " 这是一个关于对话框的例子.\n " )
_T( " 欢迎使用 %s " ), wxVERSION_STRING);
wxMessageBox(msg, _T( " ¹关于 " ), wxOK | wxICON_INFORMATION, this );
}
{
Close( true );
}
void MyFrame::OnAbout(wxCommandEvent & WXUNUSED( event ))
{
wxString msg;
msg.Printf( _T( " 这是一个关于对话框的例子.\n " )
_T( " 欢迎使用 %s " ), wxVERSION_STRING);
wxMessageBox(msg, _T( " ¹关于 " ), wxOK | wxICON_INFORMATION, this );
}
其中
OnQuit
函数调用
Close(true)
关闭
MyFrame
,由于
MyFrame
是主窗体,因此,在
MyFrame
关闭后,应用程序也随之关闭了。
OnAbout
使用
wxMessageBox
函数弹出一个信息对话框。
运行程序
到目前为止,这个程序的代码已经基本完成了,但是在前面曾说过,
MyApp
中的
OnInit
方法在应用程序启动时执行,那么是谁调用了
OnInit
方法呢?答案当然是
wxWidgets
。
wxWidgets
为了调用这个方法,提供了一个宏
IMPLEMENT_APP(…)
,这个宏有一个参数,需要将
MyApp
做为参数传入。即
IMPLEMENT_APP(MyApp)
。这个宏相当于一个
WinMain
函数(和控制台程序的
main
函数类似),即在
WinMain
函数中调用了
MyApp
中的
OnInit
函数。在加入这个宏后,就可使用一个
C++
编译器将以上的源程序编译生成
exe
文件了。应用程序的界面如图
1
、图
2
所示。
![](https://i-blog.csdnimg.cn/blog_migrate/a38e19d7e7734f902aa6469286b3f200.jpeg)
图1 Windows下的程序界面
![](https://i-blog.csdnimg.cn/blog_migrate/1df4f5599b9885cb617a073921af1011.jpeg)
图2 Linux下的程序界面
st1":*{behavior:url(#ieooui) }
注:在
windows
下使用的
wxWidgets
版本是
wxWidgets2.6.2
,在
linux
下使用的
wxWidgets
版本号是
wxWidgets2.6.3
,因此,在
windows
和
linux
下的
wxVERSION_STRING
值不一样。
wxWidgets的优势和不足
通过上面的介绍,相信读者已经对如何使用
wxWidgets
编写
GUI
程序有了一定的了解。
wxWidgets
在开发跨平台的软件上有着许多其它软件开发包不具备的优势,下面就总结一下
wxWidgets
所具有的优势。
1. 跨平台
wxWidgets 支持非常多的操作系统平台,如 Windows 、 Linux 、 Unix 等。
1. 跨平台
wxWidgets 支持非常多的操作系统平台,如 Windows 、 Linux 、 Unix 等。
2.
丰富的组件
wxWidgets
拥有上百个组件可供用户选择。有了这些组件,将会给我们带来更加丰富的用户体验。
3.
支持多种语言
wxWidgets
不仅可以在
C++
中使用,而且也可以在其它语言中使用,这些语言包括
python
、
perl
、
c#
等。
4.<span times="" new="" roman';="" font-size-adjust:="" none;="" font-stretch:="" normal"="" style="padding: 0px; margin: 0px;">
使用本地控制
从上面给出的两个应用程序界面可以看出,在
Windows
和
Linux
下运行这个应用程序保持了各自的风格。这是因为
wxWidgets
采用了本地的
API
,而不象其它的跨平台库去模拟它们。因此,使用
wxWidgets
开发和在
Windows
下使用
Win32 API
或在
Linux
下使用
GTK
开发没有什么区别。
5.
免费开源
这个世界上免费的开发包很多,强大的开发包也很多,当然,开源的开发包就更多了。但是要想同时满足这三点:免费、开源、强大,又同时具有本地程序一样的性能,恐怕
wxWidgets
是唯一的选择,至少是最佳的选择。
相信上面关于
wxWidgets
的
5
个优势已经足以成为我们选择它的理由了。也就是说,如果选择
wxWidgets
,不仅可以获得强大的功能、卓越的性能,而且您不必为此付一分钱。当然,人无完人、物无完物。
wxWidgets
也并不是没有缺点。下面就说一下
wxWidgets
的不足之处。
1. IDE
支持不够
对于
wxWidgets
来说,最大的优点也就是它最大的缺点。由于
wxWidgets
所提供的组件很多,但到现在为止还没有一个强大的
IDE
来支持它,这将给大型系统的开发带来麻烦。
2.
对双字节字符的支持不理想
wxWidgets
中的有些组件,如
xml
组件,无法识别双字节字符,如汉字会被认为是非法字符而无法装载
xml
文档。
综合上述,
wxWidgets
从总体上来说还是一个非常强大的跨平台软件开发包。如果您没有足够的资金来购买商业的软件开发包,也许
wxWidgets
是最好的选择。虽然
wxWidgets
也有一些不足,但这并不能阻碍
wxWidgets
的发展。
wxWidgets
的功能还很多,由于篇幅所限,本文只能从一个简单的例子来讨论如何用
wxWidgets
来开发一个跨平台的
GUI
程序,如果读者对
wxWidgets
感性趣,可以访问
http://www.wxWidgets.org
获得更多的信息。
要在Linux下使用Eclipse开发wxWidgets程序,请读者参阅 《快速配置Linux + Eclipse + wxWidgets开发环境 》