Introduction
I argue that QML https://en.wikipedia.org/wiki/QML is better cross-platform solution! Please have a look at the awesome demos http://quitcoding.com/
Recently I chatted with my university classmate, he asked me choosing which library to develop GUI, I said using MFC for VC++, WinForm for C#, and Gtk for Linux. Then he passionatly introduced DirectUI to me, a windowless presentation manager using XML to describe GUI, skinning with bmp/jpg/png, animating based on DirectX. Actrualy it is smiliar with Linux GUI toolchain Glade (based on XML), Cairo (2D graphic library), OpenGL (3D graphic library) and Gtk.
After these years I found that ncurses is better UI library :)
Background
DirectUI is a C++ user interface library created by Microsoft to be a WPF like API for native applications. It is not released to the public but is being used widely throughout Microsoft products including Windows, Microsoft Office, and Windows Live Messenger.
So I google with DirectUI keyword, it often shown the commercial products, but I really want to find out some open source libraries. Fortunatly it is on the Bjarke Viksoe personal website and the extended version maintained by DuiLib group.
I checked out the extended version source code, it added alpha rendering support and some other cool features, but it removed 3D animation based on DirectX. So I simply added 3D animation based on DirectX developed by Bjarke Viksoe to the extended version.
DirectUI controls` architecture generated by doxygen for the extended version shown as below:
Using the code
Because there is #include <d3d9.h>
in DirectUICore/Internal.h, it need to download DirectX SDK.
Set DirectX SDK include and library path, for example, in VS 2005`s Tool -> Option... -> Project and solution -> VC++ directory.
You can check out the DirectUI source code from DirectUI Souce Code Version Control, then compilered and hack it.
HelloWorld
In the DirectUI source code there is HelloWorld test case to play with DirectUI.
- Use VS2005 to create VC++ project step by step:
- Create Win32 console application;
- Choose category as Window application;
- Set Link -> Output file: ..\bin\Debug\HelloWorld.exe
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE , LPSTR , int nCmdShow)
{
CPaintManagerUI::SetInstance(hInstance);
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
HRESULT Hr = ::CoInitialize(NULL);
if (FAILED(Hr)) return 0;
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;
}
WM_CREATE
in the HandleMessage use res/hello.xml (it need to copy the files and directories under res/ directory to compiler folder such as bin/Debug) to describe GUI. In the hello.xml, set window min and normal size, defined the button with normal, hot and pushed images then notify the click event.
XXXWindowWnd architecture generated by doxygen shown as below:
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 ) { delete this; };
void Notify(TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
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;
};
Control(Widget) Factory
There is no Menubar container, NumericUpDown, CheckBox/RadioBox, ToggleButton, SwitchButton... supported by DuiLib the extended version of DirectUI, so simple Control Factory only shown as below:
Control Factory skin resource use Mrdoob Widget Factory SVG, it looks like GNOME 3 widget factory.
TestCase
TestApp1 is a sandbox test case more complex than Hello World stuff.
WM_CREATE
in the HandleMessage use res/test1.xml (it need to copy the files and directories under res/ directory to compiler folder such as bin/Debug) to describe GUI; Windowinit
in the Notify call OnPrepare
to add Delegate routine for some slider controls, CDelegateBase architecture shown as below: AddAnimJob
in the OnPrepare
adding 3D animation based on DirectX cool effect; changeskinbtn
is able to change the skin runtime;
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 ) { 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;
};
Points of Interest
DirectUI developed by Bjarke Viksoe is an outstanding open source project, so it is comfortable to read the source code and learn a lot! What`s more, it is glad to see more and more Chinese developers such as DuiLib group had contributed to open source world :)
TODO list
- Add DirectX animation support developed by Bjarke Viksoe (bjarke@viksoe.dk)
- Add Menubar container
- Add NumericUpDown
- Add CheckBox/RadioBox
- Add ToggleButton
- Add SwitchButton
- Fix CEditUI focused bkcolor when Window`s bkcolor is black
- Fix CComboUI fail to inherit Window`s defaultfontcolor
History
- 2012-04-24: Added 3D animation based on DirectX developed by Bjarke Viksoe to the extended version.
An individual human existence should be like a river - small at first, narrowly contained within its banks, and rushing passionately past boulders and over waterfalls. Gradually the river grows wider, the banks recede, the waters flow more quietly, and in the end, without any visible break, they become merged in the sea, and painlessly lose their individual being.