65.9K
CodeProject 正在变化。 阅读更多。
Home

DirectUI

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (19投票s)

2012 年 4 月 24 日

LGPL3

3分钟阅读

viewsIcon

770558

downloadIcon

13217

带有 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 动画添加到扩展版本。
© . All rights reserved.