Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Skinning in MFC, Part 1


Skinning in MFC, Part 1

I’ve seen some applications with really cool window shapes that are nonrectangular. How can I add these kinds of window shapes to my application?

Anyone who’s tried out an MP3 player on the PC such as Nullsoft’s Winamp has seen the prodigious use of highly customized window shapes as a way to map real world (and sometimes fantasy world) images onto PC-based user interfaces. Although Microsoft Windows has created a standard look-and-feel interface that the vast bulk of Windows applications follow, a few developers decided to go down a different path. The result has been an explosion of what is called “skinning”—the practice of applying textures, graphics and other artifacts to an application by the user to allow for extensive customization.

Many of these applications provide a skinning engine that applies a skin from a file (or set of files) to the application in a defined manner. As long as the skin obeys the requirements of the engine, a skin designer can create very imaginative user interfaces including such things as nonrectangular window shapes. Although the nonrectangular shape is not a required element of skinning, it is commonly seen where an application presents itself as a real-world device (and not many of those appear as rectangles). The MP3 player appearing as a stereo system complete with dials, meter bars for the equalizer, sliders, and so on provides for instant recognition by anyone with a similar stereo in their home or office.

This focus on custom interfaces versus the use of the standard Windows model is in some sense retro as that was the way interfaces were designed in years past. However, it was usually so much work for the developer just to have a workable interface that libraries were developed to take over some of the drudgery. The downside to these were that no two libraries were the same, so developers were locked into the API of a particular vendor. Thus, the arrival of Windows and its standard controls, window shapes, artifacts, and the like, particularly when Windows 95 appeared, allowed the majority of developers to focus more on solving a specific domain problem rather than trying to figure out what a menu should look like, or how to optimize the painting of data in a form.

Before plunging into the skinning world with your own application, it’s probably wise to consider whether it really makes sense for your application and, more importantly, for your users. If there is a real-world device that maps directly to your application (such as an MP3 player) then a customized look may make total sense and be intuitive for the user. However, if the intent is to simply add some pizzazz to the user interface, then skinning may be more work and trouble than it’s worth. I’ve used many skinned applications and have found some to be more troublesome than helpful when it took time to figure out how to use the interface. Often they were pretty to look at initially, but became aggravating to use in practice.

If you still feel the need to skin your application, then here is the basic idea of how it’s done.

First, the Windows SDK contains a very important function that is the centerpiece for creating nonrectangular window shapes—SetWindowRgn. Calling this function with a window handle and a region handle causes Windows to turn over all window painting/management to the window procedure (proc) of that window. This is very powerful and a little scary if you’ve never had a chance to mess with painting at this level. Windows creates a normal rectangular window but applies the region to it for you. Windows then ensures that any background information within the boundary of the window but outside of the region area appears as though the window wasn’t really there. Anything inside the region is left to the window procedure to handle. This includes anything that might have appeared in the client area and nonclient area such as menus, toolbars, borders, and so on. Essentially, the developer asks Windows to create a blank slate on which to paint. Windows obliges the request via SetWindowRgn but leaves everything else for that window up to the developer.

Considering how easy it is to take for granted a menu or toolbar in a standard Windows application, it can be a shock for the new skin developer to have to take on the task of creating/managing such items without any help from Windows itself. That is the bargain Windows makes with the skinner—full control over what is presented and full responsibility to implement everything needed.

The skin itself is usually generated as a series of image files using a graphics editor (for example, Photoshop or Paintshop Pro). Often there must be image files for each interface element in a variety of states. For example, a button usually needs four different images—normal (or default), depressed, disabled, and selected. A skin might provide for less (for example, the skin engine doesn’t support the selected state) or more (the skin provides for additional user actions such as differentiating between a left and right mouse click on the button). These images may be combined into a single file containing the full set of image states similar to the way a toolbar bitmap contains images for toolbar buttons. The full set of skin images may be packaged inside of a compressed file or other container, and include a manifest that describes the contents and how to map the images to various window areas.

The manifest is critical to the engine being able to efficiently process the skin and apply it to the window. Within the manifest there would be information describing the placement of images at a pixel location within the window. These instructions could get very elaborate, depending on the skinning engine, and include such things as relationships to other skin items and how to handle collisions between items.

Once the images are created by a skin designer and the manifest developed, the engine processes this information by loading images and converting them to regions. This is to allow for images that are nonrectangular. In fact, the main application window image is usually loaded like this and converted to a region in order to know how to tell Windows what region to use in the call to SetWindowRgn(). The process of image to region conversion is complex and requires a great deal of knowledge of image layouts. Over the next several issues of the newsletter,I’ll explore how this is done. For now, assume there is a function named ConvertImageToRegion(). To create the appropriate shape for the main application window, the background image is loaded, passed to ConvertImageToRegion(), then the region passed to SetWindowRgn(). Here is how that would look in MFC:

class CMainWindow : public CWnd 
{
public:
	/* normal MFC declarations.. */

private:
	HDC m_hBitmapDC;
	HBITMAP m_hBmp,m_hOrigBmp;
}

LRESULT CMainWindow::OnCreate(UINT,WPARAM,LPARAM,BOOL&)
{
	// load the bitmap from an external file.
HBITMAP m_hBmp = (HBITMAP)LoadImage( AfxGetApp()->m_hInstance, "c:\theimage.bmp, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE );
	
	// create a compatible device context for the bitmap.  we will use this
	// DC when painting the image onto the background of the window.
	HDC dc = GetDC();
	HBITMAP m_hBitmapDC = ::CreateCompatibleDC( dc );
	ReleaseDC( dc );
	m_hOrigBmp = (HBITMAP)SelectObject( m_hBitmapDC, m_hBmp );

	// convert image to region  custom function explored in the next issue..
	long heightOfBmp=0,widthOfBmp=0;
	HRGN hRgn = ConvertImageToRegion( m_hBmp,&heightOfBmp,&widthOfBmp );
	
	// resize the window with its height/width based on that of the bitmap.
	SetWindowPos( NULL, 0, 0, widthOfBmp,heightOfBmp,SWP_NOZORDER | SWP_NOMOVE );

	// finally, ask Windows to force the window to take on the shape of the
	// region. 
	// note:  the System owns the region after this call so there is no need
	// to release it manually.
	SetWindowRgn( hBmpRgn, FALSE );
		
	return 0;
}

You can see that converting a window to a nonrectangular shape is pretty straightforward (assuming you can convert the bitmap to a region in the first place—but don’t worry, that will be explored in the next newsletter). The next step is to handle repainting of the window using the new bitmap DC. This is basic GDI, but again, we’ll explore this area in the next series of newsletters.


Mark M. Baker is the Chief of Research & Development at BNA Software located in Washington, D.C. He can be contacted at [email protected].



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.