Win32 Window Skinning by (05 March 2002) | Return to The Archives

Introduction

In this tutorial, I'll explain the basics of window regions and how to skin your application windows so that in the end you'll be able to easily enhance your application's visuals dramatically. At the end of this article I will present you with a skinning class that will make window skinning very easy and straightforward.
If you don't know what we're talking about, take a look at Winamp, Sonic, Windows Media Player or Tagarela. All of these apps show cute irregular interfaces with nice graphics instead of the regular rectangular windows.
First, let me tell you that we're going to cover two different topics in this tutorial. Regions are one thing. Window skinning is another thing. Nevertheless, we need to use both to create those cool irregular skinned windows.
So, let's go on with the subject.

Regions

Quoting the Win32 documentation: "In Microsoft® Windows®, a region is a rectangle, polygon, or ellipse (or a combination of two or more of these shapes) that can be filled, painted, inverted, framed, and used to perform hit testing (testing for the cursor location)."
So, what do you understand from the above statement? Regions are just rectangles, polygons, or ellipses, and we can do some interesting things with it. Think of regions as shapes that we can build and manage.
The Win32 API provides some functions to handle regions, enabling us to create any shape we want using just those basic primitives. With some creativity we can create very complex shapes.
Let's see the arsenal available for us to use:
HRGN
This is just a data type, meaning "region handle". We need one for each region object we want to work with. The API will request or return a region handle when we call any region related function.
Example of use:

    HRGN hRegion = CreateRectRgn(x,y,x+128,y+128);

We need to DeleteObject() the region after using it.
CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn, CreateRectRgn, CreateRectRgnIndirect, CreateRoundRectRgn, EqualRgn, ExtCreateRegion, FillRgn, FrameRgn, GetPolyFillMode, GetRegionData, GetRgnBox, InvertRgn, OffsetRgn, PaintRgn, PtInRegion, RectInRegion, SetPolyFillMode.
These are the region functions provided by the Win32 API. As you can see, there are many of them available. We don't need and won't cover all these functions, just because after you figure how to create a rectangular region, you'll automatically know how to create an elliptical one, for example. After reading this article, please open the Win32 API documentation and continue reading. =)
SetWindowRgn
This is the function that performs the actual magic. We can attach|detach our region shape to any window.
OK then, so how do we use those functions to create a cool shape? Suppose we want a region-shaped window like the figure:
Actually this is very easy if you ask me. We need the following API functions to reproduce that shape:

    // this function will return an elliptical region.
    HRGN CreateEllipticRgn (
        int  nLeftRect,	// x-coordinate of the upper-left corner
        int  nTopRect,	// y-coordinate of the upper-left corner
        int  nRightRect,	// x-coordinate of the lower-right corner
        int  nBottomRect 	// y-coordinate of the lower-right corner
    );

    // this function will combine two regions
    int CombineRgn (
        HRGN  hrgnDest,	// handle of destination region
        HRGN  hrgnSrc1,	// handle of source region
        HRGN  hrgnSrc2,	// handle of source region
        int  fnCombineMode 	// region combining mode
    );

    // this function will attach a region to a window.
    // use NULL for the region handle to detach.
    int SetWindowRgn (
        HWND  hWnd,	        // handle to window whose window region is to be set
        HRGN  hRgn,	        // handle to region
        BOOL  bRedraw	// window redraw flag
    );

So let's write some real code to test it. After the initial window creation and setup, we just need to add these few lines to get the thing done:

    // --------------------------------------------------
    // create an elliptical region.
    // we use a negative starting y coordinate to make
    // the ellipse cover a bit more of the caption.
    // --------------------------------------------------
    HRGN hRegion1 = CreateEllipticRgn(20,-20,190,150);

    // --------------------------------------------------
    // create one more elliptical region in other place.
    // --------------------------------------------------
    HRGN hRegion2 = CreateEllipticRgn(140,100,300,240);

    // --------------------------------------------------
    // combine the two regions to build a new region
    // that will be the sum of the two.
    // the resulting region will be stored in region1,
    // like if we were making something like:
    //
    //          hRegion1 = hRegion1 + hRegion2.
    //
    // there are many RGN_ operations, take a look at
    // the CombineRgn() documentation.
    // --------------------------------------------------
    CombineRgn(hRegion1, hRegion1, hRegion2, RGN_OR);

    // --------------------------------------------------
    // attach the region to the window
    // --------------------------------------------------
    SetWindowRgn(hWnd, hRegion1, true);

    // --------------------------------------------------
    // delete the region objects
    // --------------------------------------------------
    DeleteObject(hRegion1);
    DeleteObject(hRegion2);

To detach the region, we just use:

    // --------------------------------------------------
    // detach the region
    // --------------------------------------------------
    SetWindowRgn(hWnd, NULL, true);

So is it all just that? Yes, it is. Really easy, isn't it? Yes, it is! You need to setup a window and build an application framework before doing that, of course. A complete example can be downloaded here: article_win32skins_regions.zip (13k)

Skins

The term "skin" is actually used to describe the graphics that are used to modify the visual appearance of an application. Many modern hi-tech applications are using bitmaps to cover the regular window and give the application a much more appealing appearance.
Until now, we are already able to create irregular windows, and our windows aren't just fake irregular, they are really irregular (you can see through and click through too). So what do we need now to make it yet more cool? Skins. We need one or more bitmaps and a way of putting those bitmaps over our irregular windows so that we get rid of the standard looking for ever.
Actually this is quite easy too, as you'll see. To keep things simple, we will use the same window and region we built above and modify it a little so that we can have a basic skin support.
The first step is the creation of a bitmap that fits exactly over the window. The example above creates a window of 320x240 pixels. So we'll just create a bitmap with 320x240 pixels too. Take a look:
Although this is not a top-notch skin, it certainly looks better than the standard looking, eh? Note that the skin bitmap itself is still rectangular, and we can't do anything about that. But it doesn't matter, as the window region will automatically clip the skin for us. We will just blit the skin bitmap to the window, and the region setup will take care of the dirty work.
So the actual steps to acomplish our objective are as follows:
1 - Load the bitmap;
2 - Create a device context for the skin and select the bitmap into it;
3 - Put a switch to toggle between normal and skinned modes;
4 - Hide the caption bar and lock window resizing when entering skinned mode;
5 - Show up the caption bar and unlock window resizing when leaving skinned mode;
6 - Blit the skin into to the window in response to a WM_PAINT message when in skinned mode;
7 - Handle WM_LBUTTON message so that the user can drag the window from any point when in skinned mode.
Sounds easy? It is. Let's do it step-by-step then:
1 - Load the bitmap:

    // -----------------------------------------------------------------------
    // load the skin bitmap from resource
    // -----------------------------------------------------------------------
    hSkinBmp = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_SKIN));
    if (!hSkinBmp) return -1;

As you can see, that is just standard code, nothing fancy. Of course we could load any bitmap format we wanted, like JPG, PNG, TGA, etc. But this is not the point of this tutorial, so we're keeping things really simple here.
2 - Create a device context for the skin and select the bitmap into it:

    // -----------------------------------------------------------------------
    // create a device context for the skin
    // -----------------------------------------------------------------------
    dcSkin = CreateCompatibleDC(0);

    // -----------------------------------------------------------------------
    // select the skin bitmap
    // -----------------------------------------------------------------------
    hOldBmp = (HBITMAP)SelectObject(dcSkin, hSkinBmp);

A note here is that I personally prefer to keep the HDC allocated all the time, as it is faster than recreating it and selecting the bitmap every time we need to blit to the screen. We just can't forget to release these objects before shutting the application down.
3 - Put a switch to toggle between normal and skinned modes:

    case VK_SPACE:
    {
      if (!bRegioned)
        RegionMe();
      else
        UnRegionMe();

      break;
    }

We will see the RegionMe() and UnregionMe() in a minute. The above fragment is placed inside the main window procedure, responding to a WM_KEYDOWN message. What we do here is toggle a switch (bRegioned) on|off and then calling the specific function that will setup the correct window mode.
4 - Hide the caption bar and lock window resizing when entering skinned mode:

    // ------------------------------------------------------------------------
    // Build a basic region and set it to the window.
    // ------------------------------------------------------------------------
    void RegionMe()
    {
      // --------------------------------------------------
      // create an elliptical region.
      // we use a negative starting y coordinate to make
      // the ellipse cover a bit more of the caption.
      // --------------------------------------------------
      HRGN hRegion1 = CreateEllipticRgn(20,-20,190,150);
      OffsetRgn(hRegion1, GetSystemMetrics(SM_CXBORDER)*4, GetSystemMetrics(SM_CYCAPTION));

      // --------------------------------------------------
      // create one more elliptical region in other place.
      // --------------------------------------------------
      HRGN hRegion2 = CreateEllipticRgn(140,100,300,240);
      OffsetRgn(hRegion2, GetSystemMetrics(SM_CXBORDER)*4, GetSystemMetrics(SM_CYCAPTION));

      // --------------------------------------------------
      // combine the two regions to build a new region
      // that will be the sum of the two.
      // the resulting region will be stored in region1,
      // like if we were making something like:
      // hRegion1 = hRegion1 + hRegion2.
      // --------------------------------------------------
      CombineRgn(hRegion1, hRegion1, hRegion2, RGN_OR);

      // --------------------------------------------------
      // assign the region to the window
      // --------------------------------------------------
      SetWindowRgn(hWnd, hRegion1, true);

      // --------------------------------------------------
      // delete the region objects
      // --------------------------------------------------
      DeleteObject(hRegion1);
      DeleteObject(hRegion2);

      // --------------------------------------------------
      // change window style (get rid of the caption bar)
      // --------------------------------------------------
      DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
      dwStyle &= ~(WS_CAPTION|WS_SIZEBOX);
      SetWindowLong(hWnd, GWL_STYLE, dwStyle);

      // --------------------------------------------------
      // force a window repainting
      // --------------------------------------------------
      InvalidateRect(hWnd, NULL, TRUE);
      SetWindowPos(hWnd, NULL, 0,0,320,242, SWP_NOMOVE|SWP_NOZORDER);

      // --------------------------------------------------
      // flag just to make sure our app knows about it.
      // --------------------------------------------------
      bRegioned = true;
    }

OK, now things got a little more advanced. But if you read carefully the above code, you'll see that it is really simple. It does some different things, though. First, it builds the already explained region for the window, and attach it. However, this time as the app starts with a caption and a border, we need to offset the region a little to compensate for that used space. Then, we use a little trick to hide the caption bar and lock window resizing. Finally, we force a WM_PAINT message with InvalidateRect() and SetWindowPos().
As we know, our window procedure will blit the skin to the window in response to a WM_PAINT message. We will get on that later.
5 - Show up the caption bar and unlock window resizing when leaving skinned mode:

    // ------------------------------------------------------------------------
    // Remove the region from the window
    // ------------------------------------------------------------------------
    void UnRegionMe()
    {
      // --------------------------------------------------
      // unassign the region
      // --------------------------------------------------
      SetWindowRgn(hWnd, NULL, true);

      // --------------------------------------------------
      // change window style (show caption bar again)
      // --------------------------------------------------
      DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
      dwStyle |= WS_CAPTION|WS_SIZEBOX;
      SetWindowLong(hWnd, GWL_STYLE, dwStyle);

      // --------------------------------------------------
      // force a window repainting
      // --------------------------------------------------
      InvalidateRect(hWnd, NULL, TRUE);
      SetWindowPos(hWnd, NULL, 0,0,320,240, SWP_NOMOVE|SWP_NOZORDER);

      // --------------------------------------------------
      // flag just to make sure our app knows about it.
      // --------------------------------------------------
      bRegioned = false;
    }

OK, the above code is the switch back to the normal window mode. You see a SetWindowRgn() with NULL for the region handle, then the WS_CAPTION|WS_SIZEBOX being put back to the window, and an enforcement for a window repainting.
6 - Blit the skin into to the window in response to a WM_PAINT message when in skinned mode:

    case WM_PAINT:
    {
      // -------------------------------------------------
      // tell user to press SPACE to toggle region.
      // -------------------------------------------------

      PAINTSTRUCT ps;
      BeginPaint(hWnd,&ps);

      // blit the skin
      if (bRegioned) SkinMe(ps.hdc);

      // show text
      SetBkMode(ps.hdc,TRANSPARENT);
      SetTextColor(ps.hdc,RGB(255,0,0));
      TextOut(ps.hdc, 115,90, "Press SPACE", 11);

      EndPaint(hWnd,&ps);

      break;
    }

This fragment starts with the common BeginPaint() call. Then we call SkinMe() - the function that will blit the skin to the window. After that there is a TextOut() just to tell the user to press SPACE to switch modes.
Note that the SkinMe() is called only when we're in the skinned mode (bRegioned).
The actual SkinMe() function is like this:

    // ------------------------------------------------------------------------
    // Skin the window
    // ------------------------------------------------------------------------
    void SkinMe(HDC dc)
    {
      // --------------------------------------------------
      // this is just as easy as blitting the skin
      // --------------------------------------------------
      BitBlt(dc, 0,0,320,240, dcSkin, 0,0, SRCCOPY);
    }

Actually pretty simple, isn't it? But what you wanted then? We have done all the needed things to prepare the app for a nice blit. The device context is ready, the window device context was passed by the WM_PAINT, so we just need to blit the skin and we're done.
7 - Handle WM_LBUTTON message so that the user can drag the window from any point when in skinned mode:

    case WM_LBUTTONDOWN:
    {
      // ---------------------------------------------------------
      // this is a common trick for easy dragging of the window.
      // this message fools windows telling that the user is
      // actually dragging the application caption bar.
      // ---------------------------------------------------------
      if (bRegioned) SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION,NULL);
      break;
    }

This is a little trick to fool windows making it think that the user is actually clicking the caption bar. So the window will be dragged if the user clicks anywhere on it. Note that the trick is done only when the app is in the skinned mode.
Allright, now we know how to do nice irregular skinned windows. But we have to setup an application and write all that common stuff to initialize and bring the app up and running. Grab the complete example here: article_win32skins_skinregion.zip (52k)

Advanced Regions

So what, did you think we're done??? Definitely not. You don't want to be limited to just a few rectangles and ellipses to make the shape of your application windows, do you? I am sure you want to create some dramatic shapes, with highly intrincate curves and holes. So what can we do to enhance our windows shapes?
The answer is that we'll have to write our own external utility that will scan bitmaps for us and generate a pixel-perfect transparent region file of any complexity we want. With this utility at hands, we just need to load the generated file and attach to the window. No more fiddling with region coordinates, rectangles and ellipses. Sounds good? But it is, dude. Take a look at this fragment:

    // -------------------------------------------------------------------------------------
    // Scan a bitmap and return a perfect fit region.
    // The caller must release the region object...
    // (this method starts with a full region and excludes inside the loop)
    // -------------------------------------------------------------------------------------
    HRGN ScanRegion(HBITMAP pBitmap, BYTE jTranspR, BYTE jTranspG, BYTE jTranspB)
    {
      // bitmap width and height
      WORD wBmpWidth,wBmpHeight;

      // the final region and a temporary region
      HRGN hRgn, hTmpRgn;

      // 24bit pixels from the bitmap
      BYTE *pPixels = Get24BitPixels(pBitmap, &wBmpWidth, &wBmpHeight);
      if (!pPixels) return NULL;

      // create our working region
      hRgn = CreateRectRgn(0,0,wBmpWidth,wBmpHeight);
      if (!hRgn) { delete pPixels; return NULL; }

      // ---------------------------------------------------------
      // scan the bitmap
      // ---------------------------------------------------------
      DWORD p=0;
      for (WORD y=0; y<wBmpHeight; y++)
      {
        for (WORD x=0; x<wBmpWidth; x++)
        {
          BYTE jRed   = pPixels[p+2];
          BYTE jGreen = pPixels[p+1];
          BYTE jBlue  = pPixels[p+0];

          if (jRed == jTranspR && jGreen == jTranspG && jBlue == jTranspB)
          {
            // remove transparent color from region
            hTmpRgn = CreateRectRgn(x,y,x+1,y+1);
            CombineRgn(hRgn, hRgn, hTmpRgn, RGN_XOR);
            DeleteObject(hTmpRgn);
          }

          // next pixel
          p+=3;
        }
      }

      // release pixels
      delete pPixels;

      // return the region
      return hRgn;
    }

If you really read this code at all, you will find that it first create a rectangular region of the same size as the bitmap passed as a parameter. Then it scans pixel-by-pixel through the whole bitmap and, whenever it finds a pixel of the same color as the specified, it excludes that pixel from the region. At the end of the scan, the function has a perfect region set to the bitmap shape.
At one other place, we just save the scanned region to a file, without any special processing.
I won't go deeper at the utility, first because this text is going too extended and second because there is really no need for that, I suppose at this point that you got the point already. The central point of the utility is described above. So, let's accelerate, just get the RegionCreator right here: article_win32skins_regioncreator.zip (19k)
RegionCreator was made as a console utility, so you will have to spawn a DOS box and call it:
regioncreator < bitmap.bmp > < r > < g > < b >
bitmap.bmp: the bitmap to be scanned
r,g,b : the transparent color (in decimal, eg: 255 255 255)

Loading Advanced Regions

If you're going to use the RegionCreator utility you just downloaded, you'll need to load the advanced region files from somewhere. I am making some assumptions on this tutorial, and one is that I'm assuming you'll put the region file in the resource and retrieve from there when needed. Well, loading from an external file is just easier, so we're lucky. =)
To retrieve the region file, you just need this piece of code:

    // -------------------------------------------------
    // retrieve the skin region from resource.
    // -------------------------------------------------

    // ask resource for our skin.
    HRSRC hrSkin = FindResource(hInstance, MAKEINTRESOURCE(IDB_SKIN),"BINARY");
    if (!hrSkin) return false;

    // this is standard "BINARY" retrieval.
    LPRGNDATA pSkinData = (LPRGNDATA)LoadResource(hInstance, hrSkin);
    if (!pSkinData) return false;

    // create the region using the binary data.
    HRGN rgnSkin = ExtCreateRegion(NULL, SizeofResource(NULL,hrSkin), pSkinData);

    // free the allocated resource
    FreeResource(pSkinData);

After this little step, you have the region with the rgnSkin handle. So all you need to do is continue as explained before, attaching the region to the window and so on.
Do not forget to delete the region after you finish using it, though. Use a DeleteObject(rgnSkin) to do the job.

Encapsulated Enhanced Irregular Skin Class

Now that we have all the code needed to make a cool skinned application, why not to go just a last step further and make a class to encapsulate the skin initialization, loading and blitting? Doing that, we can easily skin many windows without having to handle lots of device contexts, bitmap handles, and toggles. Our code will be much more clean and easy to manage.
So I did that already for you. Just download the completed CSkin class with a sample here: article_win32skins_skinclass.zip (65k)
The CSkin class is all automated, will load the enhanced regions from resource, and will do the skinning work without any intervention, so you don't have to do anything besides initialization (and the initialization is done with just one or two lines at maximum).
The class goes yet a little step further, doing windowing subclassing, but I'll not explain this topic on this article. Subclassing is a topic by itself, so for now, I'll let it alone and schedule a new article. =)
You can download all of the code from this article here: article_win32skins_code.zip (149k)
Be good.
Cheers,
Vander Nunes
http://www.virtware.net

Additional Comments and Notes

27 October 2002 - This is an update to the CSkin class source-code, included on the "Win32 Window Skin" article. Download: skinclass_fixes1.zip (67k) These updates are all suggested by people who read the article on flipcode, saw some things that I didn't see at the time I wrote the article, and sent their suggestions to me. I feel happy with these contributions.
Thanks to:

> - Elliot Kim
> - Peter Waller
> - Vlad S. (Lothix)
> - Darrol Wlash
> - Mark Mikulec

for their suggestions and improvements. Below are the original messages these people sent, in cronological order.
Elliot Kim

Vander,
Excellent tutorial! One of the clearest I have seen on this subject. I have found a couple of bugs though for which I have the fix. First, when you unhook the skin in the skin class destructor, the window has already been destroyed. Therefore, the SetWindowRgn and SetWindowLong calls fail in the UnHook method. To remedy this, I added a Destroy method to the skin class. Just call the Destroy method prior to destroying the window. The Destroy method contains everything that was in the original skin class destructor method.
Also, I initialized m_bHooked to false and m_OldWndProc to NULL. This prevents the Hook method from unnecessarily UnHooking the first time through.
Cheers,
Elliot
Peter Waller

Hi Vander,
I saw your article on flipcode.com regarding Window Skinning.. and wanted to offer an additional tip (which you may add as an amendment to your article if you please).
When regions are complex they become slow, you'll notice this when dragging the window around the desktop.. even on a high end pc they appear somewhat jerky. So it is best to not "over do" the non-rectangular thing! anyway, thats not what I'm writing to you about.. I recently coded an interface for a demo-scene chipdisc which adopts the same technique.
Find a screenshot + download here: http://www.pouet.net/prod.php?which=5213
Anyway, Windows 2000/XP has a function called "SetLayeredWindowAttributes" which allows you to set the opacity or transparency of a layered window. Although this is available in the Platform SDK, we need to import it manually so our executable will run on Win9x machines ( else export will fail ).
Some Source:

    #define LWA_COLORKEY  0x00000001
    #define WS_EX_LAYERED  0x00080000

    typedef BOOL (WINAPI *lpfnSetLayeredWindowAttributes)(HWND hWnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags);
    lpfnSetLayeredWindowAttributes SetLayeredWindowAttributes;

    HMODULE hUser32 = GetModuleHandle("user32.dll");
    SetLayeredWindowAttributes = (lpfnSetLayeredWindowAttributes)GetProcAddress(hUser32,"SetLayeredWindowAttributes");

That takes care of our importing, now we have to check to see if the user is running 2000/XP:

    bool isRunning2000XP()
    {
     bool bPlatform;
     OSVERSIONINFO osVer = { sizeof(osVer) };
     GetVersionEx(&osVer);
     bPlatform = (VER_PLATFORM_WIN32_NT == osVer.dwPlatformId && osVer.dwMajorVersion >= 5);
     return bPlatform;
    }

So all we need to do now when we setup our skinning:

    if ( isRunning2000XP() )
    {
     // update window style
     SetWindowLong(g_hWnd, GWL_EXSTYLE, GetWindowLong(g_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
     SetLayeredWindowAttributes(g_hWnd, g_ColourKey, 0, LWA_COLORKEY);
    } else
     SetupWin9xRegion();

g_ColourKey being the part we wish to make transparent, eg. 0xff00ff
Thats all there is to it, which makes it much much smoother under 2000/XP!
Anyway, thought I'd share that with you.. I'd be thrilled if you amended your article with it.. since a lot of people do use 2000/XP and can enjoy a more pleasant non-rectangular gui!!
Cheerio,
Peter Waller.
aka sulph / rebels.
Another response from Peter:
Vander,
Thanks for your email, good to hear from you! Nice to hear you're updating the article.. although it is already very good! Anyway, main reason for reply, in skinclass.zip, skintest.exe doesn't exit properly after you close the window, the process continues to run.. after checking your source, I found it only exits properly when you press escape!! most people use alt-f4 when theres no visible exit button? I dunno.. thought I'd pass that comment on.. its only minor fix:

    case WM_DESTROY:
     PostQuitMessage( 0 );

Ignore me if I'm being picky ;)
Looking forward to seeing the update.
Regards,
Peter.
Vlad S. (Lothix)

Hi there!
Wonderful tutorial, thank you for writing it up! I have one suggestion though.
Your message handling loop

      MSG mMsg;

      while (1)
      {
        if(PeekMessage(&mMsg, 0, 0, 0, PM_REMOVE))
        {
          // -------------------------------------------------
          // the quit message
          // can arrive at any time from our window procedure.
          // -------------------------------------------------
          if(mMsg.message == WM_QUIT)
          { break; }

          // -------------------------------------------------
          // the common stuff, just translate&dispatch.
          // -------------------------------------------------
          TranslateMessage(&mMsg);
          DispatchMessage(&mMsg);
        }
      }

Can be better written as

    MSG mMsg;
    while( GetMessage(&mMsg, NULL, 0, 0) ) {

        if( PeekMessage(&mMsg, 0, 0, 0, PM_REMOVE) )
            if( mMsg.message == WM_QUIT ) break;

        TranslateMessage( &mMsg );
        DispatchMessage( &mMsg );
    }

drops cpu utilization from 99% (deal loop =/) to 0%.
- lothix
Darrol Wlash

I run XP and the skins do not line up with the region. The Advanced region woks great. Thank you for putting this together.
The problem is with the skin blitting. If you change the following

    void SkinMe(HDC dc)
    {
      // --------------------------------------------------
      // this is just as easy as blitting the skin
      // --------------------------------------------------
      BitBlt(dc, 0,0,320,240, dcSkin, 0,0, SRCCOPY);
    }

to:

    void SkinMe(HDC dc)
    {
      // --------------------------------------------------
      // this is just as easy as blitting the skin
      // --------------------------------------------------
      BitBlt(dc, 0,5,320,240, dcSkin, 0,0, SRCCOPY);
    }

In XP it doesn't clip due to the removal of the title bar. I am not sure why this is. I will continue to look into it.
Darroll
Mark Mikulec

Hi Vander,
Let me start off by saying you've presented a very well written, easy to understand tutorial on skinning and regions. I'll definitely be using it!
Just wanted to let you know, there'a minor bug in your final sample, the one with the Orb and hand project.. upon window destruction, the application process is not completely destroyed. It's gone from the task manager, but in the process list it still sitting there. (This is Windows 2000) Sounds like an orphaned device context, handle or something. Just a heads up.
Regards,
Mark

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.