网易duilib界面库优点
虽有原版Duilib简单易用,但是存在一些bug和不足,如控件种类不丰富、不支持动画、不支持半透明异形窗体、对多线程支持不好等,因此云信使用了专门增强扩展的云信Duilib
。云信Duilib
有一下优点:
- 优化了Duilib渲染效率
- 增强了界面渲染效果
- 增强了XML布局的功能,更方便的实现更多的布局效果
- 增强了控件功能属性
- 控件支持动画效果
- 控件支持gif图片格式
- 支持半透明异形窗体
- 支持多语言功能
使用云信Duilib
,配合比较高效的引擎库base库
解决多线程问题,可以做出功能更强更稳定的客户端界面。
目录结构
网易duilib的源码目录结构如图所示 主要包括 Animation(窗口弹出样式)、Automation(自动化测试)、Box(容器布局)、Control(控件)、Core(核心 主要包括窗口 xml解析 控件基础类 容器基础类 全局配置等)、Render(渲染)、Utils(工具集)几个部分。
目录结构及描述如下所示:
下面借着一个标准的窗体程序来详细了解duilib界面库的组成
// basic.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "main.h"
#include "basic_form.h"
enum ThreadId
{
kThreadUI
};
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// 创建主线程
MainThread thread;
// 执行主线程循环
thread.RunOnCurrentThreadWithLoop(nbase::MessageLoop::kUIMessageLoop);
return 0;
}
void MainThread::Init()
{
nbase::ThreadManager::RegisterThread(kThreadUI);
// 获取资源路径,初始化全局参数
std::wstring theme_dir = nbase::win32::GetCurrentModuleDirectory();
#ifdef _DEBUG
// Debug 模式下使用本地文件夹作为资源
// 默认皮肤使用 resources\\themes\\default
// 默认语言使用 resources\\lang\\zh_CN
// 如需修改请指定 Startup 最后两个参数
ui::GlobalManager::Startup(theme_dir + L"resources\\", ui::CreateControlCallback(), false);
#else
// Release 模式下使用资源中的压缩包作为资源
// 资源被导入到资源列表分类为 THEME,资源名称为 IDR_THEME
// 如果资源使用的是本地的 zip 文件而非资源中的 zip 压缩包
// 可以使用 OpenResZip 另一个重载函数打开本地的资源压缩包
ui::GlobalManager::OpenResZip(MAKEINTRESOURCE(IDR_THEME), L"THEME", "");
// ui::GlobalManager::OpenResZip(L"resources.zip", "");
ui::GlobalManager::Startup(L"resources\\", ui::CreateControlCallback(), false);
#endif
ui::GlobalManager::EnableAutomation();
// 创建一个默认带有阴影的居中窗口
BasicForm* window = new BasicForm();
window->Create(NULL, BasicForm::kClassName.c_str(), WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 0);
window->CenterWindow();
window->ShowWindow();
}
void MainThread::Cleanup()
{
ui::GlobalManager::Shutdown();
SetThreadWasQuitProperly(true);
nbase::ThreadManager::UnregisterThread();
}
标准的流程主要是:
- 初始化
- 窗体创建
- 反初始化
云信Duilib初始化
在创建并使用窗体功能之前,需要对云信Duilib
进行初始化。一般在wWinMain
入口函数中进行初始化。GlobalManager
类管理了程序所有的所用公共界面资源,初始化函数Startup
需要传入两个参数:第一个参数表示所有资源的根目录;第二个参数表示创建自定义控件的回调函数,一般不需要指定。示例代码如下:
std::wstring theme_dir = QPath::GetAppPath();
ui::GlobalManager::Startup(theme_dir + L"themes\\default", ui::CreateControlCallback());
- 初始化函数会根据指定的目录,自动搜寻目录根位置中的
global.xml
文件,global.xml
文件中定义了全局公用资源,包含了字体、class、文字颜色。global.xml
文件是可选的,但是无疑几乎所有程序都会使用字体和文字颜色等信息。 - 初始化函数同时搜索多语言文件,多语言文件应该位于相对于程序的
\lang\zh_CN\gdstrings.ini
位置,名字必须为gdstrings.ini
,多语言文件是可选的。 global.xml
文件和gdstrings.ini
文件具体的编写和使用请参考云信Duilib属性列表和云信DemoNIM Demo For PC。
窗体创建
// 创建一个默认带有阴影的居中窗口
BasicForm* window = new BasicForm();
window->Create(NULL, BasicForm::kClassName.c_str(), WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 0);
window->CenterWindow();
window->ShowWindow();
Duilib反初始化
- 当消息循环结束,在程序结束之前,应该调用反初始化函数来做清理工作,示例代码如下:
ui::GlobalManager::Shutdown();
开发窗体类
窗体类是程序UI的基本,云信Duilib
已经提供了编写好的窗体基类WindowImplBase
,在创建您自己的窗体类时,只需要继承WindowImplBase
类或者其子类即可帮助您完成大部分的窗体类开发工作,您只需要重写几个必要的方法即可。一个基本的窗体类框架如下:
class BasicForm : public ui::WindowImplBase
{
public:
BasicForm();
~BasicForm();
/**
* 一下三个接口是必须要覆写的接口,父类会调用这三个接口来构建窗口
* GetSkinFolder 接口设置你要绘制的窗口皮肤资源路径
* GetSkinFile 接口设置你要绘制的窗口的 xml 描述文件
* GetWindowClassName 接口设置窗口唯一的类名称
*/
virtual std::wstring GetSkinFolder() override;
virtual std::wstring GetSkinFile() override;
virtual std::wstring GetWindowClassName() const override;
/**
* 收到 WM_CREATE 消息时该函数会被调用,通常做一些控件初始化的操作
*/
virtual void InitWindow() override;
/**
* 收到 WM_CLOSE 消息时该函数会被调用
*/
virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
static const std::wstring kClassName;
};
GetSkinFolder
方法(必须重写的方法)用于返回本窗体类所使用的资源的目录,这个目录相对于云信Duilib
初始化时指定的目录。一般把本窗体使用的图片素材和XML布局文件都放到此目录。示例如下:
std::wstring BasicForm::GetSkinFolder()
{
return L"basic";
}
GetSkinFile
方法(必须重写的方法)用于返回本窗体类所加载的XML布局文件,这个文件应该存放在GetSkinFolder
方法所指定的目录中。示例如下:
std::wstring BasicForm::GetSkinFile()
{
return L"basic.xml";
}
GetWindowClassName
方法(必须重写的方法)用于返回本窗体类的类名,不用的窗体类应该具有不同的类名,示例如下:
std::wstring MyForm::GetWindowClassName() const
{
return L"NIM_LoginForm";
}
InitWindow
方法(可选的方法)用于初始化窗体,当窗体被创建后,会自动调用InitWindow
方法,我们可以在这里对窗体进行初始化(比如初始化各个控件指针,为控件添加消息处理函数),示例如下:
void MyForm::InitWindow()
{
m_pRoot->AttachBubbledEvent(ui::kEventAll, nbase::Bind(&MyForm::Notify, this, std::placeholders::_1));
btn_login = static_cast<ui::Button*>(FindControl(L"login_button"));
}
GetClassStyle
方法(可选的方法)用于设置本窗体类的风格,一般不用重写。示例如下:
UINT MyForm::GetClassStyle() const
{
return CS_DBLCLKS;
}
CreateControl
方法(可选的方法)用于返回自定义控件,当库提供的基础控件不满足需求是,就需要开发自定义控件,当解析XML布局文件时遇到位置的自定义控件时就会触发此函数并传入自定义控件在XML中的名字,让您返回您的自定义控件。示例如下:
Control* MyForm::CreateControl(const std::wstring& pstrClass)
{
if (pstrClass == L"MyControl")
{
return new MyControl;
}
return NULL;
}