Click here to Skip to main content
15,393,706 members
Articles / Desktop Programming / Win32
Posted 13 Dec 2008


237 bookmarked

SonicUI - A Convenient GUI Engine You've Never Seen

Rate me:
Please Sign up or sign in to vote.
4.92/5 (77 votes)
26 May 2010CPOL7 min read
A convenient and high-powered GUI engine with plenty of tricks



SonicUI is a GUI Engine based on nake GDI APIs. It offers several simple UI components to accomplish high efficiency UI effects, such as self-draw buttons, irregular windows, animation, URL in windows and image operation methods. The main purpose is to use least code to achieve best effects.


Recently generic application UI development is becoming more like game development. Self-draw components are used here and there in many merchant software, such as the most popular IM software in China - QQ2009. I have been a leisure game developer in a company, so I would also like to introduce some game developing mechanisms into my current work. With this thought, I wrote this GUI engine to help. As everyone knows, UI development is often a repeated and disinteresting job. So I designed this engine with two principles: simple to use and high efficiency. Let's take a look into the usage of the engine below, you will find some interesting points.

Using the Code

First of all, let me introduce the class factory and components manager: ISonicUI. This interface is used for creating and destroying objects and act as some global function.

1. Showing and Rotating an Image

The image operation interface: ISonicImage, which is used to load, save, rotate, stretch, gray or do HSL adjustment, etc. Thanks to the author of CxImage, I use this lib to avoid encoding or decoding multifarious formats of image. But after the images are loaded by CxImage and converted to standard dib format, I deal with them in my own way. The usage of ISonicImage is simple:

ISonicImage * pImg = GetSonicUI()->CreateImage();

2. Making a URL

It may be boring to output a colorful string or add a URL to the window using some controls. With the nake GDI APIs, you have to select a different font or other GDI objects into and out from dc again and again. But with ISonicString, you will accomplish the job in just three or four lines.

ISonicString * pStr = GetSonicUI()->CreateString();
pStr->Format("/a='', c=%x/Hello I'm a clik", RGB(0, 0, 255));
pStr->TextOut(hdc, 10, 10);

Notice: Don't create and format ISonicString in WM_PAINT procedure to avoid repeated initialization and put the pStr->TextOut() method between BeginPaint() and EndPaint().

Yes, three lines to make a URL, without putting any controls in the window or paying attention to boring message dispatch, seems impossible? Hmm, just subclass tricks. In this way, you can make your code just as simple as HTML code. The only thing you will attend is the keywords of ISonicString. You can find a particular explanation in the interface file - ISonicUI.h.

3. Making an Animation Self-draw Button

Self-draw button is also familiar to us UI developers. With ISonicString, we can easily make a beautiful button with just a little difference from making a URL.

void WINAPI OnMove(ISonicString * pStr, LPVOID)

ISonicImage * pImgNormal = GetSonicUI()->CreateImage();
pImgNormal->SetColorKey(RGB(255, 0, 255));

ISonicImage * pImgHover = GetSonicUI()->CreateImage();
pImgHover->SetColorKey(RGB(255, 0, 255));

ISonicImage * pImgClick = GetSonicUI()->CreateImage();
pImgClick->SetColorKey(RGB(255, 0, 255));

ISonicString * pAniButton = GetSonicUI()->CreateString();
pAniButton->Format("/a, p=%d, ph=%d, pc=%d, animation=40/",
    pImgNormal->GetObjectId(), pImgHover->GetObjectId(), pImgClick->GetObjectId());
pAniButton->Delegate(DELEGATE_EVENT_CLICK, NULL, NULL, OnMove);
pAniButton->TextOut(hdc, 10, 10);

The "p, ph, pc" keywords stand for three statuses (normal, hover, click) of a button. Every keyword appoints a ISonicImage as its displaying item. If you get a source image, which tiles three status, then it doesn't matter either. You just need to appoint "p, ph, pc" to the same object id of an ISonicImage and everything will be done. I do the source rect clip internally for you. The "animation=40" figures that this is a shading button, in other words, animation will be displayed during status switch. 40 is the shading speed, the higher, the faster. The Delegate() method delegates a procedure to the click event as a callback, and then the procedure will be called if you click the button. We will talk about more details on Delegation trick later.

4. Making an Irregular Window

ISonicWndEffect component is used for making irregular windows, or to make windows do some animation, such as moving smoothly, rotating or stretching smoothly, etc. There are two methods to make irregular windows: using window Rgn or layered window. First the window rgn way:

// ISonicImage * pImg
SetWindowRgn(hWnd, pImg->CreateRgn());

Second using layered window:

ISonicWndEffect * pEffect = GetSonicUI()->CeateWndEffect();
// use alpha-per-pixel attaching mode
pEffect->Attach(hWnd, TRUE);
// ISonicImage * pImg

Image 3

5. Other Components

There are many other components, like ISonicTextScrollBar and ISonicAnimation, with which you can implement lots of familiar UI effects, such as scroll text, moving a picture smoothly, rotating or stretching it smoothly with a good visual sense. The usage is rather easy and you can look it up in the interface file ISonicUI.h. Here I will save my words for the more interesting part below.

Points of Interest

I will show several tricks in my project in this chapter. These tricks contain ASM and API hook techniques.

1. Delegation

Of course we want to find a simple way to delegate different procedures to self-draw buttons so as to make the components capable of being universally used. But there is a problem in function declaration. VC++ doesn't permit you to transfer a member function of a class as a parameter in the normal way. You have to use member function pointer, which is class relevant and obviously against the "universal" principle. So I use the volatile parameter to avoid the restriction.

void ISonicBase::Delegate(UINT message, LPVOID pReserve, LPVOID pClass, ...)
	if(IsValid() == FALSE)
	ISonicBaseData * pData = dynamic_cast<isonicbasedata />(this);
	if(pData == NULL)
	pm.pClass = pClass;
	pm.pReserve = pReserve;
	va_list argPtr;
	va_start(argPtr, pClass);
	pm.pFunc = va_arg(argPtr, LPVOID);
	pData->m_mapDelegate[message] = pm;

And we cannot make a callback in the normal way either. Don't worry, just a little ASM code will do the job.

void ISonicBaseData::OnDelegate(UINT message)
	MSG_TO_DELEGATE_PARAM::iterator it = m_mapDelegate.find(message);
	if(it == m_mapDelegate.end())
	DELEGATE_PARAM &pm = it->second;
	if(pm.pFunc == NULL || IsBadCodePtr((FARPROC)pm.pFunc))
	ISonicBase * pBase = dynamic_cast<isonicbase />(this);
	if(pBase == NULL)
	LPVOID pReserve = pm.pReserve;
	LPVOID pClass = pm.pClass;
	LPVOID pFunc = pm.pFunc;
		push ecx
		push [pReserve]
		push [pBase]
		mov ecx, [pClass]
		call [pFunc]
		pop ecx

In some respects, the security of C++ syntax check is wrecked by us, so you must ensure the declaration of the callback function strictly obeys the rule:

void WINPAI Func(ISonicBase *, LPVOID)

Otherwise you will get a stack crash or some fatal errors.

2. Layered Window

Layered Window is widely used for implementing transparent windows or irregular windows. There are two APIs used for displaying a layered window, SetLayeredWindowAttributes and UpdateLayeredWindow. But there's a mortal difference between these two functions to application developers although SetLayeredWindowAttributes uses UpdateLayeredWindow internally as MSDN said. For further discussion, I may start another article on it. But here I can just say the main difference is when UpdateLayeredWindow is used, WM_PAINT message is abandoned, all your child controls will not show themselves, and generic GDI APIs may work incorrectly while SetLayeredWindowAttributes uses a redirected mechanism to keep everything working well. Sounds like the UpdateLayeredWindow is just a trouble maker, but if you want to make an alpha-per-pixel window and use a PNG as the background to implement some shadow effects, UpdateLayeredWindow will be the only choice.

Since ISonicWndEffect is just an "attachment" which attaches to an existing hwnd, how can I demand the engine user to rewrite all his rendering code between BeginPaint and EndPaint? So I use an API hook trick.

	// ...
	HMODULE hMod = GetModuleHandle("User32.dll");
	if(hMod == NULL)
		return FALSE;
	m_pOldBeginPaint = ReplaceFuncAndCopy(GetProcAddress
				(hMod, "BeginPaint"), MyBeginPaint);
	m_pOldEndPaint = ReplaceFuncAndCopy(GetProcAddress
				(hMod, "EndPaint"), MyEndPaint);

HDC CSonicUI::MyBeginPaint( HWND hwnd, LPPAINTSTRUCT lpPaint )
	HDC hdc;
		memset(lpPaint, 0, sizeof(PAINTSTRUCT));
		lpPaint->hdc = m_hPaintDC;
		GetClientRect(hwnd, &lpPaint->rcPaint);
		hdc = m_hPaintDC;
		g_UI.m_rtUpdate = lpPaint->rcPaint;
		GetUpdateRect(hwnd, g_UI.m_rtUpdate, FALSE);
			push [ebp + 0ch]
			push [ebp + 8h]
			call [m_pOldBeginPaint]
			mov [hdc], eax
	g_UI.m_bPainting = TRUE;
	return hdc;

BOOL CSonicUI::MyEndPaint( HWND hWnd, CONST PAINTSTRUCT *lpPaint )
	BOOL bRet = TRUE;
	// ...
		m_hPaintDC = NULL;
		return TRUE;
			push [ebp + 0ch]
			push [ebp + 8h]
			call [m_pOldEndPaint]
			mov [bRet], eax
	GetClientRect(hWnd, g_UI.m_rtUpdate);
	g_UI.m_bPainting = FALSE;
	return bRet;

By this way, when I want to redraw the window attached by ISonicWndEffect using alpha-per-pixel mode (internally implemented with UpdateLayeredWindow), I just send the window a fake WM_PAINT message and use a memdc as the wParam of WM_PAINT, everything will be correctly rendered, without any change to the rendering code.

In fact, using this trick would bring us a little present. When this engine is in your process, all your windows can be drawn to a specified memdc, even when it's hidden.


There are still many other tricks and techniques, such as convert float operation to integer, dirty rect update mechanism, SSE2 instructions, etc. to optimize the efficiency of the engine. I will leave these parts to readers with my code. I hope you enjoy my engine and contact me if you have any good ideas or suggestions.


  • 13th December, 2008
    • First post
  • 13th January, 2009
    • Changed the function hook code to avoid memory leak warnings
    • Modified some code within CSonicString::TextOut to make it capable of being used with memory dc without specifying a hwnd
    • Added gauss blur and even blur feature to ISonicImage
  • 15th March, 2009
    • Fixed server bugs in ISonicImage which may cause crash
    • Added DirectTransfrom method to ISonicWndEffect
    • Added some features to ISonicTextScrollBar
  • 25th May, 2010
    • Added ISonicSkin component. You can prettify your windows and dialogs with only three lines of code. It's cool and handy!
    • Added Unicode support
    • Added static library output
    • Changed several types from MFC support to ATL support to make the engine lighter
    • Optimised some kernel implements, such as dirty rect update mechanism
    • Added interfaces to ISonicUI, ISonicString
    • Modified format of keyword "p" in ISonicString. Here is an example to make a self-draw button with a 4-state tiled image: ISonicString::Format("/a, p4=%d/", pImg->GetObjectId());. The original "ph" and "pc" keywords were discarded. Refer to ISonicUI.h to get more details.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Sonic Guan
Software Developer (Senior)
China China
A windows platform developer who is addicted to UI and reverse project.

Comments and Discussions

AnswerRe: trojan in demo?? Pin
Sonic Guan26-May-10 19:55
MemberSonic Guan26-May-10 19:55 
GeneralRe: trojan in demo?? Pin
David_LoveCpp27-May-10 16:45
MemberDavid_LoveCpp27-May-10 16:45 
Generalgreat code! Pin
oneye26-May-10 17:08
Memberoneye26-May-10 17:08 
Generalsuccessed in vc6(vc6编译成功,需要的给我email) Pin
huangzongwu3-Feb-10 19:29
Memberhuangzongwu3-Feb-10 19:29 
GeneralRe: successed in vc6(vc6编译成功,需要的给我email) Pin
pophelix27-Aug-10 23:14
Memberpophelix27-Aug-10 23:14 
GeneralRe: successed in vc6(vc6编译成功,需要的给我email) Pin
jjqcat18-Sep-10 16:54
Memberjjqcat18-Sep-10 16:54 
GeneralRe: successed in vc6(vc6编译成功,需要的给我email) Pin
zhangcan15-Nov-10 15:03
Memberzhangcan15-Nov-10 15:03 
Questionhow to used MFC app ? Pin
dnybz30-Nov-09 0:34
Memberdnybz30-Nov-09 0:34 
how to used MFC app ?

Smile | :)
GeneralWoW it;s really great! Pin
Kyusik1-Sep-09 18:25
MemberKyusik1-Sep-09 18:25 
General写的好 ,为中国争光! Pin
xjb9227-Jul-09 18:49
Memberxjb9227-Jul-09 18:49 
QuestionCould you make a static link version of SonicUI ? Pin
Kevin Eastman16-May-09 18:08
MemberKevin Eastman16-May-09 18:08 
AnswerRe: Could you make a static link version of SonicUI ? Pin
zorodey7-Aug-12 18:38
Memberzorodey7-Aug-12 18:38 
Generalinterface flickering Pin
mayborn999921-Apr-09 8:31
Membermayborn999921-Apr-09 8:31 
GeneralRe: interface flickering Pin
mayborn99993-May-09 13:50
Membermayborn99993-May-09 13:50 
QuestionGreat Work,But how to support Web Page? Pin
Dragon Wong4-Apr-09 21:10
MemberDragon Wong4-Apr-09 21:10 
AnswerRe: Great Work,But how to support Web Page? Pin
D.K.Wang7-Sep-09 17:44
MemberD.K.Wang7-Sep-09 17:44 
GeneralQuiet good the article,quite the code,that is what i want. Pin
artspring19-Mar-09 17:28
Memberartspring19-Mar-09 17:28 
GeneralMore demo samples would be desirable Pin
Tage Lejon16-Mar-09 3:31
MemberTage Lejon16-Mar-09 3:31 
GeneralRe: More demo samples would be desirable Pin
Sonic Guan18-Mar-09 18:52
MemberSonic Guan18-Mar-09 18:52 
QuestionHi,Sonic Guan,can you make it comaptible with VC6.0? Pin
e_ilite3-Feb-09 21:36
Membere_ilite3-Feb-09 21:36 
AnswerRe: Hi,Sonic Guan,can you make it comaptible with VC6.0? Pin
Sonic Guan5-Feb-09 15:56
MemberSonic Guan5-Feb-09 15:56 
GeneralRe: Hi,Sonic Guan,can you make it comaptible with VC6.0? Pin
e_ilite5-Feb-09 18:14
Membere_ilite5-Feb-09 18:14 
GeneralRe: Hi,Sonic Guan,can you make it comaptible with VC6.0? Pin
e_ilite5-Feb-09 18:18
Membere_ilite5-Feb-09 18:18 
GeneralRe: Hi,Sonic Guan,can you make it comaptible with VC6.0? Pin
Sonic Guan5-Feb-09 18:55
MemberSonic Guan5-Feb-09 18:55 
GeneralRe: Hi,Sonic Guan,can you make it comaptible with VC6.0? Pin
e_ilite5-Feb-09 19:07
Membere_ilite5-Feb-09 19:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.