Ive seen some applications with really cool window shapes that are nonrectangular. How can I add these kinds of window shapes to my application?
Anyone whos tried out an MP3 player on the PC such as Nullsofts 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 skinningthe 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, its 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 its worth. Ive 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 its done.
First, the Windows SDK contains a very important function that is the centerpiece for creating nonrectangular window shapesSetWindowRgn. 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 youve 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 wasnt 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 skinnerfull 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 imagesnormal (or default), depressed, disabled, and selected. A skin might provide for less (for example, the skin engine doesnt 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,Ill 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 placebut dont 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, well 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].