💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 9.5 使用wxWidgets资源文件 你可以从一个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); ``` 首先初始化XRC处理系统,你需要在OnInit中增加下面的代码: ``` wxXmlResource::Get()->InitAllHandlers(); ``` 然后加载一个XRC文件: ``` wxXmlResource::Get()->Load(wxT("resources.xrc")); ``` 这只是告诉wxWidgets这个资源文件的存在,要创建真实的用户界面,还需要类似下面的代码: ``` 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")); ``` 你也可以将XRC文件编译为C++的代码,通过和别的C++的代码编译在一起,你就可以去掉某个单独的资源文件了.编译用的命令行如下所示: ``` wxrc resource1.xrc resource2.xrc c -o resource.cpp ``` 编译方法和编译普通的C++代码相同,这个文件包含一个InitXmlResource函数,你必须在你的主程序中调用这个函数: ``` extern void InitXmlResource(); // defined in generated file wxXmlResource::Get()->InitAllHandlers(); InitXmlResource(); ``` 下面列出了wxrc程序的命令行参数: | 短命令格式 | 长命令格式 | 描述 | |:--- |:--- |:--- | | -h | help | 显式帮助信息. | | -v | verbose | 打印执行过程信息. | | -c | cpp-code | 编译目标为C++代码,而不是XRS文件. | | -p | python-code | 编译目标为Python代码而不是XRS文件. | | -e | extra-cpp-code | 和-c一起使用,指示为XRC定义的窗口生成头文件. | | -u | uncompressed | 不要压缩Xml文件(C++ only). | | -g | gettext | 将相关的字符串翻译为poEdit或者gettext可以识别的格式.输出到标准输出或者某个文件中(如果指定了-o参数的话). | | -n | function &lt;name&gt; | 指定特定的C++初始化函数(和-c一起使用). | | -o &lt;filename&gt; | output &lt;filename&gt; | 指定输出文件名,比如resource.xrs or resource.cpp. | | -l &lt;filename&gt; | list-of-handlers &lt;filename&gt; | 列举这个资源文件所需要的处理函数. | 资源翻译 如果wxXmlResource对象创建的时候指定了wxXRC_USE_LOCALE标记(默认行为),所有可显示的字符串都将被认为是需要翻译的,具体内容参考第16章,"编写国际化应用程序",然后poEdit并能查找XRC文件来发现那些需要翻译的字符串,因此,必须使用"-g" 参数产生一个对应的C++文件,以供poEdit使用,命令行如下: ``` wxrc -g resources.xrc -o resource_strings.cpp ``` 然后你就可以使用poEdit来搜索这个和其它的C++文件了. XRC的文件格式 这里显然不是完整描述XRC文件格式的地方,因此我们只举一个简单的使用了布局控件的例子: ``` <?xml version="1.0"?> <resource version="2.3.0.1"> <object class="wxDialog" name="simpledlg"> <title>A simple dialog</title> <object class="wxBoxSizer"> <orient>wxVERTICAL</orient> <object class="sizeritem"> <object class="wxTextCtrl"> <size>200,200d</size> <style>wxTE_MULTILINE|wxSUNKEN_BORDER</style> <value>Hello, this is an ordinary multiline\n textctrl....</value> </object> <option>1</option> <flag>wxEXPAND|wxALL</flag> <border>10</border> </object> <object class="sizeritem"> <object class="wxBoxSizer"> <object class="sizeritem"> <object class="wxButton" name="wxID_OK"> <label>Ok</label> <default>1</default> </object> </object> <object class="sizeritem"> <object class="wxButton" name="wxID_CANCEL"> <label>Cancel</label> </object> <border>10</border> <flag>wxLEFT</flag> </object> </object> <flag>wxLEFT|wxRIGHT|wxBOTTOM|wxALIGN_RIGHT</flag> <border>10</border> </object> </object> </object> </resource> ``` XRC文件格式的详细描述可以在wxWidgets自带的文档目录docs/tech/tn0014.txt中找到.如果你使用对话框编辑器的话,你管它的文件格式干嘛呢. 你可能回问怎样在XRC文件中指定二进制的图片或者图标文件呢?实际上这些资源是通过URLs来指定的,wxWidgets的虚拟文件系统将会从合适的地方(比如一个压缩文件中)获取指定的文件.举例如下: ``` <object class="wxBitmapButton" name="wxID_OK"> <bitmap>resources.bin#zip:okimage.png</bitmap> </object> ``` 关于使用虚拟文件系统加载资源或者图片的细节,请参考第10章,"在程序中使用图片"以及第14章"文件和流". 编写资源处理类 XRC系统使用不同的资源处理类来识别Xml文件中定义的不同的资源.如果你编写了自己的控件,你就需要编写自己的资源处理类. wxButton的资源处理类如下所示: ``` #include "wx/xrc/xmlres.h" class wxButtonXmlHandler : public wxXmlResourceHandler { DECLARE_DYNAMIC_CLASS(wxButtonXmlHandler) public: wxButtonXmlHandler(); virtual wxObject *DoCreateResource(); virtual bool CanHandle(wxXmlNode *node); }; ``` 资源处理类的实现是非常简单的.在其构造函数的实现中,使用 XRC_ADD_STYL宏来使得处理类可以识别控件相关的特殊的窗口类型,然后使用AddWindowStyles增加这些类型.然后在 DoCreateResource函数中,使用两步法创建按钮实例,其中第一步要使用XRC_MAKE_INSTANCE函数,然后调用Create函数,参数需要使用对应的函数从Xml文件中获得.而CanHandle函数则用来回答是否这个处理类可以处理某个Xml节点的问题.使用一个处理类处理多种Xml节点是允许的. ``` IMPLEMENT_DYNAMIC_CLASS(wxButtonXmlHandler, wxXmlResourceHandler) wxButtonXmlHandler::wxButtonXmlHandler() : wxXmlResourceHandler() { XRC_ADD_STYLE(wxBU_LEFT); XRC_ADD_STYLE(wxBU_RIGHT); XRC_ADD_STYLE(wxBU_TOP); XRC_ADD_STYLE(wxBU_BOTTOM); XRC_ADD_STYLE(wxBU_EXACTFIT); AddWindowStyles(); } wxObject *wxButtonXmlHandler::DoCreateResource() { XRC_MAKE_INSTANCE(button, wxButton) button->Create(m_parentAsWindow, GetID(), GetText(wxT("label")), GetPosition(), GetSize(), GetStyle(), wxDefaultValidator, GetName()); if (GetBool(wxT("default"), 0)) button->SetDefault(); SetupWindow(button); return button; } bool wxButtonXmlHandler::CanHandle(wxXmlNode *node) { return IsOfClass(node, wxT("wxButton")); } ``` 要使用某种处理类,应用程序需要包含相应的头文件并且登记这个处理类,就象下面这样: ``` #include "wx/xrc/xh_bttn.h" wxXmlResource::AddHandler(new wxBitmapXmlHandler); ``` 外来控件 XRC文件还可以通过class="unknown"来指定某个控件是外来的或者说是"未知的"控件.这可以用来实现在其父窗口已经加载到应用程序之中以后,使用C++代码来创建这个未知的控件.当XRC文件加载一个未知控件的时候,它将创建一个用来占位的窗口,然后在代码中,可以使用C ++先创建这个实际的控件,然后使用AttachUnknownControl函数替换掉那个用来占位的窗口.如下所示: ``` wxDialog dlg; // 加载对话框 wxXmlResource::Get()->LoadDialog(&dlg, this, wxT("mydialog")); // 创建特殊控件 MyCtrl* myCtrl = new MyCtrl(&dlg, wxID_ANY); // 增加到对话框里 wxXmlResource::Get()->AttachUnknownControl(wxT("custctrl"), myCtrl); // 显示整个对话框 dlg.ShowModal(); ``` 外来的控件在XRC文件中可以这样定义: ``` <object class="unknown" name="custctrl"> <size>100,100</size> </object> ``` 使用这种技术,你可以既不用创建新的资源处理类,又可以在资源文件中使用未知的控件.