My first Duilib program
1. Prepare for development
打开DuiFarm项目DuiFarm.cpp文件,将除_tWinMain函数之外所有内容删除。删除后的DuiFarm.cpp内容如下:
int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow) {;}
在DuiFarm.cpp文件首引入头文件UIlib.h,并引用namespace,及相关预编译指令。如下:
#include "..\\..\\duilib-master\\DuiLib\\UIlib.h" using namespace DuiLib; #ifdef _DEBUG #ifdef _UNICODE #pragma comment(lib,"..\\..\\duilib-master\\Lib\\Duilib_ud.lib") #else #pragma comment(lib,"..\\..\\duilib-master\\Lib\\Duilib_d.lib") #endif #else #ifdef _UNICODE #pragma comment(lib,"..\\..\\duilib-master\\Lib\\Duilib_u.lib") #else #pragma comment(lib,"..\\..\\duilib-master\\Lib\\Duilib.lib") #endif #endif int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow) { //TODO: Input code here... }
这样,Duilib已经成功集成在项目DuiFarm项目中了。注意作者项目的文件结构,引用Duilib.h与Duilib_x.lib文件使用相对路径必须正确(,相对路径以DuiFarm下Debug目录内为参照)。
在编写其他代码之前,需要明确几个知识点。Duilib的几个内置对象。
- CWindowWnd:窗口对象父类,负责创建窗口、窗口消息过程处理、提供窗口子类化与超类化接口。
- CPaintManagerUI:负责窗口图形绘制。CPaintManagerUI三个主要功能:绘制控件、管理消息、事件通知。
- CDialogBuilder:控件布局类,主要作用:解析XML,构建控件树,创建控件对象。
- INotifyUI:处理事件和通知。子类重载Notify(TNotify &msg)虚函数,实现处理事件通知功能。
- CControlUI:控件管理的父类,为控件提供通用属性。
2.Starting coding "Hello World"
在DuiFarm.cpp中新建类DuiFarm,继承自CWindowWnd与INotifyUI,重写CWindowWnd的虚函数GetWindowClassName、HandleMessage与INotify的虚函数Notify。代码如下:
class DuiFarm: public CWindowWnd, public INotifyUI { protected: CPaintManagerUI m_PaintManager; public: virtual LPCTSTR GetWindowClassName() const {/*some code*/} virtual void Notify(TNotifyUI& msg) {/*some code*/} virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) {/*some code*/} };
其中三个函数的主要功能:
- GetWindowClassName:返回窗口名称。
- Notify:事件响应,如点击事件。
- HandleMessage:处理消息,例如创建窗口消息。
首先补全GetWindowClassName函数的代码,在函数体内,直接返回窗体名字符串。
virtual LPCTSTR GetWindowClassName() const {return _T("DuiFramFrame");}
HandleMessage函数中补全对窗体创建等消息的处理。
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lRes = ; if( uMsg == WM_CREATE ) { m_PaintManager.Init(m_hWnd); //init dialog paint CDialogBuilder builder; /* load xml, create widget obj, draw dialog */ CControlUI *pRoot = builder.Create(_T(,NULL,&m_PaintManager); ASSERT(pRoot&&"### XML error!"); m_PaintManager.AttachDialog(pRoot); //widget attach to dialog m_PaintManager.AddNotifier(this); //add msg notify to window return lRes; } if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes)) { return lRes; } return __super::HandleMessage(uMsg, wParam, lParam); }
第4行在消息为窗体创建(WM_CREATE)时,执行:
- 初始化当前窗口绘制(line 6)
- 加载窗口设计xml文件,初始化窗口部件(line 9)
- 窗口部件对象附着对话框(line 11)
- 添加消息通知(line 12)
执行完上述代码,窗体即绘制完成,事件通知已可用。在上述代码中,第8行加载xml文件即为duilib的界面设计xml。犹如html之于浏览器,dui通过解析xml将控件绘制到窗体上。
xml文件如下:
<?xml version="1.0" encoding="utf-8"?> <Window size="100,100"> <HorizontalLayout bkcolor="#FFFFFF00"> <Button name="btnHello" text="Hello World :-)"/> </HorizontalLayout> </Window>
将文件保存到项目Debug目录下(因当前项目以Debug模式运行)。这段xml描述了窗体的整体布局:
- 第二行定义了窗体大小,长宽各500像素。
- 第三行定义了一个横向布局,背景黄色(bkcolor为背景色设置)。
- 第四行定义了一个按钮,名称是btnHello,显示文字为Hello World :-)
现在窗体已经定义好,需要添加对按钮事件的处理。根据上一篇介绍,按钮事件处理要在重载的Notify函数中编写。
virtual void Notify(TNotifyUI& msg) { if(msg.sType==_T("click")) { if(msg.pSender->GetName()==_T("btnHello")) ::MessageBox(NULL,_T("Hello World :-)"),_T("Hallo Welt :-D"),NULL); } }
- 第2行代码表示:“此处要接收类型为‘click’的事件”。
- 第5行代码表示:“在所有click事件中,此处仅接收名为‘btnHello’这家伙的事件”。
- 第6行代码表示:“提示英德对照的Hello World,以及它们对应的口型”。
编写到这里,项目已经具备窗口绘制、窗口布局和事件处理的基本功能,下面需要在入口函数中为之初始化与分配资源:
int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow) { CPaintManagerUI::SetInstance(hInstance); CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath()); DuiFarm duiFarm; duiFarm.Create(NULL, _T("Dui Farm"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); duiFarm.CenterWindow(); duiFarm.ShowModal(); ; }
- 第3行~第4行,入口函数对实例和资源进行分配,第4行实参为当前项目路径。
- 第6行:创建窗口。
- 第7行:设置窗口在屏幕居中。
- 第8行:显示窗口。
在调试运行前,需要确认DuiLib[x].dll是否位于DuiFarm项目的Debug目录下(x为_d,_ud或不存在,根据项目编码以及是否为Debug模式而定)。
运行效果如下:
图2.2.1 显示窗体
如图,窗体显示黄色,而按钮实际上占据了整个窗体。点击按钮如下:
图2.2.2 点击按钮(按钮占据了整个窗体)
通过以上代码,笔者实现了一个最简单的Duilib窗体界面——一个窗体,一个铺满了整个窗口区域的黄色按钮(由xml定义可知,窗体显示“HelloWorld :-)”的部分是Windows按钮)。
Duilib三部分分割业务与显示:XML、(XML-)UI引擎、Win32,并且Duilib可以脱离传统Windows界面限制,开发出独具特色的UI界面风格。我们通过重写HandleMessage函数来实现简单的无边界窗口:
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lRes = ; if( uMsg == WM_CREATE ) { m_PaintManager.Init(m_hWnd); //init dialog paint CDialogBuilder builder; /* load xml, create widget obj, draw dialog */ CControlUI *pRoot = builder.Create(_T(,NULL,&m_PaintManager); ASSERT(pRoot&&"### XML error!"); m_PaintManager.AttachDialog(pRoot); //widget attach to dialog m_PaintManager.AddNotifier(this) ;//add msg to window return lRes; } /* blocked message: WM_NCACTIVATE,WM_NCCALCSIZE,WM_NCPAINT*/ else if(uMsg == WM_NCACTIVATE) { if(!::IsIconic(m_hWnd)) )?TRUE:FALSE; } else if(uMsg == WM_NCCALCSIZE || uMsg == WM_NCPAINT) { ; } if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes)) { return lRes; } return __super::HandleMessage(uMsg, wParam, lParam); }
通过上面代码第15行~28行,屏蔽WM_NCACTIVATE,WM_NCCALCSIZE,WM_NCPAINT三个消息,实现无边界窗体设计。显示和点击效果如图:
在实际软件开发过程中,显示与业务分离是软件开发的指导性思想,UI与业务分离使得软件开发工作更好的划分业务范围。与原始的开发模式对比,XML定义界面相比C++代码定义界面更加简洁便利,后台开发人员可以从界面设计、图片图标呈现等非本专业工作中解放出来,专注于业务逻辑和数据处理;美工与UI设计人员专注于界面美化而不必顾及后台兼容性问题。Duilib通过xml解析、对象创建、部件附着将复杂的UI设计封装到可配置的库中,完全基于win32平台使开发人员不必另学一种技术,从而避免了“重新造*”给程序员带来额外的学习成本开销。