准备
参考
- https://wiki.wxwidgets.org/Microsoft_Visual_C++_NuGet
- https://wiki.wxwidgets.org/Tools#Rapid_Application_Development_.2F_GUI_Builders
- https://docs.wxwidgets.org/3.2/
- https://docs.wxwidgets.org/latest/overview_helloworld.html
- https://wizardforcel.gitbooks.io/wxwidgets-book/content/0.html 中文教程
环境搭建
- VS2019+
- GUI设计工具 wxformbuilder
nuget 包
如果使用nuget提供的wxWidgets包,则不需要配置其他东西,就可以使用了。
- nuget wxWidgets
- vs project 子系统设置为
窗口 (/SUBSYSTEM:WINDOWS)
- 然后,修改源代码为demo 代码编译即可。
自编译or官网自下载
如果使用的是自己编译的wxWidgets库,或者从官方网站下载的预编译包,则需要手动配置:
- nuget wxWidgetsTemplate
- wx 官网 下载 header, dev。假设放在
C:\MyCode\VSBase\wxDemo\third\
文件夹中。 - VS 中project配置:
-
wxMSVC_VERSION 配置最为关键,对应下载的预编译库的vc版本。
-
它对应
lib\vc14x_x64_dll\
这个文件夹的名字一部分14x
-
如果是自编译的wxWidgets库,这里需要查看错误信息进行调试修改。
-
- vs project 子系统设置为
窗口 (/SUBSYSTEM:WINDOWS)
- 然后,修改源代码为demo 代码编译即可。
Demo-hello world
// wxDemo.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <wx/wxprec.h>
// wxWidgets "Hello World" Program
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
class MyFrame : public wxFrame
{
public:
MyFrame();
private:
void OnHello(wxCommandEvent& event);
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
};
enum
{
ID_Hello = 1
};
wxIMPLEMENT_APP(MyApp);
bool MyApp::OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show(true);
return true;
}
MyFrame::MyFrame()
: wxFrame(NULL, wxID_ANY, "Hello World")
{
wxMenu* menuFile = new wxMenu;
menuFile->Append(ID_Hello, "&Hello...\tCtrl-H",
"Help string shown in status bar for this menu item");
menuFile->AppendSeparator();
menuFile->Append(wxID_EXIT);
wxMenu* menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT);
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, "&File");
menuBar->Append(menuHelp, "&Help");
SetMenuBar(menuBar);
CreateStatusBar();
SetStatusText("Welcome to wxWidgets!");
Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}
void MyFrame::OnExit(wxCommandEvent& event)
{
Close(true);
}
void MyFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox("This is a wxWidgets Hello World example",
"About Hello World", wxOK | wxICON_INFORMATION);
}
void MyFrame::OnHello(wxCommandEvent& event)
{
wxLogMessage("Hello world from wxWidgets!");
}
//int main()
//{
// std::cout << "Hello World!\n";
//}
核心理念
整体概述
和大多数现代的GUI编程框架一样,wxWidgets大量使用了面向对象编程的概念。每一个窗口都是一个C++的对象。
wxWidgets公用API层,各个平台发行版,用于各个平台的API和操作系统层:
wxMSW
面向windows平台,也可以使用Wine的库进行编译,并且可以被配置成在WinCE上运行wxGTK
可以使用GTK的1.x或者2.x版本,支持所有可以运行X11和GTK的类Unix平台wxX11
使用了wxUniversal的窗口控件集,直接运行在Xlib上。这使得它很适合嵌入式系统,当然它也可以运行在那些不喜欢GTK+的桌面系统上。它支持所有可以运行X11的Unix系统,当然wxX11并不像wxGTK那样完善。wxMotif
可以在大多数拥有 Motif, OpenMotif, 或者Lesstif的Unix系统上. 既然连Sun自己都正准备把它的窗口控件集转向GNOME和GTK+,对于大多数开发者来说,Motif并不是一个很可靠的选择.wxMac
为Mac OS 9 (9.1以后的版本)和Mac OS X (10.2.8以后的版本)准备的.wxCocoa
这是一个正在进行中的版本, 它使用Mac OS X的Cocoa API. 虽然Carbon和Cocoa的功能很相似,但是这个版本有可能会支持除Mac以外的其它支持GNUStep的操作系统。wxWinCE
封装了WindowsCE平台上的各种不同的开发包,包括Pocket PC和Smartphone等. 这个版本包含在wxMSW的Win32版本中。wxPalmOS
为Palm OS 6准备的。到作者写这本书的时候为止,这个版本还处在很初级的阶段wxOS2
wxOS2是一个由别人维护的用于OS/2或者eComStation的版本wxMGL
这个版本使用了SciTech公司的底层图形库,窗口控件使用的是wxUniversal中的版本
主要维护的平台还是 wxMSW, wxGTK, wxMac 三个port.
当前资料比较老,新的参考:wxWidgets: Introduction
核心类
每一个wxWidgets程序都需要定义一个wxApp类的子类,并且需要并且只能构造一个这个类的实例,这个实例控制着整个程序的执行。
- 这个继承自 wxApp的子类
MyApp
至少需要定义一个OnInit函数 - 这个OnInit函数中,你通常应该创建至少一个窗口,如果这个函数返回真,wxWidgets将开始事件循环用来处理用户输入并且在必要的情况下处理这些输入。如果OnInit函数返回假, wxWidgets将会释放它内部已经分配的资源,然后结束整个程序的运行。
- 需要告诉wxWidgets需要创建哪一个App类的实例,所以你还需要增加一个宏
IMPLEMENT_APP(MyApp)
- 使用时候,可以使用默认创建的全局对象 wxTheApp, 但是需要申明
DECLARE_APP(MyApp)
MyApp实现:
#include "wx/wx.h"
bool MyApp::OnInit()
{
MyFrame *frame = new MyFrame(wxT("Minimal wxWidgets App"));
frame->Show(true);
return true;
}
IMPLEMENT_APP(MyApp)
DECLARE_APP(MyApp)
Frame 实现:
#include "wx/wx.h"
class MyFrame : public wxFrame
{
public:
MyFrame(const wxString& title);
void OnQuit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
private:
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
EVT_MENU(wxID_EXIT, MyFrame::OnQuit)
END_EVENT_TABLE()
#include "mondrian.xpm"
MyFrame::MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
SetIcon(wxIcon(mondrian_xpm));
wxMenu *fileMenu = new wxMenu;
wxMenu *helpMenu = new wxMenu;
helpMenu->Append(wxID_ABOUT, wxT("&About...\tF1"),
wxT("Show about dialog"));
fileMenu->Append(wxID_EXIT, wxT("E&xit\tAlt-X"),
wxT("Quit this program"));
wxMenuBar *menuBar = new wxMenuBar();
menuBar->Append(fileMenu, wxT("&File"));
menuBar->Append(helpMenu, wxT("&Help"));
SetMenuBar(menuBar);
CreateStatusBar(2);
SetStatusText(wxT("Welcome to wxWidgets!"));
}
void MyFrame::OnAbout(wxCommandEvent& event)
{
wxString msg;
msg.Printf(wxT("Hello and welcome to %s"),
wxVERSION_STRING);
wxMessageBox(msg, wxT("About Minimal"),
wxOK | wxICON_INFORMATION, this);
}
void MyFrame::OnQuit(wxCommandEvent& event)
{
Close();
}
事件处理
每一个wxEvtHandler的派生类,例如frame,按钮,菜单以及文档等,都会在其内部维护一个事件表,用来告诉wxWidgets事件和事件处理过程的对应关系。所有继承自wxWindow的窗口类,以及应用程序类都是wxEvtHandler的派生类.
要创建一个静态的事件表(意味着它是在编译期间创建的),你需要下面几个步骤:
- 定义一个直接或者间接继承自wxEvtHandler的类.
- 为每一个你想要处理的事件定义一个处理函数。
- 在这个类中使用DECLARE_EVENT_TABLE声明事件表。
- 在.cpp文件中使用使用BEGIN_EVENT_TABLE和END_EVENT_TABLE实现一个事件表。
- 在事件表的实现中增加事件宏,来实现从事件到事件处理过程的映射。
事件表 or 动态connect
所有的事件处理函数拥有相同的形式。他们的返回值都是void,他们都不是虚函数,他们都只有一个事件对象作为参数。
不同的事件参数类型可以调用的方法也不相同。
class MyFrame : public wxFrame
{
public:
...
void OnSize(wxSizeEvent& event);
void OnButtonOK(wxCommandEvent& event);
private:
DECLARE_EVENT_TABLE()
};
wxButton* button = new wxButton(this, wxID_OK, wxT("OK"), wxPoint(200, 200));
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
...
EVT_SIZE ( MyFrame::OnSize)
EVT_BUTTON (wxID_OK, MyFrame::OnButtonOK)
END_EVENT_TABLE()
事件处理常见笔记
event.skip
跳过处理wxWindow::PushEventHandler
压入新的事件wxWindow::PopEventHandler
获取第一个事件,参数传true还可以删除它- 支持
connect
方式,类似QT一样处理事件表.
// 不使用DECLARE_EVENT_TABLE来声明一个事件表
frame->Connect( wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MyFrame::OnQuit) );
frame->Connect( wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MyFrame::OnAbout) );
wxID
在代码中,我们使用了很多 wxID_ANY, wxID_OK ...
标识,他们用来唯一确定窗口ID,但是这个ID只要求在Frame内部唯一即可。所以,相同ID在不同Frame中是可以重复使用的。
下表列举了wxWidgets提供的一些标准的标识符。
你应该尽可能的使用这些标识符,这是由于下面一些原因。
某些系统会给特定的标识符提供一些小图片(例如GTK+系统上的OK和取消按钮)或者提供默认的处理函数(例如自动产生wxID_CANCEL事件来响应Escape键)。在 Mac OS X系统上,wxID_ABOUT, wxID_PREFERENCES和wxID_EXIT菜单项也有特别的处理。
另外一些wxWidgets的控件也会自动处理标识符为 wxID_COPY, wxID_PASTE或 wxID_UNDO等的一些菜单或者按钮的命令。
标识符名称 | 描述 |
---|---|
wxID_ANY | 让wxWidgets自动产生一个标识符 |
wxID_LOWEST | 最小的系统标识符值 (4999) |
wxID_HIGHEST | 最大的系统标识符值 (5999) |
wxID_OPEN | 打开文件 |
wxID_CLOSE | 关闭窗口 |
wxID_NEW | 新建窗口文件或者文档 |
wxID_SAVE | 保存文件 |
wxID_SAVEAS | 文件另存为(应该弹出文件位置对话框) |
wxID_REVERT | 恢复文件在磁盘上的状态 |
wxID_EXIT | 退出应用程序 |
wxID_UNDO | 撤消最近一次操作 |
wxID_REDO | 重复最近一次操作 |
wxID_HELP | 帮助 (例如对话框上的帮助按钮可以用这个标识符) |
wxID_PRINT | 打印 |
wxID_PRINT_SETUP | 打印设置 |
wxID_PREVIEW | 打印预览 |
wxID_ABOUT | 显示一个用来描述整个程序的对话框 |
wxID_HELP_CONTENTS | 显示上下文帮助 |
wxID_HELP_COMMANDS | 显示应用程序命令 |
wxID_HELP_PROCEDURES | 显示应用程序过程 |
wxID_HELP_CONTEXT | 未使用 |
wxID_CUT | 剪切 |
wxID_COPY | 复制到剪贴板 |
wxID_PASTE | 粘贴 |
wxID_CLEAR | 清除 |
wxID_FIND | 查找 |
wxID_DUPLICATE | 复制 |
wxID_SELECTALL | 全选 |
wxID_DELETE | 删除 |
wxID_REPLACE | 覆盖 |
wxID_REPLACE_ALL | 全部覆盖 |
wxID_PROPERTIES | 查看属性 |
wxID_VIEW_DETAILS | 列表框中的按照详细信息方式显示 |
wxID_VIEW_LARGEICONS | 列表框按照大图标的方式显示 |
wxID_VIEW_SMALLICONS | 列表框中按照小图标的方式显示 |
wxID_VIEW_LIST | 列表框中按照列表的的方式显示 |
wxID_VIEW_SORTDATE | 按照日期排序 |
wxID_VIEW_SORTNAME | 按照名称排序 |
wxID_VIEW_SORTSIZE | 按照大小排序 |
wxID_VIEW_SORTTYPE | 按照类型排序 |
wxID_FILE1 to wxID_FILE9 | 显示最近使用的文件 |
wxID_OK | 确定 |
wxID_CANCEL | 取消 |
wxID_APPLY | 应用变更 |
wxID_YES | YES |
wxID_NO | No |
wxID_STATIC | 静态文本或者静态图片可以用这个标识符 |
wxID_FORWARD | 向前 |
wxID_BACKWARD | 向后 |
wxID_DEFAULT | 恢复默认设置 |
wxID_MORE | 显示更多选项 |
wxID_SETUP | 显示一个设置对话框 |
wxID_RESET | 重置所有选项 |
wxID_CONTEXT_HELP | 显示上下文帮助 |
wxID_YESTOALL | 全部选是 |
wxID_NOTOALL | 全部选否 |
wxID_ABORT | 中止当前操作 |
wxID_RETRY | 重试 |
wxID_IGNORE | 忽略错误 |
wxID_UP | 向上 |
wxID_DOWN | 向下 |
wxID_HOME | 首页 |
wxID_REFRESH | 刷新 |
wxID_STOP | 停止正在进行的操作 |
wxID_INDEX | 显示一个索引 |
wxID_BOLD | 加粗显示 |
wxID_ITALIC | 斜体显示 |
wxID_JUSTIFY_CENTER | 居中 |
wxID_JUSTIFY_FILL | 格式 |
wxID_JUSTIFY_RIGHT | 右对齐 |
wxID_JUSTIFY_LEFT | 左对齐 |
wxID_UNDERLINE | 下划线 |
wxID_INDENT | 缩进 |
wxID_UNINDENT | 反缩进 |
wxID_ZOOM_100 | 放大到100% |
wxID_ZOOM_FIT | 缩放到整页 |
wxID_ZOOM_IN | 放大 |
wxID_ZOOM_OUT | 缩小 |
wxID_UNDELETE | 反删除 |
wxID_REVERT_TO_SAVED | 恢复到上次保存的状态 |
为了避免你自己定义的标识符和这些预定义的标识符重复,你可以使用大于wxID_HIGHEST的标识符或者小于wxID_LOWEST的标识符。
自定义事件
假如我们要实现一个新的控件wxFontSelectorCtrl,这个控件将可以显示字体的预览。用户通过点击字体的预览来弹出一个对话框让用户可以更改字体。应用程序也许想拦截这个字体改变事件,因此我们在我们的底层鼠标消息处理过程中将会给应用程序发送一个自定义的字体改变事件。
因此我们需要定义一个新的事件wxFontSelectorCtrlEvent.应用程序可以通过事件映射宏 EVT_FONT_SELECTION_CHANGED(id, func)来增加对这个事件的处理。我们还需要给这个事件定义一个事件类型: wxEVT_COMMAND_FONT_SELECTION_CHANGED.
// 新建一个 wxCommandEvent派生类,这里使用 wxNotifyEvent 派生
class wxFontSelectorCtrlEvent : public wxNotifyEvent
{
public:
wxFontSelectorCtrlEvent(wxEventType commandType = wxEVT_NULL,int id = 0): wxNotifyEvent(commandType, id) {}
wxFontSelectorCtrlEvent(const wxFontSelectorCtrlEvent& event): wxNotifyEvent(event) {}
virtual wxEvent *Clone() const { return new wxFontSelectorCtrlEvent(*this); }
DECLARE_DYNAMIC_CLASS(wxFontSelectorCtrlEvent);
};
// 事件回调函数类型声明
typedef void (wxEvtHandler::*wxFontSelectorCtrlEventFunction)(wxFontSelectorCtrlEvent&);
// 事件ID声明
BEGIN_DECLARE_EVENT_TYPES()
DECLARE_EVENT_TYPE(wxEVT_COMMAND_FONT_SELECTION_CHANGED, 801)
END_DECLARE_EVENT_TYPES()
// 关联关系声明
#define EVT_FONT_SELECTION_CHANGED(id, fn) DECLARE_EVENT_TABLE_ENTRY( \
wxEVT_COMMAND_FONT_SELECTION_CHANGED, id, -1, (wxObjectEventFunction) (wxEventFunction) \
(wxFontSelectorCtrlEventFunction) & fn, (wxObject *) NULL ),
// 使用
wxFontSelectorCtrlEvent event(wxEVT_COMMAND_FONT_SELECTION_CHANGED, GetId());
event.SetEventObject(this);
GetEventHandler()->ProcessEvent(event);
BEGIN_EVENT_TABLE(MyDialog, wxDialog)
EVT_FONT_SELECTION_CHANGED(ID_FONTSEL, MyDialog::OnChangeFont)
END_EVENT_TABLE()
void MyDialog::OnChangeFont(wxFontSelectorCtrlEvent& event)
{
// 字体已经更改了,可以作一些必要的处理。
...
}
字符编码
由于 wxWidgets 3.0 Unicode 支持始终处于启用状态,并且仍然可以在没有 Unicode 支持的情况下构建库,因此不建议再使用它,并且将在不久的将来停止支持。
wxWidgets 在所有系统下默认使用 wxString 实现中的系统wchar_t
。这意味着在内部仅使用 Unicode 字符串,并且在 Microsoft Windows 下使用 Unicode 系统 API。
但是您仍然可以继续使用窄字符串(即当前区域设置编码的 char*
)字符串。任何 wxWidgets 函数都接受任一类型的参数,因为这两种字符串都隐式转换为 wxString
在wxString中提供了很多和编码有关的转换函数,支持从不同的数据源转换,实际上建议Window上使用Unicode, Linux可以考虑重新编译为UTF8使用(不是强制,统一Unicode对跨平台很好)。
注意:与 wxWidgets 一起使用的窄字符串_始终_假定为当前 locale 编码。
wxMessageBox("Hello, world!");
wxMessageBox(L"Salut \u00E0 toi!"); // ok
wxMessageBox(wxString::FromUTF8("Salut \xC3\xA0 toi!"));
窗口
基本窗口类
下面的这些基本的窗口类实现了一些最基本的功能。这些类主要是用来作为别的类型的基类以生成更实用的派生类。
- wxWindow. 这是所有窗口类的基类。
- wxControl. 所有控件(比如wxButton)的基类.
- wxControlWithItems. 是那些拥有多个子项目的控件的基类.
顶层窗口类
顶层窗口类通常指那些独立的位于桌面上的类。
- wxFrame. 一个可以包含其他窗口,并且大小可变的窗口类。
- wxMDIParentFrame. 是一个可以管理其他Frame类的类.
- wxMDIChildFrame. 是一个可以被其父窗口管理的frame类.
- wxDialog. 是一种可变大小的用于给用户提供选项的窗口类.
- wxPopupWindow. 是一种暂态的只有很少修饰的顶层窗口.
容器窗口类
容器窗口类可以管理其他窗口
- wxPanel. 这是一个给其它窗口提供布局的窗口.
- wxNotebook. 可以实用TAB页面进行切换的窗口.
- wxScrolledWindow. 可以有滚动条的窗口.
- wxSplitterWindow. 可以管理两个子窗口的一种特殊窗口类.
非静态控件窗口类
这些控件是用户可以操作或者编辑的。
- wxButton. 一种拥有一个标签的按钮控件.
- wxBitmapButton. 一种拥有图片和标签的按钮控件.
- wxChoice. 拥有一个下拉列表的选择控件.
- wxComboBox. 拥有一组选项的可编辑的选择控件.
- wxCheckBox. 拥有一个复选框的控件,复选框有选中和未选中两种状态.
- wxListBox. 拥有一组可选择的字符串项目的列表框.
- wxRadioBox. 拥有一组选项的单选框.
- wxRadioButton. 单选框.
- wxScrollBar. 滚动条控件。
- wxSpinButton. 一个拥有增加和减小两个选项的按钮.
- wxSpinCtrl. 拥有一个文本编辑框和一个wxSpinButton用来编辑整数.
- wxSlider. 这个控件用来在一个固定的范围内选择一个整数.
- wxTextCtrl. 单行或者多行的文本编辑框.
- wxToggleButton. 两态按钮.
静态控件
这些控件提供不能被最终用户编辑的静态信息
- wxGauge. 用来显式数量的控件.
- wxStaticText. 文字标签控件.
- wxStaticBitmap. 用来显示一幅静态图片.
- wxStaticLine. 用来显式静态的一行.
- wxStaticBox. 用来在别的控件周围显示一个静态的方框.
菜单
菜单是一种包含一组命令列表的窗口
控件条
控件条通常在Frame窗口中使用,用来为信息或者命令的访问提供快捷操作
- wxMenuBar. wxFrame上的菜单条.
- wxToolBar. 工具条.
- wxStatusBar. 状态条用来在程序运行过程中显示运行期信息.
绘制
在wxWidgets中,所有的绘画相关的动作,都是由设备上下文完成的。
每一个设备上下文都是wxDC的一个派生类。
从来就没有直接在窗口上绘画这种事情,每次在窗口上绘画,都要先创建一个窗口绘画设备上下文,然后在这个上下文上绘画。
设备上下文还可以通过SetClippingRegion函数指定一个区域,这个区域以外的部分将不被显示,可以使用 DestroyClippingRegion函数清除设备上下文当前指定的区域。
下面列出了你可以使用的设备上下文:
- wxClientDC. 用来在一个窗口的客户区绘画。
- wxBufferedDC. 用来代替wxClientDC来进行双缓冲区绘画。
- wxWindowDC. 用来在窗口的客户区和非客户区(比如标题栏)绘画.这个设备上下文极少使用而且也不是每个平台都支持。
- wxPaintDC. 仅用在重绘事件的处理函数中,用来在窗口的客户区绘画。
- wxBufferedPaintDC. 和wxPaintDC类似,不过采用双缓冲区进行绘画。
- wxScreenDC. 用来直接在屏幕上绘画。
- wxMemoryDC. 用来直接在图片上绘画。
- wxMetafileDC. 用来创建一个图元文件(只支持Windows和Mac OS X).
- wxPrinterDC. 用来在打印机上绘画。
- wxPostScriptDC. 用来在PostScript文件上或者在支持PostScript的打印机上绘画。
//
BEGIN_EVENT_TABLE(MyWindow, wxWindow)
EVT_MOTION(MyWindow::OnMotion)
END_EVENT_TABLE()
void MyWindow::OnMotion(wxMouseEvent& event)
{
if (event.Dragging())
{
wxClientDC dc(this);
wxPen pen(*wxRED, 1); // red pen of width 1
dc.SetPen(pen);
dc.DrawPoint(event.GetPosition());
dc.SetPen(wxNullPen);
}
}
// 替代wxClientDC的方法是使用wxBufferedDC,后者将你的所有绘画的结果保存在内存中,然后当自己被释放的时候一次性把所有的内容传输到窗口上
窗口类通常会收到两种和绘画相关的事件:
- wxPaintEvent事件用来绘制窗口客户区的主要图形,而wxEraseEvent事件则用来通知应用程序擦除背景。
- 如果你只拦截并处理了wxPaintEvent事件,则默认的wxEraseEvent事件处理函数会用最近一次的 wxWindow::SetBackgroundColour函数调用设置的颜色或者别的合适的颜色清除整个背景。
BEGIN_EVENT_TABLE(MyWindow, wxWindow)
EVT_ERASE_BACKGROUND(MyWindow::OnErase)
EVT_PAINT(MyWindow::OnPaint)
END_EVENT_TABLE()
void MyWindow::OnErase(wxEraseEvent& event)
{
wxClientDC* clientDC = NULL;
if (!event.GetDC())
clientDC = new wxClientDC(this);
wxDC* dc = clientDC ? clientDC : event.GetDC() ;
wxSize sz = GetClientSize();
wxEffects effects;
effects.TileBitmap(wxRect(0, 0, sz.x, sz.y), *dc, m_bitmap);
if (clientDC)
delete clientDC;
}
void MyWindow::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxRED_BRUSH);
// 获取窗口大小
wxSize sz = GetClientSize();
// 要绘制的矩形的大小
wxCoord w = 100, h = 50;
// 将我们的矩形设置在窗口正中,
// 但是不为负数的位置
int x = wxMax(0, (sz.xw)/2);
int y = wxMax(0, (sz.yh)/2);
wxRect rectToDraw(x, y, w, h);
// 只有在需要的时候才重画以便提高效率
if (IsExposed(rectToDraw))
DrawRectangle(rectToDraw);
}
减少闪烁
一个减少闪烁的方法,是把背景和前景统一在窗口重画事件处理函数中,而不是将它们分开处理,配合 wxBufferedPaintDC,那么所有的绘画动作在完成之前都是在内存中进行的,这样在窗口被重绘之前你将看不到窗口背景被更新。
你需要增加一个空的背景擦除事件处理函数,并且使用SetBackgroundStyle函数设置背景类型为wxBG_STYLE_CUSTOM以便告诉某些系统不要自动擦除背景。
为了提高性能,当你使用wxBufferedPaintDC时,你可以维护一个足够大的(比如屏幕大小的)位图,然后将其传递给 wxBufferedPaintDC的构造函数作为第二个参数,这可是避免每次使用wxBufferedPaintDC的时候创建和释放一个位图。
#include "wx/dcbuffer.h"
BEGIN_EVENT_TABLE(MyCustomCtrl, wxScrolledWindow)
EVT_PAINT(MyCustomCtrl::OnPaint)
EVT_ERASE_BACKGROUND(MyCustomCtrl::OnEraseBackground)
END_EVENT_TABLE()
//重画事件处理函数
void MyCustomCtrl::OnPaint(wxPaintEvent& event)
{
wxBufferedPaintDC dc(this);
// 平移设备座标以便我们不需要关心当前滚动窗口的位置
PrepareDC(dc);
// 在重画绘制函数中绘制背景
PaintBackground(dc);
// 然后绘制前景
...
}
/// 绘制背景
void MyCustomCtrl::PaintBackground(wxDC& dc)
{
wxColour backgroundColour = GetBackgroundColour();
if (!backgroundColour.Ok())
backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE);
dc.SetBrush(wxBrush(backgroundColour));
dc.SetPen(wxPen(backgroundColour, 1));
wxRect windowRect(wxPoint(0, 0), GetClientSize());
//我们需要平移当前客户区矩形的座标以便将其转换成相对于整个滚动窗口而不是当前窗口的座标
//因为在前面我们已经对设备上下文进行了PrepareDC的动作。
CalcUnscrolledPosition(windowRect.x, windowRect.y, &windowRect.x, &windowRect.y);
dc.DrawRectangle(windowRect);
}
// 空函数 只为了防止闪烁
void MyCustomCtrl::OnEraseBackground(wxEraseEvent& event)
{
}
Screen
使用wxScreenDC可以在整个屏幕的任何位置绘画。
wxBitmap GetScreenShot()
{
wxSize screenSize = wxGetDisplaySize();
wxScreenDC dc;
wxBitmap bitmap(screenSize.x, screenSize.y);
wxMemoryDC memDC;
memDC.SelectObject(bitmap);
memDC.Blit(0, 0, screenSize.x, screenSize.y, & dc, 0, 0);
memDC.SelectObject(wxNullBitmap);
return bitmap;
}
wxPrinterDC用来实现打印机接口。在windows和Mac平台上,这个接口实现到标准打印接口的映射。在其它类Unix系统上,没有标准的打印接口,因此需要使用wxPostScriptDC代替(除非打开了Gnome打印支持,参考接下来的小节,“在类Unix系统上使用 GTK+进行打印”)。
一个简单的创建wxPrinterDC设备上下文的方法是显示一个wxPrintDialog对话框,在用户选择各种参数以后,使用wxPrintDialog::GetPrintDC的方法获取一个对应的wxPrinterDC对象。作为更高级的用法,你可以从 wxPrintout定义一个自己的派生类,以便更精确定义打印以及打印预览的行为,然后把它的一个实例传递给wxPrinter对象(在后面小节中还会详细介绍)。
// 一个全局变量用来存储打印相关的设置信息
wxPrintDialogData g_printDialogData;
// 用户从主菜单中选择打印命令以后
void MyFrame::OnPrint(wxCommandEvent& event)
{
wxPrinter printer(& g_printDialogData);
MyPrintout printout(wxT("My printout"));
if (!printer.Print(this, &printout, true))
{
if (wxPrinter::GetLastError() == wxPRINTER_ERROR)
wxMessageBox(wxT("There was a problem printing.\nPerhaps your current printer
is not set correctly?"), wxT("Printing"), wxOK);
else
wxMessageBox(wxT("You cancelled printing"),
wxT("Printing"), wxOK);
}
else
{
(*g_printDialogData) = printer.GetPrintDialogData();
}
}
// 用户选择打印预览菜单命令
void MyFrame::OnPreview(wxCommandEvent& event)
{
wxPrintPreview *preview = new wxPrintPreview(
new MyPrintout, new MyPrintout,
& g_printDialogData);
if (!preview->Ok())
{
delete preview;
wxMessageBox(wxT("There was a problem previewing.\nPerhaps your current printer is
not set correctly?"),
wxT("Previewing"), wxOK);
return;
}
wxPreviewFrame *frame = new wxPreviewFrame(preview, this,
wxT("Demo Print Preview"));
frame->Centre(wxBOTH);
frame->Initialize();
frame->Show(true);
}
如果你要打印的内容主要是文本,你可以考虑使用wxHtmlEasyPrinting类,以便忽略wxPrinterDC和 wxPrintout排版的细节:你只需要按照wxWidgets’实现的那些HTML的语法编写HTML文件,然后创建一个 wxHtmlEasyPrinting对象用来实现打印和预览,这通常可以节省你几天到几周的时候来对那些文本,表格和图片进行排版。
绘制工具
wxColour(wxColor),wxPen, wxBrush, wxFont和wxPalette这些绘画工具
wxColour对象有很多种创建方法,你可以使用RGB三元色的值(0到255)来构建wxColour,或者通过一个标准的字符串,比如WHITE或者CYAN,或者从另外一个wxColour对象创建。
或者你还可以直接使用系统预定的颜色对象指针: wxBLACK, wxWHITE, wxRED, wxBLUE, wxGREEN, wxCYAN,和 wxLIGHT_GREY.还有一个wxNullColour对象用来代表未初始化的颜色,它的Ok函数总是返回False。
使用wxSystemSettings类可以获取很多系统默认的颜色,比如3D表面颜色,默认的窗口背景颜色,菜单文本颜色等等。请参考相关文档中wxSystemSettings::GetColour的部分来获取详细的列表。
wxTheColourDatabase 可以管理全局颜色,支持 Add/Find 等操作
(wxPen) 画笔指定了随后的绘画操作中线条的颜色,粗细以及线条类型。wxPen的开销很小,你可以放心的在你的绘图代码中创建局部变量类型的画笔对象而不用对它们进行全局存储。
下表列出了目前支持的画笔线条类型,其中Hatch和stipple类型目前的GTK+版本不支持:
线形 | 描述 |
---|---|
wxSOLID | 纯色线. |
wxTRANSPARENT | 透明 |
wxDOT | 纯点线. |
wxLONG_DASH | 长虚线. |
wxSHORT_DASH | 短虚线.在windows平台上等同于wxLONG_SASH. |
wxDOT_DASH | 短线和点间隔线. |
wxSTIPPLE | 使用一个位图代替点的点虚线,这个位图是其构造函数的第一个参数 |
wxUSER_DASH | 自定义虚线. 参考用户手册. |
wxBDIAGONAL_HATCH | 反斜线虚线. |
wxCROSSDIAG_HATCH | 交叉虚线. |
wxFDIAGONAL_HATCH | 斜线虚线. |
wxCROSS_HATCH | 十字虚线. |
wxHORIZONTAL_HATCH | 水平线段虚线. |
wxVERTICAL_HATCH | 垂直线段虚线. |
使用SetCap定义粗线条的末端的样子,使用SetJoin函数来设置当有线段相连时候的联结方式。 |
可以直接使用预定的画笔对象:wxRED_PEN, wxCYAN_PEN, wxGREEN_PEN, wxBLACK_PEN, wxWHITE_PEN, wxtrANSPARENT_PEN, wxBLACK_DASHED_PEN, wxGREY_PEN, wxMEDIUM_GREY_PEN 和 wxLIGHT_GREY_PEN.
这些都是指针,所以在SetPen函数中使用的时候,应该使用*
号指向它们的实例。还有一个预定义的对象(不是指针)wxNullPen,可以用来复位设备上下文中的画笔。
wxThePenList->FindOrCreatePen 管理全局 Pen
画刷wxBrush对象可以用SetBrush函数指定,它决定设备上下文中图像的填充方式。
画刷的构造函数采用一个颜色参数和一个画刷类型参数,如下表所示:
画刷类型 | 描述 |
---|---|
wxSOLID | 纯色画刷. |
wxTRANSPARENT | |
wxBDIAGONAL_HATCH | 反斜线画刷. |
wxCROSSDIAG_HATCH | 交叉画刷. |
wxFDIAGONAL_HATCH | 斜线画刷. |
wxCROSS_HATCH | 十字画刷. |
wxHORIZONTAL_HATCH | 水平线画刷. |
wxVERTICAL_HATCH | 垂直线画刷. |
wxSTIPPLE | 位图画刷, 其位图在构造函数中指定. |
也可以直接使用下面这些系统预定义的画刷:wxBLUE_BRUSH, wxGREEN_BRUSH, wxWHITE BRUSH, wxBLACK_BRUSH, wxGREY_BRUSH, wxMEDIUM_GREY_BRUSH, wxLIGHT_GREY_BRUSH, wxtrANSPARENT_BRUSH, wxCYAN_BRUSH和 wxRED_BRUSH.
这些都是指针,类似的还有wxNullBrush用来复位设备上下文的画刷。
wxTheBrushList->FindOrCreateBrush 管理全局brush
字体wxFont对象来设置一个设备上下文使用的字体。
字体名属性是可选参数,用来指定一个特定的字体,如果其为空,则将使用指定字体家族默认的字体。
字体家族标识符 | 描述 |
---|---|
wxFONTFAMILY_SWISS | 非印刷字体,依平台的不同通常是Helvetica或Arial. |
wxFONTFAMILY_ROMAN | 一种正规的印刷字体. |
wxFONTFAMILY_SCRIPT | 一种艺术字体. |
wxFONTFAMILY_MODERN | 一种等宽字体.通常是Courier |
wxFONTFAMILY_DECORATIVE | 一种装饰字体. |
wxFONTFAMILY_DEFAULT | wxWidgets选择一个默认的字体家族. |
字体类型可以是wxNORMAL, wxSLANT或wxITALIC。 | |
其中wxSLANT可能不是所有的平台或者所有的字体都支持。 |
可以使用下面这些系统预定义的字体: wxNORMAL_FONT, wxSMALL_FONT, wxITALIC_FONT和wxSWISS_FONT. 除了wxSMALL_FONT以外,其它的字体都使用同样大小的系统默认字体(wxSYS_DEFAULT_GUI_FONT), 而wxSMALL_FONT则比另外的三个字体小两个点.
你可以使用wxSystemSettings::GetFont来获取当前系统的默认字体。
wxFont font(12, wxFONTFAMILY_ROMAN, wxITALIC, wxBOLD, false);
wxFont font(10, wxFONTFAMILY_SWISS, wxNORMAL, wxBOLD, true, wxT("Arial"), wxFONTENCODING_ISO8859_1));
wxFont font(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
wxTheFontList->FindOrCreateFont 管理全局字体
调色板wxPalette是一个表,这个表的大小通常是256,表中的每一个索引被映射到一个对应的rgb颜色值。
因为现在大多数电脑的显示设备都支持真彩色,调色板已经很少使用了。
绘制demo
绘制文本
// 绘制文本
void DrawTextString(wxDC& dc, const wxString& text, const wxPoint& pt)
{
wxFont font(12, wxFONTFAMILY_SWISS, wxNORMAL, wxBOLD);
dc.SetFont(font);
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetTextForeground(*wxBLACK);
dc.SetTextBackground(*wxWHITE);
dc.DrawText(text, pt);
}
// 绘制到中间位置
// 如果你需要知道每个字符的精确占用大小,你可以使用GetPartialTextExtents函数,它使用wxString和一个wxArrayInt的引用作为参数。
// 这个函数的效率在某些平台上优于对每个单个的字符使用GetTextExtent函数。
void CenterText(const wxString& text, wxDC& dc, wxWindow* win)
{
dc.SetFont(*wxNORMAL_FONT);
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetTextForeground(*wxRED);
// 获取窗口大小和文本大小
wxSize sz = win->GetClientSize();
wxCoord w, h;
dc.GetTextExtent(text, & w, & h);
// 计算为了居中显示需要的文本开始位置
// 并保证其不为负数.
int x = wxMax(0, (sz.x - w)/2);
int y = wxMax(0, (sz.y - h)/2);
dc.DrawText(msg, x, y);
}
绘制线条
一个约定俗成的规矩,线上的最后一个点将不会绘制
void DrawSimpleShapes(wxDC& dc)
{
// 设置黑色的轮廓线,绿色的填充色
dc.SetPen(wxPen(*wxBLACK, 2, wxSOLID));
dc.SetBrush(wxBrush(*wxGREEN, wxSOLID));
// 画点
dc.DrawPoint(5, 5);
// 画线
dc.DrawLine(10, 10, 100, 100);
// 画矩形
dc.SetBrush(wxBrush(*wxBLACK, wxCROSS_HATCH));
dc.DrawRectangle(50, 50, 150, 100);
// 改成红色画刷
dc.SetBrush(*wxRED_BRUSH);
// 画圆角矩形
dc.DrawRoundedRectangle(150, 20, 100, 50, 10);
// 没有轮廓线的圆角矩形
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(*wxBLUE));
dc.DrawRoundedRectangle(250, 80, 100, 50, 10);
// 改变颜色
dc.SetPen(wxPen(*wxBLACK, 2, wxSOLID));
dc.SetBrush(*wxBLACK);
// 画圆
dc.DrawCircle(100, 150, 60);
// 再次改变画刷颜色
dc.SetBrush(*wxWHITE);
// 画一个椭圆
dc.DrawEllipse(wxRect(120, 120, 150, 50));
}
// 绘制圆弧
int x = 10, y = 200, radius = 20;
dc.DrawArc(xradius, y, x + radius, y, x, y);
// 大小为 200x40. 圆弧角度从 270到420度的圆弧.
dc.DrawEllipticArc(10, 100, 200, 40, 270, 420);
// 绘制光滑曲线
// 五点云行规曲线
wxPoint star[5];
star[0] = wxPoint(100, 60);
star[1] = wxPoint(60, 150);
star[2] = wxPoint(160, 100);
star[3] = wxPoint(40, 100);
star[4] = wxPoint(140, 150);
dc.DrawSpline(WXSIZEOF(star), star);
绘图
绘制位图有两种主要的方法:DrawBitmap和Blit。
DrawBitmap其实是Blit的一种简写形式,它使用一个位图,一个位置和一个bool类型的透明标志参数。
wxBitmap bmp(wxT("toucan.png"), wxBITMAP_TYPE_PNG);
dc.DrawBitmap(bmp, 250, 100, true);
填充
FloodFill函数采用三个参数来填充某个特定的区域。一个起始点参数,一个颜色参数用来确定填充的边界和一个填充类型参数,设备上下文将使用当前的画刷定义进行填充。
// 画一个红边绿色的矩形
dc.SetPen(*wxRED_PEN);
dc.SetBrush(*wxGREEN_BRUSH);
dc.DrawRectangle(10, 10, 100, 100);
dc.SetBrush(*wxBLACK_BRUSH);
// 将绿色区域变成黑色
dc.FloodFill(50, 50, *wxGREEN, wxFLOOD_SURFACE);
dc.SetBrush(*wxBLUE_BRUSH);
// 开始填充蓝色(直到遇到红色)
dc.FloodFill(50, 50, *wxRED, wxFLOOD_BORDER);
逻辑函数
逻辑函数指定在绘画时,源象素怎样和目标象素进行合并操作,默认的wxCOPY只是使用源象素取代目标象素。其它的值则指定了一种逻辑操作。
wxPen pen(*wxBLACK, 1, wxDOT);
dc.SetPen(pen);
// 以取反逻辑函数绘制
dc.SetLogicalFunction(wxINVERT);
dc.DrawLine(10, 10, 100, 100); // 反色的线
// 再次绘制
dc.DrawLine(10, 10, 100, 100); // 恢复原来的颜色
// 恢复正常绘制方法
dc.SetLogicalFunction(wxCOPY);
绘制三维图像
感谢OpenGL和wxGLCanvas,让wxWidgets拥有了绘制三维图形的能力。
如果你的平台不支持OpenGL,你仍然可以使用它的一个开放源码的实现Mesa。
要让wxWidgets在windows平台上支持wxGLCanvas,你需要编辑include/wx/msw/setup.h,设置 wxUSE_GLCANVAS为1,然后编译的时候在命令行使用USE_OPENGL=1,在连接的时候你也可能需要增加opengl32.lib。
而在 Unix或者Mac OS X上,你只需要在配置wxWidgets的时候增加–with-opengl参数来打开OpenGL或者Mesa的支持。
void TestGLCanvas::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
SetCurrent();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(-0.5f, 0.5f, -0.5f, 0.5f, 1.0f, 3.0f);
glMatrixMode(GL_MODELVIEW);
/* 清除颜色和深度缓冲 */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* 绘制一个立方体的六个面 */
glBegin(GL_QUADS);
glNormal3f( 0.0f, 0.0f, 1.0f);
glVertex3f( 0.5f, 0.5f, 0.5f); glVertex3f(-0.5f, 0.5f, 0.5f);
glVertex3f(-0.5f,-0.5f, 0.5f); glVertex3f( 0.5f,-0.5f, 0.5f);
glNormal3f( 0.0f, 0.0f,-1.0f);
glVertex3f(-0.5f,-0.5f,-0.5f); glVertex3f(-0.5f, 0.5f,-0.5f);
glVertex3f( 0.5f, 0.5f,-0.5f); glVertex3f( 0.5f,-0.5f,-0.5f);
glNormal3f( 0.0f, 1.0f, 0.0f);
glVertex3f( 0.5f, 0.5f, 0.5f); glVertex3f( 0.5f, 0.5f,-0.5f);
glVertex3f(-0.5f, 0.5f,-0.5f); glVertex3f(-0.5f, 0.5f, 0.5f);
glNormal3f( 0.0f,-1.0f, 0.0f);
glVertex3f(-0.5f,-0.5f,-0.5f); glVertex3f( 0.5f,-0.5f,-0.5f);
glVertex3f( 0.5f,-0.5f, 0.5f); glVertex3f(-0.5f,-0.5f, 0.5f);
glNormal3f( 1.0f, 0.0f, 0.0f);
glVertex3f( 0.5f, 0.5f, 0.5f); glVertex3f( 0.5f,-0.5f, 0.5f);
glVertex3f( 0.5f,-0.5f,-0.5f); glVertex3f( 0.5f, 0.5f,-0.5f);
glNormal3f(-1.0f, 0.0f, 0.0f);
glVertex3f(-0.5f,-0.5f,-0.5f); glVertex3f(-0.5f,-0.5f, 0.5f);
glVertex3f(-0.5f, 0.5f, 0.5f); glVertex3f(-0.5f, 0.5f,-0.5f);
glEnd();
glFlush();
SwapBuffers();
}
用户输入
基本的鼠标事件使用wxMouseEvent作为参数,不加任何翻译的发送给响应的窗口事件处理函数,而窗口事件处理函数则通常把它们翻译成对应的命令事件(wxCommandEvent)。
下表列出了所有对应的鼠标事件映射宏。需要注意的是,wxMouseEvent事件是不会传递给父窗口处理的,所以,为了处理这个事件,你必须重载一个新的窗口类,或者重载一个新的wxEvtHandler,然后将其挂载在某个窗口上,当然你还可以使用动态事件处理函数Connect。
EVT_LEFT_DOWN(func) | 用来处理wxEVT_LEFT_DOWN事件, 在鼠标左键按下的时候产生. |
---|---|
EVT_LEFT_UP(func) | 用来处理wxEVT_LEFT_UP事件, 在鼠标左键被释放的时候产生. |
EVT_LEFT_DCLICK(func) | 用来处理wxEVT_LEFT_DCLICK事件,在鼠标左键被双击的时候产生. |
EVT_MIDDLE_DOWN(func) | 用来处理wxEVT_MIDDLE_DOWN事件, 在鼠标中键被按下的时候产生. |
EVT_MIDDLE_UP(func) | 用来处理wxEVT_MIDDLE_UP事件,当鼠标中键被释放的时候产生. |
EVT_MIDDLE_DCLICK(func) | 用来处理wxEVT_MIDDLE_DCLICK事件,在鼠标中键被双击的时候产生. |
EVT_RIGHT_DOWN(func) | 用来处理wxEVT_RIGHT_DOWN事件,鼠标右键被按下的时候产生. |
EVT_RIGHT_UP(func) | 用来处理wxEVT_RIGHT_UP事件,鼠标右键被释放的时候产生. |
EVT_RIGHT_DCLICK(func) | 用来处理wxEVT_RIGHT_DCLICK事件,鼠标右键被双击的时候产生. |
EVT_MOTION(func) | 用来处理wxEVT_MOTION事件,鼠标指针移动的时候产生. |
EVT_ENTER_WINDOW(func) | 用来处理wxEVT_ENTER_WINDOW事件,鼠标指针移入某个窗口的时候产生. |
EVT_LEAVE_WINDOW(func) | 用来处理wxEVT_LEAVE_WINDOW事件,鼠标移出某个窗口的时候产生. |
EVT_MOUSEWHEEL(func) | 用来处理wxEVT_MOUSEWHEEL事件,鼠标滚轮滚动的时候产生. |
EVT_MOUSE_EVENTS(func) | 用来处理所有的鼠标事件. |
要检测当产生某个事件时状态键的状态,可以使用AltDown,MetaDown,ControlDown或者ShiftDown等函数.使用CmdDown函数来检测Mac OS X平台上的Meta键或者别的平台上的Control键的状态. |
// 一个绘图demo
BEGIN_EVENT_TABLE(DoodleCanvas, wxWindow)
EVT_MOUSE_EVENTS(DoodleCanvas::OnMouseEvent)
END_EVENT_TABLE()
void DoodleCanvas::OnMouseEvent(wxMouseEvent& event)
{
static DoodleSegment *s_currentSegment = NULL;
wxPoint pt(event.GetPosition());
if (s_currentSegment && event.LeftUp())
{
// 鼠标按钮释放的时候停止当前线段
if (s_currentSegment->GetLines().GetCount() == 0)
{
// 释放线段记录并且释放指针
delete s_currentSegment;
s_currentSegment = (DoodleSegment *) NULL;
}
else
{
// 已经得到一个有效的线段,把它存下来
DrawingDocument *doc = GetDocument();
doc->GetCommandProcessor()->Submit(
new DrawingCommand(wxT("Add Segment"), DOODLE_ADD,
doc, s_currentSegment));
doc->Modify(true);
s_currentSegment = NULL;
}
}
else if (m_lastX > -1 && m_lastY > -1 && event.Dragging())
{
//正在拖动鼠标,增加一行到当前的线段中
if (!s_currentSegment)
s_currentSegment = new DoodleSegment;
DoodleLine *newLine = new DoodleLine(m_lastX, m_lastY, pt.x, pt.y);
s_currentSegment->GetLines().Append(newLine);
wxClientDC dc(this);
DoPrepareDC(dc);
dc.SetPen(*wxBLACK_PEN);
dc.DrawLine( m_lastX, m_lastY, pt.x, pt.y);
}
m_lastX = pt.x;
m_lastY = pt.y;
}
键盘事件
键盘事件是由wxKeyEvent类表示的.总共有三种不同类型的键盘事件,分别为:键按下,键释放和字符事件. 键按下和键释放事件是原始事件,而字符事件是翻译事件.
下表列出了对应的事件映射宏:
EVT_KEY_DOWN(func) | 用来处理wxEVT_KEY_DOWN事件 (原始按键按下事件). |
---|---|
EVT_KEY_UP(func) | 用来处理wxEVT_KEY_UP事件 (原始的按键释放). |
EVT_CHAR(func) | 用来处理wxEVT_CHAR事件 (已经翻译的按键按下事件). |
要获得按键编码,你可以使用GetKeyCode函数(在Unicode版本中,你还可以使用GetUnicodeKeyCode函数).下表列出了所有的按键编码: |
WXK_BACK | WXK_RIGHT |
---|---|
WXK_TAB | WXK_DOWN |
WXK_RETURN | WXK_SELECT |
WXK_ESCAPE | WXK_PRINT |
WXK_SPACE | WXK_EXECUTE |
WXK_DELETE | WXK_SNAPSHOT |
WXK_INSERT | WXK_START |
WXK_HELP | WXK_LBUTTON |
WXK_RBUTTON | WXK_NUMPAD0 |
WXK_CANCEL | WXK_NUMPAD1 |
WXK_MBUTTON | WXK_NUMPAD2 |
WXK_CLEAR | WXK_NUMPAD3 |
WXK_SHIFT | WXK_NUMPAD4 |
WXK_CONTROL | WXK_NUMPAD5 |
WXK_MENU | WXK_NUMPAD6 |
WXK_PAUSE | WXK_NUMPAD7 |
WXK_CAPITAL | WXK_NUMPAD8 |
WXK_PRIOR | WXK_NUMPAD9 |
WXK_NEXT | WXK_END |
WXK_MULTIPLY | WXK_HOME |
WXK_ADD | WXK_LEFT |
WXK_SEPARATOR | WXK_UP |
WXK_SUBTRACT | WXK_DECIMAL |
WXK_PAGEDOWN | WXK_DIVIDE |
WXK_NUMPAD_SPACE | WXK_F1 |
WXK_NUMPAD_TAB | WXK_F2 |
WXK_NUMPAD_ENTER | WXK_F3 WXK_F4 |
WXK_NUMPAD_F1 | WXK_F5 |
WXK_NUMPAD_F2 | WXK_F6 |
WXK_NUMPAD_F3 | WXK_F7 |
WXK_NUMPAD_F4 | WXK_F8 |
WXK_NUMPAD_HOME | WXK_F9 |
WXK_NUMPAD_LEFT | WXK_F10 |
WXK_NUMPAD_UP | WXK_F11 |
WXK_NUMPAD_RIGHT | WXK_F12 |
WXK_NUMPAD_DOWN | WXK_F13 |
WXK_NUMPAD_PRIOR | WXK_F14 |
WXK_NUMPAD_PAGEUP | WXK_F15 |
WXK_NUMPAD_NEXT | WXK_F16 |
WXK_NUMPAD_PAGEDOWN | WXK_F17 |
WXK_NUMPAD_END | WXK_F18 |
WXK_NUMPAD_BEGIN | WXK_F19 |
WXK_NUMPAD_INSERT | WXK_F20 |
WXK_NUMPAD_DELETE | WXK_F21 |
WXK_NUMPAD_EQUAL | WXK_F22 |
WXK_NUMPAD_MULTIPLY | WXK_F23 |
WXK_NUMPAD_ADD | WXK_F24 |
WXK_NUMPAD_SEPARATOR | WXK_NUMPAD_SUBTRACT |
WXK_NUMLOCK | WXK_NUMPAD_DECIMAL |
WXK_SCROLL | WXK_NUMPAD_DIVIDE |
WXK_PAGEUP |
要判断在按键的时候是否有状态键按下,可以使用AltDown, MetaDown, ControlDown或者ShiftDown函数. HasModifiers函数在有Control或者Alt键按下的时候返回True(不包括Shift和Meta键).
BEGIN_EVENT_TABLE( wxThumbnailCtrl, wxScrolledWindow )
EVT_CHAR(wxThumbnailCtrl::OnChar)
END_EVENT_TABLE()
void wxThumbnailCtrl::OnChar(wxKeyEvent& event)
{
int flags = 0;
if (event.ControlDown())
flags |= wxTHUMBNAIL_CTRL_DOWN;
if (event.ShiftDown())
flags |= wxTHUMBNAIL_SHIFT_DOWN;
if (event.AltDown())
flags |= wxTHUMBNAIL_ALT_DOWN;
if (event.GetKeyCode() == WXK_LEFT ||
event.GetKeyCode() == WXK_RIGHT ||
event.GetKeyCode() == WXK_UP ||
event.GetKeyCode() == WXK_DOWN ||
event.GetKeyCode() == WXK_HOME ||
event.GetKeyCode() == WXK_PAGEUP ||
event.GetKeyCode() == WXK_PAGEDOWN ||
event.GetKeyCode() == WXK_PRIOR ||
event.GetKeyCode() == WXK_NEXT ||
event.GetKeyCode() == WXK_END)
{
Navigate(event.GetKeyCode(), flags);
}
else if (event.GetKeyCode() == WXK_RETURN)
{
wxThumbnailEvent cmdEvent(wxEVT_COMMAND_THUMBNAIL_RETURN, GetId());
cmdEvent.SetEventObject(this);
cmdEvent.SetFlags(flags);
GetEventHandler()->ProcessEvent(cmdEvent);
}
else
event.Skip();
}
键盘事件提供的是未翻译的按键编码, 而字符事件提供的是翻译以后的字符编码.
对于未翻译的按键编码来说, 子母永远是大些字符, 其它字符则是在WXK_XXX
中定义的字符.
而对于已经翻译的按键编码来说,字符的值和同样的按键在一个文本编辑框中被按下以后在编辑框中产生的字符相同.
加速键
最简单的定义加速键的方法是在菜单项定义函数中使用下面的代码:
menu->Append(wxID_COPY, wxT("Copy\tCtrl+C"));
你可以使用Ctrl,Alt和Shift以及它们的各种组合,然后加一个+号或者-号再跟一个字符或者功能键,
比如下面的这些加速键都是合法的加速键: Ctrl+B, G, Shift-Alt-K, F9, Ctrl+F3, Esc 和Del.
在你的加速键定义中可以使用下面的名字: Del, Back, Ins, Insert, Enter, Return, PgUp, PgDn, Left, Right, Up, Down, Home, End, Space, Tab, Esc和 Escape.
这些命令是大小写无关的(你想怎样使用大小写都可以).
另外一种设置加速键的方法是使用wxAcceleratorEntry对象定义一个加速键表,然后使用wxWindow:: SetAcceleratorTable
函数将其和某个窗口绑定.
每一个wxAcceleratorEntry的记录是由一个修饰键比特位值和一个字符或者功能键以及一个窗口标识符组成的, 如下所示:
wxAcceleratorEntry entries[4];
entries[0].Set(wxACCEL_CTRL, (int) 'N', wxID_NEW);
entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_EXIT);
entries[2].Set(wxACCEL_SHIFT, (int) 'A', wxID_ABOUT);
entries[3].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_CUT);
wxAcceleratorTable accel(4, entries);
frame->SetAcceleratorTable(accel);
手柄joy
wxJoystick类让你可以在windows平台或者linux平台上使用一到两个游戏手柄.
wxJoystick类产生wxJoystickEvent类型的事件,下表列出了所有相关的事件映射宏:
EVT_JOY_BUTTON(func) | 用来处理wxEVT_JOY_BUTTON_DOWN事件,手柄上的某个按钮被按下的时候产生. |
---|---|
EVT_JOY_BUTTON(func) | 用来处理wxEVT_JOY_BUTTON_UP事件,某个按钮被释放的时候产生. |
EVT_JOY_MOVE(func) | 用来处理wxEVT_JOY_MOVE事件,手柄在X-Y平面上产生移动的时候产生. |
EVT_JOY_ZMOVE(func) | 用来处理wxEVT_JOY_ZMOVE事件,手柄在Z方向上产生移动的时候产生. |
EVT_JOYSTICK_EVENTS(func) | 处理所有的手柄事件. |
BEGIN_EVENT_TABLE(MyCanvas, wxScrolledWindow)
EVT_JOYSTICK_EVENTS(MyCanvas::OnJoystickEvent)
END_EVENT_TABLE()
MyCanvas::MyCanvas(wxWindow *parent, const wxPoint& pos,
const wxSize& size):
wxScrolledWindow(parent, wxID_ANY, pos, size, wxSUNKEN_BORDER)
{
m_stick = new wxJoystick(wxJOYSTICK1);
m_stick->SetCapture(this, 10);
}
MyCanvas::~MyCanvas()
{
m_stick->ReleaseCapture();
delete m_stick;
}
void MyCanvas::OnJoystickEvent(wxJoystickEvent& event)
{
static long xpos = -1;
static long ypos = -1;
wxClientDC dc(this);
wxPoint pt(event.GetPosition());
// if negative positions are possible then shift everything up
int xmin = wxGetApp().m_minX;
int xmax = wxGetApp().m_maxX;
int ymin = wxGetApp().m_minY;
int ymax = wxGetApp().m_maxY;
if (xmin < 0) {
xmax += abs(xmin);
pt.x += abs(xmin);
}
if (ymin < 0) {
ymax += abs(ymin);
pt.y += abs(ymin);
}
// Scale to canvas size
int cw, ch;
GetSize(&cw, &ch);
pt.x = (long) (((double)pt.x/(double)xmax) * cw);
pt.y = (long) (((double)pt.y/(double)ymax) * ch);
if (xpos > -1 && ypos > -1 && event.IsMove() && event.ButtonIsDown())
{
dc.SetPen(*wxBLACK_PEN);
dc.DrawLine(xpos, ypos, pt.x, pt.y);
}
xpos = pt.x;
ypos = pt.y;
wxString buf;
if (event.ButtonDown())
buf.Printf(wxT("Joystick (%d, %d) Fire!"), pt.x, pt.y);
else
buf.Printf(wxT("Joystick (%d, %d)"), pt.x, pt.y);
frame->SetStatusText(buf);
if (event.ButtonDown() && wxGetApp().m_fire.IsOk())
{
wxGetApp().m_fire.Play();
}
}
Layout
wxWidgets使用的窗口布局控件的算法和其它GUI程序开发框架的算法,例如Java的AWT,GTK+以及Qt等是非常相似的.
它们都是基于这样的一个假设,那就是每一个窗口可以报告它们自己需要的最小尺寸以及当它们父窗口的大小发生改变的时候它们的可伸缩能力.
wxWidgets总共支持五类布局控件,每一中布局控件或者用来实现一种特殊的布局方式,或者用来实现和布局相关的一种特殊的功能.
所有的布局控件都是容器,这就是说,它们都是用来容纳一个或多个别的窗口或者元素的,不论每个单独的布局控件怎样排放它们的子元素,所有的子元素都必然有下面这些通用的特性.
- 最小大小 wxFIXED_MINSIZE
- 边界 marggin
- 对齐方式 align
- 伸缩因子 stretch factor / proportion
wxBoxSizer
wxBoxSizer可以将它的容器子元素进行横向或者纵向的排列(具体的排列方式在构造函数中指定).
- 如果采用横向排列的方法,则子元素在纵向上可以指定居中,顶部对齐,底部对齐,
- 如果采用纵向排列的方法,子元素在横向上可以指定居中,左对齐或者右对齐的方式.
// 增加一个窗口
void Add(wxWindow* window, int stretch = 0, int flags = 0, int border = 0);
// 增加一个布局控件
void Add(wxSizer* window, int stretch = 0, int flags = 0, int border = 0);
// 增加一段固定大小的空白
void AddSpacer(int size);
// 增加一个可缩放的空白
void AddStretchSpacer(int stretch = 1);
第三个参数 flags 是一个比特位列表,用来指示新增的子元素的对齐和边界的行为.
第四个参数 border 指定边界间隔的大小。
如下是第三个参数flags的可用列表(默认的值为 wxALIGN_LEFT | wxALIGN_TOP):
值 | 含义 |
---|---|
0 | 子元素保留原始大小. |
wxGROW | 子元素随这布局控件一起改变大小. 等同于wxEXPAND. |
wxSHAPED | 子元素保持原有比例按缩放因子缩放. |
wxALIGN_LEFT | 左对齐. |
wxALIGN_RIGHT | 右对齐. |
wxALIGN_TOP | 顶端对齐. |
wxALIGN_BOTTOM | 底部对齐. |
wxALIGN_CENTER_HORIZONTAL | 水平居中. |
wxALIGN_CENTER_VERTICAL | 垂直居中. |
wxALIGN_CENTER | 水平或者垂直居中. wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL. |
wxLEFT | 边界间隔位于子元素左面. |
wxRIGHT | 边界间隔位于子元素右面. |
wxTOP | 边界间隔位于子元素上面. |
wxBOTTOM | 边界间隔位于子元素下面. |
wxALL | 边界间隔位于子元素四周.wxLEFT | wxRIGHT | wxTOP | wxBOTTOM. |
MyDialog::MyDialog(wxWindow *parent, wxWindowID id, const wxString &title )
: wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
wxBoxSizer *topSizer = new wxBoxSizer( wxVERTICAL );
// 创建一个最小大小为 100x60的多行文本框
topSizer->Add(
new wxTextCtrl( this, wxID_ANY, "My text.",
wxDefaultPosition, wxSize(100,60), wxTE_MULTILINE),
1, // 垂直方向可缩放,缩放因子为1
wxEXPAND| // 水平方向可缩放
wxALL, // 四周都由空白边框
10 ); // 空白边框大小为10
wxBoxSizer *buttonSizer = new wxBoxSizer( wxHORIZONTAL );
buttonSizer->Add(
new wxButton( this, wxID_OK, "OK" ),
0, // 水平方向不可缩放
wxALL, // 四周有空白边框:(注意默认为顶部对齐)
10 ); // 空白边框大小为10
buttonSizer->Add(
new wxButton( this, wxID_CANCEL, "Cancel" ),
0, // 水平方向不可缩放
wxALL, // 四周有空白边框:(注意默认为顶部对齐)
10 ); // 空白边框大小为10
topSizer->Add(
buttonSizer,
0, // 垂直方向不可缩放
wxALIGN_CENTER ); // 无边框并且居中对齐
// !!! 绑定 sizer 到 window
SetSizer( topSizer ); // 绑定对话框和布局控件
topSizer->Fit( this ); // 调用对话框大小
topSizer->SetSizeHints( this ); // 设置对话框最小大小
}
wxStaticBoxSizer(groupbox 类似的容器)
wxStaticBoxSizer是一个继承自wxBoxSizer的布局控件,除了实现wxBoxSizer的功能,另外还在整个布局的范围以外增加了一个静态的边框wxStaticBox.
所以 wxStaticBox + wxStaticBoxSizer 才是正常使用的 groupbox
MyDialog::MyDialog(wxWindow *parent, wxWindowID id, const wxString &title )
: wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
// 创建一个顶层布局控件
wxBoxSizer* topLevel = new wxBoxSizer(wxVERTICAL);
// 创建静态文本框和静态文本框布局控件
wxStaticBox* staticBox = new wxStaticBox(this, wxID_ANY, wxT("General settings"));
wxStaticBoxSizer* staticSizer = new wxStaticBoxSizer(staticBox, wxVERTICAL);
topLevel->Add(staticSizer, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
// 在其中增加一个复选框
wxCheckBox* checkBox = new wxCheckBox( this, ID_CHECKBOX, wxT("&Show splash screen"), wxDefaultPosition, wxDefaultSize);
staticSizer->Add(checkBox, 0, wxALIGN_LEFT |wxALL, 5);
SetSizer(topLevel);
topLevel->Fit(this);
topLevel->SetSizeHints(this);
}
wxGridSizer
可以以二维表的方式排列它的子元素,这个二维表的每个表格的大小都是相同的,都等于最长的那个表格的长度和最高的那个表格的高度.
创建一个wxGridSizer需要指定它的行数和列数,以及一个额外的行间距和列间距.
MyDialog::MyDialog(wxWindow *parent, wxWindowID id, const wxString &title )
: wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
// 创建一个顶层网格布局控件, 2行3列
wxGridSizer* gridSizer = new wxGridSizer(2, 3, 0, 0);
SetSizer(gridSizer);
wxButton* button1 = new wxButton(this, ID_BUTTON1, wxT("One"));
gridSizer->Add(button1, 0, wxALIGN_CENTER_HORIZONTAL| wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxButton* button2 = new wxButton(this, ID_BUTTON2, wxT("Two (the second button)"));
gridSizer->Add(button2, 0, wxALIGN_CENTER_HORIZONTAL| wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxButton* button3 = new wxButton(this, ID_BUTTON3, wxT("Three"));
gridSizer->Add(button3, 0, wxALIGN_CENTER_HORIZONTAL| wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxButton* button4 = new wxButton(this, ID_BUTTON4, wxT("Four"));
gridSizer->Add(button4, 0, wxALIGN_CENTER_HORIZONTAL| wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxButton* button5 = new wxButton(this, ID_BUTTON5, wxT("Five"));
gridSizer->Add(button5, 0, wxALIGN_CENTER_HORIZONTAL| wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxButton* button6 = new wxButton(this, ID_BUTTON6, wxT("Six"));
gridSizer->Add(button6, 0, wxALIGN_CENTER_HORIZONTAL| wxALIGN_CENTER_VERTICAL|wxALL, 5);
gridSizer->Fit(this);
gridSizer->SetSizeHints(this);
}
wxFlexGridSizer
同样采用二维表来对其子元素进行布局,和wxGridSizer不同的是,它不要求所有的表格的大小都是一样的,只要求同一列上所有表格的宽度是相同的并且同一行上所有表格的高度是相同的。
另外还可以给行和列指定是否缩放,这意味着当整个布局控件的大小发生变化的时候,可以指定某些行或者列随着整个布局控件的缩放而缩放. AddGrowableCol
.
其他都和wxGridSizer 一样的。
wxGridBagSizer
用来模拟现实世界中的那种固定位置和大小的基于布局控件的布局.
它将它的子元素按照一个虚拟的网格进行排列,不过子元素的位置是通过wxGBPosition对象指定的,对象的大小使用wxGBSpan指定,对象的大小不仅限于一个网格.
MyDialog::MyDialog(wxWindow *parent, wxWindowID id, const wxString &title )
: wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
wxGridBagSizer* gridBagSizer = new wxGridBagSizer();
SetTopSizer(gridBagSizer);
wxButton* b1 = new wxButton(this, wxID_ANY, wxT("One (0,0)"));
gridBagSizer->Add(b1, wxGBPosition(0, 0));
wxButton* b2 = new wxButton(this, wxID_ANY, wxT("Two (2,2)"));
gridBagSizer->Add(b2, wxGBPosition(2, 2), wxGBSpan(1, 2), wxGROW);
wxButton* b3 = new wxButton(this, wxID_ANY, wxT("Three (3,2)"));
gridBagSizer->Add(b3, wxGBPosition(3, 2));
wxButton* b4 = new wxButton(this, wxID_ANY, wxT("Four (3,3)"));
gridBagSizer->Add(b4, wxGBPosition(3, 3));
gridBagSizer->AddGrowableRow(3);
gridBagSizer->AddGrowableCol(2);
gridBagSizer->Fit(this);
gridBagSizer->SetSizeHints(this);
}
平台自适应布局
尽管不同平台的对话框的绝大部分都是相同的,但是在风格上确是存在着一些不同.
比如在Windows和Linnx平台上,右对齐或者居中放置的OK,Cancel和Help按钮都是可以接受的,但是在Mac OsX上,Help按钮通常位于左面,而Cancel和OK按钮则通常依序位于右面.
要作到这种不同平台上按钮顺序的自适应,你需要使用wxStdDialogButtonSizer
布局控件,这个控件继承自wxBoxSizer,因此使用方法并没有太大的不同,只是它依照平台的不同对某些按钮进行特殊的排列.
要增加按钮可以使用两种方法:
- 传递按钮指针给AddButton函数,
- 使用SetAffirmativeButton, SetNegativeButton, and SetCancelButton来设置按钮的特性.
- 如果使用AddButton,那么按钮应使用下面的这些标识符: wxID_OK, wxID_YES, wxID_CANCEL, wxID_NO, wxID_SAVE, wxID_APPLY, wxID_HELP和 wxID_CONTEXT_HELP.
在所有的按钮都增加到布局控件以后,调用Realize函数以便布局控件调整按钮的顺序,
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
dialog->SetSizer(topSizer);
wxButton* ok = new wxButton(dialog, wxID_OK);
wxButton* cancel = new wxButton(dialog, wxID_CANCEL);
wxButton* help = new wxButton(dialog, wxID_HELP);
wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer;
buttonSizer->AddButton(ok);
buttonSizer->AddButton(cancel);
buttonSizer->AddButton(help);
buttonSizer->Realize();
topSizer->Add(buttonSizer, 0, wxEXPAND|wxALL, 10);
或者作为一个更方便的手段,你可以使用wxDialog::CreateButtonSizer函数,它基于一些按钮标记的列表来自动创建平台自适应的按钮,并将其放在一个布局控件中.
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
dialog->SetSizer(topSizer);
topSizer->Add(CreateButtonSizer(wxOK|wxCANCEL|wxHELP), 0, wxEXPAND|wxALL, 10);
标准对话框
四类: 信息对话框,文件和目录对话框,选项和选择对话框以及输入对话框.
wxMessageDialog
这种对话框显示一个消息和一组按钮,按钮可以为OK, Cancel, Yes或者No,还可以有一个可选的图标,用来显示一个惊叹号或者一个问号.消息文本中还可以包含换行符"\n".
wxMessageDialog::ShowModal
函数的返回值用来表征哪个按钮被按下了.
wxMessageBox
它的参数为一个消息文本,标题文本,类型和父窗口.
if (wxYES == wxMessageBox(wxT("Message box text"), wxT("Message box caption"),
wxNO_DEFAULT|wxYES_NO|wxCANCEL|wxICON_INFORMATION, parent))
{
return true;
}
要注意wxMessageBox
的返回值和wxMessageDialog::ShowModal
的返回值是不一样的,
前者返回wxOK, wxCANCEL, wxYES或wxNO,
而后者返回wxID_OK, wxID_CANCEL, wxID_YES或 wxID_NO.
wxProgressDialog
可以用来显示一个短的消息文本和一个进度条用来指示用户还需要等待多久.它还可以显示一个Cancel 按钮用来中止正在进行的处理,还可以显示已经过去的时间,估计剩余的时间和估计全部的时间.
这个对话框是wxWidgets在各个平台上自己实现的.
在进度对话框被创建以后,其父窗口将被禁用,如果设置了wxPD_APP_MODAL类型,则应用程序中其它的窗口也将被禁用.应用程序应用调用Update函数来更新进度条以及提示信息,如果设置了时间显示标签,则它们的值将被自动计算并在每次调用Update的时候刷新.
如果设置了wxPD_AUTO_HIDE类型,对话框会在进度达到最大值的时候自动隐藏,你应该自己根据其创建方式释放这个对话框.
#include "wx/progdlg.h"
void MyFrame::ShowProgress()
{
static const int max = 10;
wxProgressDialog dialog(wxT("Progress dialog example"),
wxT("An informative message"),
max, // range
this, // parent
wxPD_CAN_ABORT |
wxPD_APP_MODAL |
wxPD_ELAPSED_TIME |
wxPD_ESTIMATED_TIME |
wxPD_REMAINING_TIME);
bool cont = true;
for ( int i = 0; i <= max; i++ )
{
wxSleep(1);
if ( i == max )
cont = dialog.Update(i, wxT("That's all, folks!"));
else if ( i == max / 2 )
cont = dialog.Update(i, wxT("Only a half left (very long message)!"));
else
cont = dialog.Update(i);
if ( !cont )
{
if ( wxMessageBox(wxT("Do you really want to cancel?"),
wxT("Progress dialog question"),
wxYES_NO | wxICON_QUESTION) == wxYES )
break;
dialog.Resume();
}
}
if ( !cont )
wxLogStatus(wxT("Progress dialog aborted!"));
else
wxLogStatus(wxT("Countdown from %d finished"), max);
}
wxBusyInfo
其实不是一个对话框不过它的表现和对话框非常相似,当这个对象被创建的时候,屏幕上将显示一个窗口以及一条让用户耐心等待的消息,这个窗口将存在于wxBusyInfo的整个生命周期.
wxShowTip
许多应用程序都会在程序启动的时候显示一个附加的窗口,用来给出一些如何使用这个应用程序的提示信息,那些不愿阅读沉闷的文档的人会非常喜欢这样的学习方式的.
你必须实现一个wxTipProvider的派生类,实现其中的GetTip函数才可以使用wxShowTip函数,幸运的是, wxWidgets已经实现了一个这样的基于文本文件的类.你可以直接使用wxCreateFileTipProvider函数,传递以文本文件(每行一个提示文本)路径和默认选择索引来创建一个这样的类.
wxFileDialog
来让用户选择一个或多个文件.它还有一个专门用来打开文件或者保存文件的变体.
- 目录名和文件名组成一个文件全路径.如果目录名为空,则默认为当前工作目录,如果文件名为空,则没有默认文件名.
- 通配符用来确定哪种类型的文件应该显示在对话框中.通配符可以用"|"分割多种文件类型,并且可以提供文件类型的描述文字,具体格式如下所示:
BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif
如果在文件名文本区输入一个带有通配符("*","?")
的文件名,然后点确定按钮,将会导致只有符合这个通配名的文件名被显示.
wxFileDialog的类型:
wxSAVE | 指定为一个保存文件对话框. |
---|---|
wxOPEN | 指定为一个打开文件对话框(默认行为). |
wxOVERWRITE_PROMPT | 对于保存文件对话框,如果目标文件已经存在,则提示是否覆盖. |
wxFILE_MUST_EXIT | 用户只能选择已经存在的文件. |
wxMULTIPLE | 用户可以选择多个文件. |
成员函数: |
- Getdirectory返回默认的目录名或者单选文件对话框中选中文件的所在的目录名,使用SetDirectory设置默认目录.
- GetFilename返回不包括目录部分的默认文件名或者单选文件对话框中选中的文件的文件名.使用SetFilename设置默认文件名.
- GetFilenames使用wxArrayString类型返回多选文件对话框中所有选中的文件名.
- 通常,这些文件名是不含有路径的, 但是在windows平台上,如果选中的是一个快捷方式文件,windows可能会增加上一个全路径,因为应用程序无法通过通过增加当前的目录的方式来得到其全路径.
- 使用GetPaths可以得到包含全路径在内的已选中文件的列表.
- GetFilterIndex用来返回默认的基于0的过滤器的索引.过滤器通常以一个下拉框的方式显示.使用SetFilterIndex设置默认的过滤器索引.
- GetMessage返回对话框的标题文本. 使用SetMessage函数来设置标题文本.
- GetPath以全路径的方式返回单选文件框中默认文件或者选中文件名. 对于多选框,应使用GetPaths函数.
- GetWildcard返回指定的通配符, SetWildcard用来设置通配符.
#include "wx/filedlg.h"
void test(){
wxString caption = wxT("Choose a file");
wxString wildcard = wxT("BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif");
wxString defaultDir = wxT("c:\\temp"));
wxString defaultFilename = wxEmptyString;
wxFileDialog dialog(parent, caption, defaultDir, defaultFilename, wildcard, wxOPEN);
if (dialog.ShowModal() == wxID_OK)
{
wxString path = dialog.GetPath();
int filterIndex = dialog.GetFilterIndex();
}
}
wxDirDialog
用来让用户选择一个本地或者网络文件夹.如果在构造函数中设置了可选类型wxDD_NEW_DIR_BUTTON,则对话框将显示一个用来创建新文件夹的按钮.
#include "wx/dirdlg.h"
wxString defaultPath = wxT("/");
wxDirDialog dialog(parent, wxT("Testing directory picker"), defaultPath, wxDD_NEW_DIR_BUTTON);
if (dialog.ShowModal() == wxID_OK)
{
wxString path = dialog.GetPath();
wxMessageBox(path);
}
wxColourDialog
这个对话框允许用户从标准颜色或者是一个颜色范围中选择一种颜色.
#include "wx/colordlg.h"
wxColourData data;
data.SetChooseFull(true);
for (int i = 0; i < 16; i++)
{
wxColour color(i*16, i*16, i*16);
data.SetCustomColour(i, color);
}
wxColourDialog dialog(this, &data);
if (dialog.ShowModal() == wxID_OK)
{
wxColourData retData = dialog.GetColourData();
wxColour col = retData.GetColour();
myWindow->SetBackgroundColour(col);
myWindow->Refresh();
}
wxFontDialog
可以让用户来选择一个字体(在某些平台上,还可以选择字体的颜色).
许用户选择的项目包括字体家族名称,大小,类型,粗细,下划线以及前景颜色等,还包括一个示例区用来显示当前字体的样子.这种字体选择框在各种平台都是可以使用的,类的名字为wxGenericFontDialog.
#include "wx/fontdlg.h"
void test(){
wxFontData data;
data.SetInitialFont(m_font);
data.SetColour(m_textColor);
wxFontDialog dialog(this, &data);
if (dialog.ShowModal() == wxID_OK)
{
wxFontData retData = dialog.GetFontData();
m_font = retData.GetChosenFont();
m_textColor = retData.GetColour();
// 更新当前窗口以便使用当前选择的字体和颜色进行重绘
myWindow->Refresh();
}
}
wxSingleChoiceDialog
wxSingleChoiceDialog给用户提供了一组字符串以便用户可以选择其中的一个.
#include "wx/choicdlg.h"
const wxArrayString choices;
choices.Add(wxT("One"));
choices.Add(wxT("Two"));
choices.Add(wxT("Three"));
choices.Add(wxT("Four"));
choices.Add(wxT("Five"));
wxSingleChoiceDialog dialog(this,
wxT("This is a small sample\nA single-choice convenience dialog"),
wxT("Please select a value"),
choices);
dialog.SetSelection(2);
if (dialog.ShowModal() == wxID_OK)
wxMessageBox(dialog.GetStringSelection(), wxT("Got string"));
wxMultiChoiceDialog
wxMultiChoiceDialog和wxSingleChoiceDialog很相似,不过它允许用户进行多项选择.
wxNumberEntryDialog
提示用户输入一个固定范围内的数字,这个对话框包含一个spin控件因此,用户既可以手动输入数字,也可以通过鼠标点击spin按钮来调整数字的值,这个对话框是wxWidgets自己实现的,因此在各个平台上的表现都是相似的.
#include "wx/numdlg.h"
wxNumberEntryDialog dialog(parent,
wxT("This is some text, actually a lot of text\nEven two rows of text"),
wxT("Enter a number:"), wxT("Numeric input test"), 50, 0, 100);
if (dialog.ShowModal() == wxID_OK)
{
long value = dialog.GetValue();
}
wxTextEntryDialog和wxPasswordEntryDialog
提供一个消息文本和一个单行文本框控件,以便用户可以输入文本,它们的功能很类似,只不过在wxPasswordEntryDialog中输入的文本被以掩码的方式显示,因此是不能直接看到的.
#include "wx/textdlg.h"
wxTextEntryDialog dialog(this,
wxT("This is a small sample\n")
wxT("A long, long string to test out the text entrybox"),
wxT("Please enter a string"),
wxT("Default value"),
wxOK | wxCANCEL);
if (dialog.ShowModal() == wxID_OK)
wxMessageBox(dialog.GetValue(), wxT("Got string"));
wxFindReplaceDialog
wxFindReplaceDialog是一个非模式对话框,它允许用户用来输入用于搜索的文本以及(如果需要的话)用来替换的文本.实际的搜索动作需要在其派生类或者其父窗口作为这个对话框某个按钮时间的响应来完成.和大多数标准对话框不同,这种对话框必须拥有一个父窗口(非空),并且这个对话框必须是非模式显示的
wxFindReplaceDialog对话框在用户点击其上的按钮的时候产生一些命令事件.事件处理函数采用wxFindDialogEvent类型的参数,事件映射宏中的窗口标识符为这个对话框的标识符,这些宏如下表所示:
EVT_FIND(id, func) | 当"查找"按钮被按下时产生. |
---|---|
EVT_FIND_NEXT(id, func) | 当"下一个"按钮被按下时产生. |
EVT_FIND_REPLACE(id, func) | 当"替换"按钮被按下时产生. |
EVT_FIND_REPLACE_ALL(id, func) | 当"替换全部"按钮被按下时产生. |
EVT_FIND_CLOSE(id, func) | 当用户通过取消或者别的途径关闭对话框的时候产生. |
自定义对话框
下面是通常你需要采取的步骤:
- 从wxDialog派生一个新类.
- 决定数据的存放位置以及应用程序以怎样的方式访问用户选择的数据.
- 编写代码来创建和布局相关控件.
- 增加代码以便在C++变量和控件之间进行数据传输.
- 增加事件映射和相应的处理函数以处理那些来自控件的事件.
- 增加用户界面更新处理函数,以便将控件设置为正确的状态.
- 增加帮助,尤其是工具提示以及上下文敏感帮助(在Mac OS中还没有实现),以及在你的应用程序的用户手册中添加你的对话框的使用方法.
- 在你的应用程序中调用你定制的对话框.
创建一个自定义对话框的许多步骤,可以通过使用一个对话框编辑器而变得简单.
依照wxWidgets的惯例,我们同时支持了单步创建和两步窗口的方式,单步创建使用的是一个复杂的构造函数,而两步创建则使用的是一个简单的额构造函数和一个复杂的Create函数.
class PersonalRecordDialog: public wxDialog
{
DECLARE_CLASS( PersonalRecordDialog )
DECLARE_EVENT_TABLE()
public:
// 构造函数
PersonalRecordDialog( );
PersonalRecordDialog( wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxString& caption = wxT("Personal Record"),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU );
//用来初始化内部变量
void Init();
// 创建窗体
bool Create( wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxString& caption = wxT("Personal Record"),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU );
// 创建普通控件和布局控件
void CreateControls();
};
当对话框第一次显示的时候,wxWidgets会调用InitDialog函数,这个函数产生一个wxEVT_INIT_DIALOG事件.
这个事件默认的处理函数是调用transferDataToWindow函数.要把数据从控件传回变量中,你可以在用户确认输入的时候调用 transferDataFromWindow函数,wxWidgets定义了一个默认的wxID_OK命令事件的处理函数,这个函数会在 EndModal函数调用之前调用transferDataFromWindow函数.
然而,还有一个更容易的方法.wxWidgets支持验证器,所谓验证器是一个把数据变量和对应的控件联系起来的对象.虽然不总是可以使用,但是只要可以使用,总是可以节省你大量的时间和代码来进行数据的传输和验证.
FindWindow(ID_NAME)->SetValidator( wxTextValidator(wxFILTER_ALPHA, & m_name));
FindWindow(ID_AGE)->SetValidator( wxGenericValidator(& m_age));
省略了Init和CreateControls 函数的实现。
上下文敏感帮助
#include "wx/cshelp.h"
wxHelpProvider::Set(new wxSimpleHelpProvider);
// 这将告诉wxWidgets怎样提供上下文敏感帮助,然后调用SetHelpText来设置某个控件的上下文敏感帮助文本.
// 下面是我们例子中设置这两中帮助的代码:
void PersonalRecordDialog::SetDialogHelp()
{
wxString nameHelp = wxT("Enter your full name.");
wxString ageHelp = wxT("Specify your age.");
wxString sexHelp = wxT("Specify your gender, male or female.");
wxString voteHelp = wxT("Check this if you wish to vote.");
FindWindow(ID_NAME)->SetHelpText(nameHelp);
FindWindow(ID_NAME)->SetToolTip(nameHelp);
FindWindow(ID_AGE)->SetHelpText(ageHelp);
FindWindow(ID_AGE)->SetToolTip(ageHelp);
FindWindow(ID_SEX)->SetHelpText(sexHelp);
FindWindow(ID_SEX)->SetToolTip(sexHelp);
FindWindow(ID_VOTE)->SetHelpText(voteHelp);
FindWindow(ID_VOTE)->SetToolTip(voteHelp);
}
调用这个窗口:
PersonalRecordDialog dialog(NULL, ID_PERSONAL_RECORD, wxT("Personal Record"));
dialog.SetName(wxEmptyString);
dialog.SetAge(30);
dialog.SetSex(0);
dialog.SetVote(true);
if (dialog.ShowModal() == wxID_OK)
{
wxString name = dialog.GetName();
int age = dialog.GetAge();
bool sex = dialog.GetSex();
bool vote = dialog.GetVote();
}
资源文件
你可以从一个Xml文件中加载对话框,frame窗口,菜单条,工具条等等,而不一定非要用C++代码来创建它们.
这更符合界面和代码分离的原则,它可以让应用程序的界面在运行期改变.
XRC文件可以通过一系列用户界面设计的工具导出,比如:wxDesigner, DialogBlocks, XRCed和wxGlade.
要使用XRC文件,你需要在你的代码中包含wx/xrc/xmlres.h头文件.
如果你打算将你的XRC文件转换成二进制的XRS文件(我们很快会介绍到),你还需要增加zip文件系统的处理函数,你可以在你的OnInit函数中增加下面的代码来作到这一点:
#include "wx/filesys.h"
#include "wx/fs_zip.h"
wxFileSystem::AddHandler(new wxZipFSHandler);
wxXmlResource::Get()->InitAllHandlers();
wxXmlResource::Get()->Load(wxT("resources.xrc"));
// 加载对话框
MyDialog dlg;
wxXmlResource::Get()->LoadDialog(& dlg, parent, wxT("dialog1"));
dlg.ShowModal();
MyFrame::MyFrame(const wxString& title): wxFrame(NULL, -1, title)
{
SetMenuBar(wxXmlResource::Get()->LoadMenuBar(wxT("mainmenu")));
SetToolBar(wxXmlResource::Get()->LoadToolBar(this, wxT("toolbar")));
wxMenu* menu = wxXmlResource::Get()->LoadMenu(wxT("popupmenu"));
wxIcon icon = wxXmlResource::Get()->LoadIcon(wxT("appicon"));
SetIcon(icon);
wxBitmap bitmap = wxXmlResource::Get()->LoadBitmap(wxT("bmp1"));
// 既可以先创建实例再加载
MyPanel* panelA = new MyPanel;
panelA = wxXmlResource::Get()->LoadPanel(panelA, this, wxT("panelA"));
// 又可以直接创建并加载
wxPanel* panelB = wxXmlResource::Get()->LoadPanel(this, wxT("panelB"));
}
wxWidgets维护一个全局的wxXmlResource对象,你可以直接拿来使用,也可以创建一个你自己的 wxXmlResource对象,然后加载某个资源文件,然后使用和释放它.你还可以使用wxXmlResource::Set函数来让应用程序用某个 wxXmlResource对象来取代全局资源对象,并释放掉那个旧的.
要为定义在资源文件中的控件定义事件表条目,你不能直接使用整数的标识符,因为资源文件中存放的其实是字符串,你需要使用XRCID 宏,它的参数是一个资源名,返回值是这个资源对应的标识符.
其实XRCID就是直接使用的wxXmlResource::GetXRCID函数,
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(XRCID("menu_quit"), MyFrame::OnQuit)
EVT_MENU(XRCID("menu_about"), MyFrame::OnAbout)
END_EVENT_TABLE()
你可以把多个wxWidgets资源文件编译成一个二进制的压缩的xrs文件.使用的工具wxrc可以在wxWidgets的utils/wxrc目录中找到,使用方法如下:
wxrc resource1.xrc resource2.xrc -o resource.xrs
使用wxXmlResource::Load
函数加载一个二进制的压缩的资源文件,和加载普通的文本Xml文件没有区别.
提示:
你可以不必把你的XRC文件单独制作一个zip压缩文件,而是把它放在其它一个可能包含HTML文件以及图片文件的普通的zip压缩文件中, wxXmlResource::Load函数支持虚拟文件系统定义(参考第14章:“文件和流”),因此你可以通过下面的方法来加载压缩文件中的XRC文件:
wxXmlResource::Get()->Load(wxT("resources.bin#zip:dialogs.xrc"));
图片管理
wxWidgets支持四种和位图相关的类:wxBitmap, wxIcon, wxCursor和wxImage.
wxImage拥有操作其图片上某些bit的能力,因此也可以用来对图片进行一个基本的操作.
和wxBitmap不同,wxImage不可以直接被设备上下文wxDC使用,如果要在wxDC上绘图,需要现将wxImage转换成wxBitmap,然后就可以使用wxDC的 DrawBitmap函数进行绘图了.
wxImage支持设置一个掩码颜色来实现透明的效果,也支持通过alpha通道实现非常复杂的透明效果.
所有的图片类都使用下表列出的标准的wxBitmapType标识符来读取或者保存图片数据:
wxBITMAP_TYPE_BMP | Windows位图文件 (BMP). |
---|---|
wxBITMAP_TYPE_BMP_RESOURCE | 从windows可执行文件资源部分加载的Windows位图. |
wxBITMAP_TYPE_ICO | Windows图标文件(ICO). |
wxBITMAP_TYPE_ICO_RESOURCE | 从windows可执行文件资源部分加载的Windows图标. |
wxBITMAP_TYPE_CUR | Windows光标文件(CUR). |
wxBITMAP_TYPE_CUR_RESOURCE | 从windows可执行文件资源部分加载的Windows光标. |
wxBITMAP_TYPE_XBM | Unix平台上使用的XBM单色图片. |
wxBITMAP_TYPE_XBM_DATA | 从C++数据中构造的XBM单色位图. |
wxBITMAP_TYPE_XPM | XPM格式图片,最好的支持跨平台并且支持编译到应用程序中去的格式. |
wxBITMAP_TYPE_XPM_DATA | 从C++数据中构造的XPM图片. |
wxBITMAP_TYPE_TIF | TIFF格式位图,在大图片中使用比较普遍. |
wxBITMAP_TYPE_GIF | GIF格式图片,最多支持256中颜色,支持透明. |
wxBITMAP_TYPE_PNG | PNG位图格式, 一个使用广泛的图片格式,支持透明和alpha通道,没有版权问题. |
wxBITMAP_TYPE_JPEG | JPEG格式位图, 一个广泛使用的压缩图片格式,支持大图片,不过它的压缩算法是有损耗压缩,因此不适合对图片进行反复加载和压缩. |
wxBITMAP_TYPE_PCX | PCX图片格式. |
wxBITMAP_TYPE_PICT | Mac PICT位图. |
wxBITMAP_TYPE_PICT_RESOURCE | 从可执行文件资源部分加载的Mac PICT位图. |
wxBITMAP_TYPE_ICON_RESOURCE | 仅在Mac OS X平台上有效, 用来加载一个标准的图标(比如wxICON_INFORMATION)或者一个图标资源. |
wxBITMAP_TYPE_ANI | Windows动画图标(ANI). |
wxBITMAP_TYPE_IFF | IFF位图文件. |
wxBITMAP_TYPE_MACCURSOR | Mac光标文件. |
wxBITMAP_TYPE_MACCURSOR_RESOURCE | 从可执行文件资源部分加载的Mac光标. |
wxBITMAP_TYPE_ANY | 让加载图片的代码自己确定图片的格式. |
demo
// 使用当前的颜色深度创建一个200x100的位图
wxBitmap bitmap(200, 100, -1);
// 创建一个内存设备上下文
wxMemoryDC dc;
// 将创建的图片和这个内存设备上下文关联
dc.SelectObject(bitmap);
// 设置背景颜色
dc.SetBackground(*wxWHITE_BRUSH);
// 绘制位图背景
dc.Clear();
// 解除设备上下文和位图的关联
dc.SelectObject(wxNullBitmap);
// 从文件加载
wxBitmap bitmap(wxT("picture.png", wxBITMAP_TYPE_PNG);
// 加载一幅图像
wxImage image(wxT("image.png"), wxBITMAP_TYPE_PNG);
// 将其转换成bitmap
wxBitmap bitmap(image);
// 加载一个图标
wxIcon icon(wxT("image.xpm"), wxBITMAP_TYPE_XPM);
// 将其转换成位图
wxBitmap bitmap;
bitmap.CopyFromIcon(icon);
打包位图资源
个可移植的方法是,你可以将你用到的所有数据文件,包括HTML网页,图片或者别的任何类型的文件压缩在一个zip文件里,然后你可以用wxWidgets提供的虚拟文件系统对加载这个zip文件的其中任何一个或几个文件
// 创建一个文件系统
wxFileSystem*fileSystem = new wxFileSystem;
wxString archiveURL(wxT("myapp.bin"));
wxString filename(wxT("myimage.png"));
wxBitmapType bitmapType = wxBITMAP_TYPE_PNG;
// 创建一个URL
wxString combinedURL(archiveURL + wxString(wxT("#zip:")) + filename);
wxImage image;
wxBitmap bitmap;
// 打开压缩包中的对应文件
wxFSFile* file = fileSystem->OpenFile(combinedURL);
if (file)
{
wxInputStream* stream = file->GetStream();
// Load and convert to a bitmap
if (image.LoadFile(* stream, bitmapType))
bitmap = wxBitmap(image);
delete file;
}
delete fileSystem;
if (bitmap.Ok())
{
...
}
ICON
#include "myicon.xpm"
wxIcon icon(myicon_xpm);
// 1: 设置窗口图标
frame->SetIcon(icon);
// 2: 增加到wxImageList
wxImageList* imageList = new wxImageList(16, 16);
imageList->Add(icon);
// 3: 在(10, 10)的位置绘制
wxClientDC dc(window);
dc.DrawIcon(icon, 10, 10);
Windows平台 xxx.rc 文件 icon
aardvarkpro ICON aardvarkpro.ico
#include "wx/msw/wx.rc"
CURSOR
window->SetCursor(wxCursor(wxCURSOR_WAIT));
// 用wxImage创建光标
wxImage image(wxT("cursor.png"), wxBITMAP_TYPE_PNG);
image.SetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_X, 5);
image.SetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 5);
wxCursor cursor(image);
BEGIN_EVENT_TABLE(wxSplitterWindow, wxWindow)
EVT_SET_CURSOR(wxSplitterWindow::OnSetCursor)
END_EVENT_TABLE()
// 指示光标只应该被设置给分割条
void wxSplitterWindow::OnSetCursor(wxSetCursorEvent& event)
{
if ( SashHitTest(event.GetX(), event.GetY(), 0) )
{
// 使用默认的处理
event.Skip();
}
//else:什么也不作,换句话说,不调用Skip.则事件表不会被继续搜索
}
XImage
其中wxBMPHandler是默认支持的,而要支持其它的图形格式处理,就需要使用 wxImage::AddHandler函数增加对应的图形处理过程或者使用wxInitAllImageHandlers增加所有支持的图形处理过程.
#include "wx/image.h"
wxImage::AddHandler( new wxPNGHandler );
wxImage::AddHandler( new wxJPEGHandler );
wxImage::AddHandler( new wxGIFHandler );
wxImage::AddHandler( new wxXPMHandler );
各种功能
// 设置一个合理的质量压缩比
image.SetOption(wxIMAGE_OPTION_QUALITY, 80);
image.SaveFile(wxT("picture.jpg"), wxBITMAP_TYPE_JPEG);
// 保存XPM到流格式
image.SetOption(wxIMAGE_OPTION_FILENAME, wxT("myimage"));
image.SaveFile(stream, wxBITMAP_TYPE_XPM);
// 透明色
// 创建一个有颜色掩码的wxBitmap
// 首先,在这个wxBitmap上绘画
wxBitmap bitmap(400, 400);
wxMemoryDC dc;
dc.SelectObject(bitmap);
dc.SetBackground(*wxBLUE_BRUSH);
dc.Clear();
dc.SetPen(*wxRED_PEN);
dc.SetBrush(*wxRED_BRUSH);
dc.DrawRectangle(50, 50, 200, 200);
dc.SelectObject(wxNullBitmap);
// 将其转换成wxImage
wxImage image = bitmap.ConvertToImage();
// 设置掩码颜色
image.SetMaskColour(255, 0, 0);
// 把原始图片缩放到200x200,并保存在新的图片里
// 原图保持不变.
wxImage image2 = image1.Scale(200, 200);
// 将原图缩放到200x200
image1.Rescale(200, 200);
// 旋转固定角度产生新图片.
// 原图片保持不变.
wxImage image2 = image1.Rotate(0.5);
// 顺时针旋转90度产生新图片.
// 原图保持不变.
wxImage image2 = image1.Rotate90(true);
// 水平镜像产生新图片.
// 原图保持不变.
wxImage image2 = image1.Mirror(true);
剪贴板
wxClipboardLocker locker;
if (!locker)
{
// ... 报告错误然后返回 ...
}
// ... 使用剪贴板 ...
// 拷贝一些文本到剪贴板
if (wxTheClipboard->Open())
{
// 数据对象将被剪贴板释放,
// 因此不在要你的应用程序中释放它们.
wxTheClipboard->SetData(new wxTextDataObject(wxT("Some text")));
wxTheClipboard->Close();
}
// 从剪贴板获取一些文本
if (wxTheClipboard->Open())
{
if (wxTheClipboard->IsSupported(wxDF_TEXT))
{
wxTextDataObject data;
wxTheClipboard->GetData(data);
wxMessageBox(data.GetText());
}
wxTheClipboard->Close();
}
// 将一副图片拷贝到剪贴板
wxImage image(wxT("splash.png"), wxBITMAP_TYPE_PNG);
wxBitmap bitmap(image.ConvertToBitmap());
if (wxTheClipboard->Open())
{
// 数据对象将被剪贴板释放,
// 因此不在要你的应用程序中释放它们.
wxTheClipboard->SetData(new wxBitmapDataObject(bitmap));
wxTheClipboard->Close();
}
// 从剪贴板读取一幅图片
if (wxTheClipboard->Open())
{
if (wxTheClipboard->IsSupported(wxDF_BITMAP))
{
wxBitmapDataObject data;
wxTheClipboard->GetData( data );
bitmap = data.GetBitmap();
}
wxTheClipboard->Close();
}
拖放
一个拖放源需要采取的动作包括下面几步:
- 准备工作
首先,必须先创建和初始化一个将被拖动的数据对象,如下所示:
wxTextDataObject myData(wxT("This text will be dragged."));
- 开始拖动要开始拖动操作,最典型的方式是响应一个鼠标单击事件,创建一个wxDropSource对象,然后调用它的wxDropSource::DoDragDrop函数,如下所示:
wxDropSource dragSource(this);
dragSource.SetData(myData);
wxDragResult result = dragSource.DoDragDrop(wxDrag_AllowMove);
下表列出的标记,可以作为DoDragDrop函数的参数:
wxDrag_CopyOnly | 只允许进行拷贝操作. |
---|---|
wxDrag_AllowMove | 允许进行移动操作. |
wxDrag_DefaultMove | 默认操作是移动数据. |
当创建wxDropSource对象的时候,你还可以指定发起拖动操作的窗口,并且可以选择拖动使用的光标,可选的范围包括拷贝,移动以及不能释放等. | |
这些光标在GTK+上是图标,而在别的平台上是光标,因此你需要使用wxDROP_ICON来屏蔽这种区别,正如我们很快就将看到的拖动文本的例子中演示的那样. |
-
拖动
对DoDragDrop函数的调用将会阻止应用程序进行其他处理,直到用户释放鼠标按钮(除非你重载了GiveFeedback函数以便进行其他的特殊操作)当鼠标在应用程序的一个窗口上移动时,如果这个窗口可以识别这个拖动操作协议,对应的wxDropTarget函数就会被调用,参考接下来的小节"实现一个拖放目的" -
处理拖放结果
DoDragDrop函数返回一个拖放操作的结果,这
高级控件
- wxTreeCtrl; 这个控件用来帮助你为分等级的数据建模.
- wxListCtrl; 这个控件让你以灵活的方式显示一组文本标签和图标.
- wxWizard; 这个控件使用多个页面对某个特定的任务提供向导机制.
- wxHtmlWindow; 你可以在"关于"对话框和报告对话框(以及其他你可以想到的对话框)中使用的轻量级的HTML显示控件.
- wxGrid; 网格控件用是一个支持多种特性的标状数据显示控件.
- wxTaskBarIcon; 这个控件让你的程序可以很容易的访问系统托盘区或者类似的区域.
wxHTML
wxRichTextCtrl
wxStyledTextCtrl
wxAUI
wxPropertyGrid
wxTipProvider
wxBookCtrl
- wxChoicebook: controlled by a wxChoice
- wxListbook: controlled by a wxListCtrl
- wxNotebook: uses a row of tabs
- wxSimplebook: doesn’t allow the user to change the page at all.
- wxTreebook: controlled by a wxTreeCtrl
- wxToolbook: controlled by a wxToolBar
wxDateTime
wxHashMap
wxList
wxNode
wxArray
wxString
wxChar
wxObject
wxRegEx
wxLongLong
wxPoint
wxSize
wxRealPoint
wxRect
wxRegion
wxVariant 可以存储的数据包括bool, char, double, long, wxString, wxArrayString, wxList, wxDateTime, void* 和可变类型变量列表.
文件和流
wxWidgets提供了一系列的平台无关的文件处理功能.
- wxFile和wxFFile
- wxTextFile
- wxTempFile
- wxDir
- wxFileName
还有很多静态函数:
函数 | 作用 |
---|---|
wxDirExists(dir) | 是否目录存在. 参考wxFileName::DirExists |
wxConcatFiles(f1, f2, f3) | 将f1和f2合并为f3, 成功时返回True. |
wxCopyFile(f1, f2, overwrite) | 拷贝f1到f2,可选择是否覆盖已存在的f2.返回Bool型 |
wxFileExists(file) | 测试是否文件存在. 参考wxFileName::FileExists |
wxFileModificationTime(file) | 返回文件修改时间(time_t类型). 参考wxFileName::GetModificationTime,它返回wxDateTime类型 |
wxFileNameFromPath(file) | 返回文件全路径的文件名部分. 推荐使用wxFileName::SplitPath函数 |
wxGetCwd() | 返回当前工作目录. 参考wxFileName::GetCwd |
wxGetdiskSpace (path, total, free) | 返回指定路径所在的磁盘的全部空间和空闲空间,空间为wxLongLong类型. |
wxIsAbsolutePath(path) | 测试指定的路径是否为绝对路径. |
wxMkdir(dir, permission=777) | 创建一个目录,其父目录必须存在,可选指定目录访问掩码.返回bool型 |
wxPathOnly(path) | 返回给定全路径的目录部分. |
wxRemoveFile(file) | 删除文件,成功时返回True. |
wxRenameFile(file1, file2) | 重命名文件,成功时返回True.如果需要进行文件拷贝,则直接返回False. |
wxRmdir(file) | 移除空目录,成功时返回True. |
wxSetWorkingDirectory(file) | 设置当前工作目录,返回bool型. |
wxWidgets同样提供了许多函数来封装标准C函数,比如: wxFopen, wxFputc和wxSscanf,这些函数没有在手册中记录,不过你应该可以在include/wx/wxchar.h
中找到它们.
另外一个有用的宏是wxFILE_SEP_PATH,它代表了不同平台上的路径分割符,比如在windows平台上它代表"\"
,而在Unix平台上则代表"/"
.
wxStreamBase类是所有流类的基类,它声明的函数包括类似OnSysRead和OnSysWrite等需要继承类实现的函数.其子类wxInputStream和wxOutputStream则提供了更具体的流类(比如wxFileInputStream和 wxFileOutputStream子类)共同需要的用于读写操作的基本函数.
- wxFileInputStream和wxFileOutputStream 基于wxFile类实现的
- wxFFileInputStream和wxFFileOutputStream 基于wxFFile类
- wxMemoryInputStream和wxMemoryOutputStream 使用内部缓冲区来处理流数据
- wxStringInputStream则采用一个wxString引用作为构造参数来进行数据读取
stream需要使用下面四个类来以一个更高的层级处理数据:
wxTextInputStream, wxTextOutputStream, wxDataInputStream和wxDataOutputStream.这些类通过别的流类型类构造,它们提供了操作更高级的C++数据类型的方法.
Socket流 wxSocketOutputStream和wxSocketInputStream是通过wxSocket对象构造的,
wxFilterInputStream和wxFilterOutputStream是过滤器流对象的基类,过滤器流对象是一种特殊的流对象,它用来将过滤后的数据输入到其它的流对象. wxZlibInputStream是一个典型的过滤器流对象.
#include "wx/mstream.h"
#include "wx/zstream.h"
const char* buf =
"01234567890123456789012345678901234567890123456789";
// 创建一个写入wxMemoryOutputStream对象的wxZlibOutputStream类
wxMemoryOutputStream memStreamOut;
wxZlibOutputStream zStreamOut(memStreamOut);
// 压缩buf以后写入wxMemoryOutputStream
zStreamOut.Write(buf, strlen(buf));
// 获取写入的大小
int sz = memStreamOut.GetSize();
// 分配合适大小的缓冲区
// 拷贝数据
unsigned char* data = new unsigned char[sz];
memStreamOut.CopyTo(data, sz);
wxZipInputStream是一个更复杂一点的流对象,因为它是以文档的方式而不是线性的二进制数据的方式工作的.事实上,文档是通过另外的类wxArchiveClassFactory和wxArchiveEntry来处理的,但是你可以不用关心这些细节.
虚拟文件系统
wxWidgets提供了一套虚拟文件系统机制,让你的应用程序可以象使用普通文件那样使用包括zip文件中的文件,内存文件以及HTTP或FTP协议这样的特殊数据.
这种虚拟文件机制通常是只读的,意味着你不可以修改其中的内容.
各种不同的虚拟文件系统类都继承自wxFileSystemHandler类,要在应用程序中使用某个特定实现,需要在程序的某个地方 (通常是OnInit函数中)调用wxFileSystem::AddHandler函数.
- 虚拟文件系统, 格式应该为
"file:/<主机名>//<文件名>"
- 虚拟内存, wxMemoryFSHandler 允许你将数据保存在内存中并且通过内存协议在虚拟文件系统中使用它
- 虚拟网络文件, wxInternetFSHandler,它支持FTP和HTTP协议.
// "#"号前面的部分是文件名,后面的部分则是虚拟文件系统类型以及文件在虚拟文件系统中的路径.
<img src="file:myapp.bin#zip:images/logo.png">
<object class="wxBitmapButton">
<bitmap>file:myapp.bin#zip:images/fuzzy.gif</bitmap>
</object>
// 内存
<object class="wxBitmapButton">
<bitmap>memory:default.png</bitmap>
</object>
代码方式访问:
#include "wx/fs_zip.h"
#include "wx/filesys.h"
#include "wx/wfstream.h"
// 这一行代码只应该被执行依次,最好是在应用程序初始化的时候
wxFileSystem::AddHandler(new wxZipFSHandler);
wxFileSystem* fileSystem = new wxFileSystem;
wxString archive = wxT("file:///c:/myapp/myapp.bin");
wxString filename = wxT("images/logo.png");
wxFSFile* file = fileSystem->OpenFile(archive + wxString(wxT("#zip:")) + filename);
if (file)
{
wxInputStream* stream = file->GetStream();
wxImage image(* stream, bitmapType);
wxBitmap bitmap = wxBitmap(image);
delete file;
}
delete fileSystem;
wxThread
如果你要在你的代码中使用线程,首先要实现一个wxThread的派生类,并且至少要重载其虚函数Entry,这个函数包含了线程要做的主要的事情.
class MyThread : public wxThread
{
public:
MyThread(wxImage* image, int* count):
m_image(image), m_count(count) {}
virtual void *Entry();
private:
wxImage* m_image;
int* m_count;
};
// 一个标识符 用来在线程工作完成的时候通知应用程序.
#define ID_COUNTED_COLORS 100
void *MyThread::Entry()
{
(* m_count) = m_image->CountColours();
// 使用一个已知的事件来通知应用程序.
wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED,
ID_COUNTED_COLORS);
wxGetApp().AddPendingEvent(event);
return NULL;
}
// 使用
MyThread *thread = new MyThread();
if ( thread->Create() != wxTHREAD_NO_ERROR )
{
wxLogError(wxT("Can't create thread!"));
}
thread->Run();
同步手段:
- wxMutex
- wxCriticalSection
- wxCondition
- wxSemaphore
替代方案:
- wxTimer
#define TIMER_ID 1000
class MyFrame : public wxFrame
{
public:
...
void OnTimer(wxTimerEvent& event);
private:
wxTimer m_timer;
};
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_TIMER(TIMER_ID, MyFrame::OnTimer)
END_EVENT_TABLE()
MyFrame::MyFrame()
: m_timer(this, TIMER_ID)
{
// 1秒的间隔
m_timer.Start(1000);
}
void MyFrame::OnTimer(wxTimerEvent& event)
{
// 你可以在这里作任何你希望1秒执行一次的动作
}
- 空闲时间处理
// 任何窗口都有 idle
class MyFrame : public wxFrame
{
public:
...
void OnIdle(wxIdleEvent& event);
// 作一小部分工作,如果做完则返回True
bool FinishedIdleTask();
};
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_IDLE(MyFrame::OnIdle)
END_EVENT_TABLE()
void MyFrame::OnIdle(wxIdleEvent& event)
{
// 进行空闲事件处理,如果没有处理完
// 再次请求空闲事件
if (!FinishedIdleTask())
event.RequestMore();
event.Skip();
}
// 控件的idle 叫 OnInternalIdle
void wxImageCtrl::OnInternalIdle()
{
wxControl::OnInternalIdle();
if (m_needResize)
{
m_needResize = false;
SizeContent();
}
}
void wxImageCtrl::OnSize(wxSizeEvent& event)
{
m_needResize = true;
}
wxSocket
wxWidgets还不支持UDP协议的数据收发
socket操作的核心类是wxSocketBase,它提供了类似发送和接收数据,关闭连接,错误报告等这样的功能.创建一个监听socket或者连接到一个socket服务器,你需要分别使用wxSocketServer和wxSocketClient.wxSocketEvent用来通知应用程序
虚类wxSocketBase和它的一些子类比如wxIPV4address让你可以指定特定的远端地址和端口.最后, wxSocketInputStream和wxSocketOutputStream等这些流对象让你以流的方式处理socket上的数据移动和传输.
tcp server
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(SERVER_START, MyFrame::OnServerStart)
EVT_SOCKET(SERVER_ID, MyFrame::OnServerEvent)
EVT_SOCKET(SOCKET_ID, MyFrame::OnSocketEvent)
END_EVENT_TABLE()
void MyFrame::OnServerStart(wxCommandEvent& WXUNUSED(event))
{
// 创建地址,默认为localhost:0
wxIPV4address addr;
addr.Service(3000);
// 创建一个Socket,保留其地址以便我们可以在需要的时候关闭它.
m_server = new wxSocketServer(addr);
// 检查Ok函数以判断服务器是否正常启动
if (! m_server->Ok())
{
return;
}
// 设置我们需要监视的事件
m_server->SetEventHandler(*this, SERVER_ID);
m_server->SetNotify(wxSOCKET_CONNECTION_FLAG);
m_server->Notify(true);
}
void MyFrame::OnServerEvent(wxSocketEvent& WXUNUSED(event))
{
// 接受连接请求,并创建Socket
wxSocketBase* sock = m_server->Accept(false);
// 告诉这个新的Socket它的事件应该被谁处理
sock->SetEventHandler(*this, SOCKET_ID);
sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
sock->Notify(true);
}
void MyFrame::OnSocketEvent(wxSocketEvent& event)
{
wxSocketBase *sock = event.GetSocket();
// 处理事件
switch(event.GetSocketEvent())
{
case wxSOCKET_INPUT:
{
char buf[10];
// 读数据
sock->Read(buf, sizeof(buf));
// 写回数据
sock->Write(buf, sizeof(buf));
// 服务器接受的这个socket已经完成任务,释放它
sock->Destroy();
break;
}
case wxSOCKET_LOST:
{
sock->Destroy();
break;
}
}
}
tcp client
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(CLIENT_CONNECT, MyFrame::OnConnectToServer)
EVT_SOCKET(SOCKET_ID, MyFrame::OnSocketEvent)
END_EVENT_TABLE()
void MyFrame::OnConnectToServer(wxCommandEvent& WXUNUSED(event))
{
wxIPV4address addr;
addr.Hostname(wxT("localhost"));
addr.Service(3000);
// 创建Socket
wxSocketClient* Socket = new wxSocketClient();
// 设置要监视的Socket事件
Socket->SetEventHandler(*this, SOCKET_ID);
Socket->SetNotify(wxSOCKET_CONNECTION_FLAG |
wxSOCKET_INPUT_FLAG |
wxSOCKET_LOST_FLAG);
Socket->Notify(true);
// 等待连接事件
Socket->Connect(addr, false);
}
void MyFrame::OnSocketEvent(wxSocketEvent& event)
{
// 从事件获取socket
wxSocketBase* sock = event.GetSocket();
// 所有事件共享的一块缓冲(Common buffer shared by the events)
char buf[10];
switch(event.GetSocketEvent())
{
case wxSOCKET_CONNECTION:
{
// 填充'0'-'9'的ASCII码
char mychar = '0';
for (int i = 0; i < 10; i++)
{
buf[i] = mychar++;
}
// 发送10个字符到对端
sock->Write(buf, sizeof(buf));
break;
}
case wxSOCKET_INPUT:
{
sock->Read(buf, sizeof(buf));
break;
}
// 服务器在发送10个字节以后关闭了连接
case wxSOCKET_LOST:
{
sock->Destroy();
break;
}
}
}
如果你只是想进行FTP或者 HTTP的操作,你可以直接使用wxFTP或wxHTTP,它们在内部使用了wxSocket,不过这些类是不完善的,你最好还是使用CURL,它是一个通用的库,提供了使用各种Internet协议传递文件的非常直观的API,有人已经对其进行了wxWidgets封装,名字叫做wxCURL.
文档视图框架
MDI
略…
其他
单例
bool MyApp::OnInit()
{
const wxString name = wxString::Format(wxT("MyApp-%s"),
wxGetUserId().c_str());
m_checker = new wxSingleInstanceChecker(name);
if ( m_checker->IsAnotherRunning() )
{
wxLogError(_("Program already running, aborting."));
return false;
}
... more initializations ...
return true;
}
int MyApp::OnExit()
{
delete m_checker;
return 0;
}
进程间通信
include "wx/ipc.h"
// Server类,用来监听连接请求
class stServer: public wxServer
{
public:
wxConnectionBase *OnAcceptConnection(const wxString& topic);
};
// Client类,在OnInit函数中被后来的实例使用
class stClient: public wxClient
{
public:
stClient() {};
wxConnectionBase *OnMakeConnection() { return new stConnection; }
};
// Connection类,被两个实例同时使用以实现通讯
class stConnection : public wxConnection
{
public:
stConnection() {}
~stConnection() {}
bool OnExecute(const wxString& topic, wxChar*data, int size,
wxIPCFormat format);
};
// 接收到了来自别的实例的连接请求
wxConnectionBase *stServer::OnAcceptConnection(const wxString& topic)
{
if (topic.Lower() == wxT("myapp"))
{
// 检查没有活动的模式对话框
wxWindowList::Node* node = wxTopLevelWindows.GetFirst();
while (node)
{
wxDialog* dialog = wxDynamicCast(node->GetData(), wxDialog);
if (dialog && dialog->IsModal())
{
return false;
}
node = node->GetNext();
}
return new stConnection();
}
else
return NULL;
}
命令行解析
#include "wx/cmdline.h"
static const wxCmdLineEntryDesc g_cmdLineDesc[] =
{
{ wxCMD_LINE_SWITCH, wxT("h"), wxT("help"), wxT("displays help on the command line
parameters") },
{ wxCMD_LINE_SWITCH, wxT("v"), wxT("version"), wxT("print version") },
{ wxCMD_LINE_OPTION, wxT("d"), wxT("debug"), wxT("specify a debug level") },
{ wxCMD_LINE_PARAM, NULL, NULL, wxT("input file"), wxCMD_LINE_VAL_STRING,
wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_NONE }
};
bool MyApp::OnInit()
{
// 分析命令行
wxString cmdFilename;
wxCmdLineParser cmdParser(g_cmdLineDesc, argc, argv);
int res;
{
wxLogNull log;
// 传递False参数以便在分析命名行发生错误的时候不显示使用帮助对话框.
res = cmdParser.Parse(false);
}
// 检查是否用户正在询问使用帮助
if (res == -1 || res > 0 || cmdParser.Found(wxT("h")))
{
cmdParser.Usage();
return false;
}
// 检查是否用户正在询问版本号
if (cmdParser.Found(wxT("v")))
{
#ifndef __WXMSW__
wxLog::SetActiveTarget(new wxLogStderr);
#endif
wxString msg;
wxString date(wxString::FromAscii(__DATE__));
msg.Printf(wxT("Anthemion DialogBlocks, (c) Julian Smart, 2005 Version %.2f, %s"),
wbVERSION_NUMBER, (const wxChar*) date);
wxLogMessage(msg);
return false;
}
// 检查是否用户希望以调试模式启动
long debugLevel = 0;
if (cmdParser.Found(wxT("d"), & debugLevel))
{
}
// 检查是否用户传递了一个工程名
if (cmdParser.GetParamCount() > 0)
{
cmdFilename = cmdParser.GetParam(0);
// 在windows系统上,如果通过资源管理器打开一个文件的时候,
// 传递的是短格式的文件名
// 因此我们可以把它变成长格式文件名
wxFileName fName(cmdFilename);
fName.Normalize(wxPATH_NORM_LONG|wxPATH_NORM_DOTS|
wxPATH_NORM_TILDE|wxPATH_NORM_ABSOLUTE);
cmdFilename = fName.GetFullPath();
}
...
return true;
}
调用其他exe
// 异步执行程序(默认行为),函数将会立即返回.
wxExecute(wxT("c:\\windows\\notepad.exe"));
// 同步执行程序,函数在Notepad程序退出以后才会返回.
wxExecute(wxT("c:\\windows\\notepad.exe c:\\temp\\temp.txt"), wxEXEC_SYNC);
// 根据文件类型打开某个文件
wxString url = wxT("c:\\home\\index.html");
bool ok = false;
wxFileType *ft = wxTheMimeTypesManager->GetFileTypeFromExtension(wxT("html"));
if ( ft )
{
wxString cmd;
ok = ft->GetOpenCommand(&cmd, wxFileType::MessageParameters(url, wxEmptyString));
delete ft;
if (ok)
{
ok = (wxExecute(cmd, wxEXEC_ASYNC) != 0);
}
}
配置信息读写
所有wxWidgets提供的用于处理配置数据的类都是wxConfigBase的派生类,因此你可以在这个基类的手册中找到相关的使用方法.
而wxConfig则被定义为各个平台上推荐使用的用于处理配置数据的类:
- 在windows平台,它被定义为wxRegConfig(这个类使用 windows的注册表),
- 在所有别的平台上它被定义为wxFileConfig(它使用文本文件).
另外还有wxIniConfig类,它使用一个 Windows 3.1风格的.ini配置文件,不过这个类很少被使用到. 而wxFileConfig则可以支持各个平台.
#include "wx/config.h"
// Windows: HKEY_CURRENT_USER/Software/Acme/MyApp 被创建
// Unix: ~/.MyApp 文件夹
// MacOS: /Library/Preferences/MyApp/Preferences
wxConfig config(wxT("MyApp"), wxT("Acme"));
// 读取
wxString str;
if (config.Read(wxT("General/DataPath"), & str))
{
...
}
bool useToolTips = false;
config.Read(wxT("General/ToolTips"), & useToolTips));
long usageCount = 0;
config.Read(wxT("General/Usage"), & usageCount));
// 写入
config.Write(wxT("General/DataPath"), str))
config.Write(wxT("General/ToolTips"), useToolTips));
config.Write(wxT("General/Usage"), usageCount));
UI部分,可以通过 wxPropertySheetDialog 来关联配置。wxListbook、wxChoicebook、wxNotebook。
High DPI Support
wxWidgets:wxWidgets 中的高 DPI 支持 — wxWidgets: High DPI Support in wxWidgets
对于用户来说,DPI 通常使用缩放因子表示,基线 DPI 值乘以该因子。
例如,MSW 系统可能使用 125% 或 150% 的缩放,这意味着它们分别使用 120 或 144 的 DPI,因为基线 DPI 值为 96。同样,Linux 系统可能使用“2 倍”缩放,导致 DPI 值为 192。Mac 略有不同,因为即使它们也可能使用“2 倍”缩放。
如果以与普通显示器相同的方式处理高 DPI 显示器,则现有应用程序看起来会很小。例如,一个 500 像素大小的方形窗口在垂直方向上占用标准 1920×1080(“全高清”)显示器的一半,但在 3840×2160(“4K UHD”)显示器上只占用四分之一。
为了防止这种情况发生,大多数平台在高 DPI 显示器上显示窗口时,会通过上面定义的缩放因子自动缩放窗口。自动缩放很方便,但并不能真正允许应用程序使用显示器上可用的额外像素,这意味着缩放的应用程序看起来模糊不清。
一个更好的解决方案来解释高 DPI 显示器上的像素值:一个允许缩放某些像素值(例如总窗口大小)的解决方案,但不允许缩放其他一些像素值(例如,用于绘图的像素值,这些像素值应保持不缩放以使用全部可用分辨率)。
问题:
当使用高 DPI 显示器时,所有坐标和大小都以 wxWidgets API 表示的逻辑像素在所有平台上并不具有相同的含义。
虽然在 macOS 上您始终可以传入 (500,500) 的大小来创建上一段中的窗口,但无论显示器的分辨率如何,当您在 200% 显示器上工作时,您必须在 MSW 上将其增加到 (1000,1000)。
为了在应用程序中隐藏这种差异,wxWidgets 提供了与设备无关的像素,缩写为“DIP”,这些像素在所有显示器和所有平台上始终具有相同的大小。
因此,在准备应用程序以支持高 DPI 时,首先要做的是停止使用原始像素值,因为当使用 DPI 缩放时,它们在不同的平台上意味着不同的内容。
实际上,不建议使用任何像素值,最好将它们替换为基于文本度量的值,即使用 wxWindow::GetTextExtent() 获取的值,或以对话框单位表示它们(参见 wxWindow::ConvertDialogToPixels())。然而,最简单的更改是将像素值替换为 DIP 中的值:为此,只需使用 wxWindow::FromDIP() 从一个转换为另一个。
// 旧的代码
myFrame->SetClientSize(wxSize(400, 300));
// HDPI 写法
myFrame->SetClientSize(myFrame->FromDIP(wxSize(400, 300)));
// or
const wxSize sizeM = myFrame->GetTextExtent("M");
myFrame->SetClientSize(100*sizeM.x, 40*sizeM.y));
例外:
- 窗口和 wxDC 或 wxGraphicsContext 坐标必须位于逻辑像素中,这些像素可能取决于当前的 DPI 缩放,因此在编译时不应固定。
- wxWidgets API 中的几乎所有函数都采用并返回以逻辑像素表示的值。wxBitmap是物理像素单位。
为了真正从高 DPI 设备上增加的细节中受益,您还需要以更高的分辨率提供应用程序使用的图像或图稿。
方法1: 使用多个分辨率的图片
使用 wxBitmapBundle 类来表示同一位图的多个不同版本,在 wxWidgets API 中接受 wxBitmap 或 wxImage 的大多数函数都已更新为使用 wxBitmapBundle,这允许库根据当前 DPI 选择适当的大小。
wxBitmap normal(32, 32);
wxBitmap highDPI(64, 64);
... initialize the bitmaps somehow ...
wxBitmapBundle bundle = wxBitmapBundle::FromBitmaps(normal, highDPI);
对于通常在资源中嵌入位图的平台,可以使用 wxBitmapBundle::FromResources() 创建一个包含使用给定基本名称的所有位图的捆绑包,即 foo
表示普通位图,foo_2x
表示 200% 缩放的位图(对于小数值,小数分隔符必须替换为下划线,例如,使用 foo_1_5x
表示位图使用 150% 缩放)。
也可以使用与 wxBitmapBundle::FromFiles() 相同的命名约定从文件创建 wxBitmapBundle。
无论捆绑包的创建方式如何,它都将提供最接近当前 DPI 预期大小的位图,同时尽量避免缩放它。
方法2:使用svg等矢量格式
使用单个 SVG 图像并使用 wxBitmapBundle::FromSVG() 或 wxBitmapBundle::FromSVGFile() 创建位图包。此类位图包将始终以任何分辨率生成所需大小的位图。
Log
wx自带log系统,wxLog 类定义了日志目标的标准接口,以及它的几个标准实现和一系列与之一起使用的函数。
一般不需要知道wxLog相关内容,直接使用wxLogXXX
一类的函数,它们都具有与 printf() 或 vprintf() 相同的语法
- wxLogFatalError 弹窗,并且会导致程序退出
- wxLogError 弹出一个消息框,提示用户
- wxLogWarning 展示给用户,但是不会打断用户行为。
- wxLogMessage 一般的消息,可以选择是否展示给用户。
- wxLogVerbose 详细输出,一般不展示给用户
- wxLogStatus 显示状态消息,一般与wxFrame的状态栏关联
- wxLogSysError 一般给wxWidgets自身使用,记录失败的API调用
- wxLogDebug 只在调试模式
__WXDEBUG__
下执行,release模式没有效果 - wxLogTrace 与wxLogDebug一样,但是信息更多
wxLog* logger = new wxLogStream(&cout);
wxLog::SetActiveTarget(logger);
// 可以使用 wxStreamToTextRedirector 将 cout 重定向到某个UI控件
target
有一些预定义的类派生自 wxLog,它们可能有助于了解如何创建新的日志目标类,当然,也可以不做任何更改地使用。
- wxLogStderr/wxLogStream 类将消息记录到
FILE *
中,顾名思义,默认使用 stderr。 - wxLogGUI 是 wxWidgets 应用程序的标准日志目标(如果您不执行任何作,则默认使用它),并为给定平台提供对所有类型的消息的最合理处理。
- wxLogWindow 此日志目标提供了一个“日志控制台”,该控制台收集应用程序生成的所有消息,并将它们传递给上一个活动日志目标。
- wxLogBuffer 此目标将所有记录的消息收集到内部缓冲区中,以便稍后一次性向用户显示它们。
- wxLogNull 它不做任何事情。
从 wxWidgets 2.9.1 开始,可以从任何线程安全地调用日志函数。