DirectUI






4.95/5 (19投票s)
带有 DirectX 3D 动画的开源无窗口演示管理器库。
引言
我 P q 论 QML https://en.wikipedia.org/wiki/QML 是更好的跨平台解决方案! 请看这些很棒的演示
最近和一个大学同学聊天,他问我选择哪个库来开发 GUI,我说 VC++ 用 MFC,C# 用 WinForm,Linux 用 Gtk。然后他热情地向我介绍了 DirectUI,一个使用 XML 描述 GUI,使用 bmp/jpg/png 进行皮肤绘制,基于 DirectX 动画的无窗口演示管理器。实际上它与 Linux GUI 工具链Glade(基于 XML),Cairo(2D 图形库),OpenGL(3D 图形库)和Gtk 类似。
这些年我发现 ncurses 是更好的 UI 库 :)
背景
DirectUI 是微软创建的一个 C++ 用户界面库,旨在为原生应用程序提供类似 WPF 的 API。它并未公开发布,但已广泛应用于包括 Windows、Microsoft Office 和 Windows Live Messenger 在内的微软产品中。
所以我用 DirectUI 关键字进行谷歌搜索,它经常显示商业产品,但我真的很想找到一些开源库。幸运的是,它在Bjarke Viksoe 的个人网站上,以及 DuiLib 团队维护的扩展版本。
我检查了扩展版本源代码,它增加了 alpha 渲染支持和其他一些很酷的功能,但它删除了基于 DirectX 的 3D 动画。所以我简单地将 Bjarke Viksoe 开发的基于 DirectX 的3D 动画添加到了扩展版本中。
下面是 Doxygen 为扩展版本生成的 DirectUI 控件架构:
使用代码
因为在 DirectUICore/Internal.h 中有 #include <d3d9.h>
,所以需要下载DirectX SDK。
设置 DirectX SDK 的包含和库路径,例如,在 VS 2005 的工具 -> 选项... -> 项目和解决方案 -> VC++ 目录中。
你可以从DirectUI 源代码版本控制中检出 DirectUI 源代码,然后编译和修改它。
HelloWorld
DirectUI 源代码中有HelloWorld 测试用例可以用来玩转 DirectUI。
- 使用 VS2005 创建 VC++ 项目,步骤如下:
- 创建 Win32 控制台应用程序;
- 选择类别为“窗口应用程序”;
- 设置链接器 -> 输出文件:..\bin\Debug\HelloWorld.exe
// WinMain is the main entry point
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow)
{
// CPaintManagerUI is the Windowless presentation manager
CPaintManagerUI::SetInstance(hInstance);
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
HRESULT Hr = ::CoInitialize(NULL);
if (FAILED(Hr)) return 0;
// CFrameWindowWnd is the major class object to show a dialog window
CFrameWindowWnd* pFrame = new CFrameWindowWnd();
if (NULL == pFrame) return 0;
pFrame->Create(NULL, _T("HelloWorld"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
pFrame->CenterWindow();
pFrame->ShowWindow(true);
CPaintManagerUI::MessageLoop();
::CoUninitialize();
return 0;
}
HandleMessage 中的 WM_CREATE
使用 res/hello.xml(需要将 res/ 目录下的文件和目录复制到编译器文件夹,如 bin/Debug)来描述 GUI。在hello.xml 中,设置窗口的最小和正常大小,定义了具有普通、鼠标悬停和按下图像的按钮,并通知点击事件。
下面是 Doxygen 为 XXXWindowWnd 架构生成的图:
class CFrameWindowWnd : public CWindowWnd, public INotifyUI { public: CFrameWindowWnd() { }; LPCTSTR GetWindowClassName() const { return _T("UIMainFrame"); }; UINT GetClassStyle() const { return UI_CLASSSTYLE_FRAME | CS_DBLCLKS; }; void OnFinalMessage(HWND /*hWnd*/) { delete this; }; void Notify(TNotifyUI& msg) { if (msg.sType == _T("click")) { // When clicked the HelloWorld button, it shown a message box if (msg.pSender->GetName() == _T("hellobtn")) { ::MessageBox(NULL, _T("HelloWorld"), _T("HelloWorld"), MB_OK); } } } LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_CREATE) { m_pm.Init(m_hWnd); CDialogBuilder builder; CControlUI* pRoot = builder.Create(_T("hello.xml"), (UINT)0, NULL, &m_pm); ASSERT(pRoot && "Failed to parse XML"); m_pm.AttachDialog(pRoot); m_pm.AddNotifier(this); return 0; } else if (uMsg == WM_DESTROY) { ::PostQuitMessage(0L); } else if (uMsg == WM_ERASEBKGND) { return 1; } LRESULT lRes = 0; if (m_pm.MessageHandler(uMsg, wParam, lParam, lRes)) return lRes; return CWindowWnd::HandleMessage(uMsg, wParam, lParam); } public: CPaintManagerUI m_pm; };
控件(Widget)工厂
DuiLib(DirectUI 的扩展版本)不支持 Menubar 容器、NumericUpDown、CheckBox/RadioBox、ToggleButton、SwitchButton 等,所以简单的控件工厂只显示如下:
控件工厂皮肤资源 使用Mrdoob Widget Factory SVG,看起来像 GNOME 3 控件工厂。
测试用例
TestApp1 是一个比 Hello World 更复杂的沙盒测试用例。
- HandleMessage 中的
WM_CREATE
使用 res/test1.xml(需要将 res/ 目录下的文件和目录复制到编译器文件夹,如 bin/Debug)来描述 GUI; - 在 Notify 调用
OnPrepare
中的Windowinit
添加了一些滑块控件的委托例程,CDelegateBase 架构如下: - 在
OnPrepare
中的AddAnimJob
添加了基于 DirectX 的 3D 动画酷炫效果; changeskinbtn
能够运行时改变皮肤;
class CFrameWindowWnd : public CWindowWnd, public INotifyUI
{
public:
CFrameWindowWnd() { };
LPCTSTR GetWindowClassName() const { return _T("UIMainFrame"); };
UINT GetClassStyle() const { return UI_CLASSSTYLE_FRAME | CS_DBLCLKS; };
void OnFinalMessage(HWND /*hWnd*/) { delete this; };
void Init() { }
bool OnHChanged(void* param)
{
TNotifyUI* pMsg = (TNotifyUI*)param;
if (pMsg->sType == _T("valuechanged"))
{
short H, S, L;
CPaintManagerUI::GetHSL(&H, &S, &L);
CPaintManagerUI::SetHSL(true, (static_cast<CSliderUI*>(pMsg->pSender))->GetValue(), S, L);
}
return true;
}
bool OnSChanged(void* param)
{
TNotifyUI* pMsg = (TNotifyUI*)param;
if (pMsg->sType == _T("valuechanged"))
{
short H, S, L;
CPaintManagerUI::GetHSL(&H, &S, &L);
CPaintManagerUI::SetHSL(true, H, (static_cast<CSliderUI*>(pMsg->pSender))->GetValue(), L);
}
return true;
}
bool OnLChanged(void* param)
{
TNotifyUI* pMsg = (TNotifyUI*)param;
if (pMsg->sType == _T("valuechanged"))
{
short H, S, L;
CPaintManagerUI::GetHSL(&H, &S, &L);
CPaintManagerUI::SetHSL(true, H, S, (static_cast<CSliderUI*>(pMsg->pSender))->GetValue());
}
return true;
}
bool OnAlphaChanged(void* param)
{
TNotifyUI* pMsg = (TNotifyUI*)param;
if (pMsg->sType == _T("valuechanged"))
{
m_pm.SetTransparent((static_cast<CSliderUI*>(pMsg->pSender))->GetValue());
}
return true;
}
void OnPrepare()
{
CSliderUI* pSilder = static_cast<CSliderUI*>(m_pm.FindControl(_T("alpha_controlor")));
if (pSilder) pSilder->OnNotify += MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);
pSilder = static_cast<CSliderUI*>(m_pm.FindControl(_T("h_controlor")));
if (pSilder) pSilder->OnNotify += MakeDelegate(this, &CFrameWindowWnd::OnHChanged);
pSilder = static_cast<CSliderUI*>(m_pm.FindControl(_T("s_controlor")));
if (pSilder) pSilder->OnNotify += MakeDelegate(this, &CFrameWindowWnd::OnSChanged);
pSilder = static_cast<CSliderUI*>(m_pm.FindControl(_T("l_controlor")));
if (pSilder) pSilder->OnNotify += MakeDelegate(this, &CFrameWindowWnd::OnLChanged);
COLORREF clrBack = RGB(0, 0, 0);
RECT rcCtrl = m_pm.FindControl(_T("changeskinbtn"))->GetPos();
m_pm.AddAnimJob(CAnimJobUI(UIANIMTYPE_FLAT, 0, 350, clrBack, clrBack, CRect(rcCtrl.left, rcCtrl.top, rcCtrl.left + 50, rcCtrl.top + 50), 40, 0, 4, 255, 0.3f));
}
void Notify(TNotifyUI& msg)
{
if (msg.sType == _T("windowinit"))
{
OnPrepare();
}
else if (msg.sType == _T("click"))
{
if (msg.pSender->GetName() == _T("insertimagebtn"))
{
CRichEditUI* pRich = static_cast<CRichEditUI*>(m_pm.FindControl(_T("testrichedit")));
if (pRich)
{
pRich->RemoveAll();
}
}
else if (msg.pSender->GetName() == _T("changeskinbtn"))
{
if (CPaintManagerUI::GetResourcePath() == CPaintManagerUI::GetInstancePath())
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin\\FlashRes"));
else
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
CPaintManagerUI::ReloadSkin();
}
}
}
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_CREATE)
{
m_pm.Init(m_hWnd);
CDialogBuilder builder;
CControlUI* pRoot = builder.Create(_T("test1.xml"), (UINT)0, NULL, &m_pm);
ASSERT(pRoot && "Failed to parse XML");
m_pm.AttachDialog(pRoot);
m_pm.AddNotifier(this);
Init();
return 0;
}
else if (uMsg == WM_DESTROY)
{
::PostQuitMessage(0L);
}
else if (uMsg == WM_ERASEBKGND)
{
return 1;
}
LRESULT lRes = 0;
if (m_pm.MessageHandler(uMsg, wParam, lParam, lRes)) return lRes;
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}
public:
CPaintManagerUI m_pm;
};
关注点
Bjarke Viksoe 开发的 DirectUI 是一个杰出的开源项目,阅读源代码可以学到很多东西!更重要的是,很高兴看到越来越多的中国开发者,如 DuiLib 团队,为开源世界做出了贡献 :)
待办事项列表
- 添加了 Bjarke Viksoe (bjarke@viksoe.dk) 开发的 DirectX 动画支持
- 添加了 Menubar 容器
- 添加了 NumericUpDown
- 添加了 CheckBox/RadioBox
- 添加了 ToggleButton
- 添加了 SwitchButton
- 修复了当 Window 的背景色为黑色时 CEditUI 的焦点背景色问题
- 修复了 CComboUI 无法继承 Window 的默认字体颜色问题
历史
- 2012-04-24:将 Bjarke Viksoe 开发的基于 DirectX 的 3D 动画添加到扩展版本。