Controls and the Desktop Window Manager

Of all the Windows Vista for Developers series articles that I have written, the one about the Desktop Window Manager (DWM) is by far the most popular considering the blog traffic statistics and the amount of email I receive with questions about glass.

By far the most common question I hear is about how to get controls to render correctly on glass. If you recall, I wrote the DWM article before the release of Windows Vista to manufacturing. Those early builds included a hack that allowed you to easily draw controls on glass by tweaking the transparency key. I demonstrated this technique in the article. Unfortunately, when Microsoft finally released Windows Vista this “hack” no longer worked leaving developers to wonder how it could be done.

Having said my piece about DWM and what with being extremely busy with my day job and various other commitments I never quite found the time to present alternative solutions. For the sake of my email inbox, I am going to present a simple solution to address the most common request:

How can I display an edit control on glass?!

There are a number of ways of solving this sort of problem. Specifically, there are a number of ways to override the default drawing behavior of the standard and common controls.

You can handle WM_PAINT and draw it yourself. This tends to be more work than it’s worth so most developers avoid it, however it does allow you all the freedom necessary to paint with the necessary alpha channel information to render correctly on glass. My DWM sample demonstrates this technique, albeit not with a control.

Another solution is to owner draw controls. This is a lot of work but a lot less than the WM_PAINT approach as the system does a lot of the work for you. Owner draw is nice and works for most but not all controls. Notably it does not work with edit controls.

An even simpler approach is custom draw but this works with an even smaller set of controls.

Finally, for a small number of controls you can handle WM_CTLCOLORxxx to set the text and background color of the control.

Considering the options listed thus far, only the last one actually supports edit controls and does not involve a gargantuan amount of work. Unfortunately, it does not play nicely with glass since the technique involves GDI primitives that do not support alpha blending.

I repeat: How can I display an edit control on glass?!

It is at times like these that I wonder why I don’t work for Microsoft and am not compensated for the articles I post on my blog…  :)

Having received yet another email yesterday asking about edit controls on glass I decided to poke around the Windows SDK to see if it has anything new to offer. By the way, if you do not regularly read the Windows SDK I highly encourage that you do so. On a hunch, I started my search in the Themes and Visual Styles section of the SDK. After all, this is the subsystem responsible for providing the look and feel for controls.

The first thing I noticed was a set of functions added to UxTheme.dll in Windows Vista to support buffered painting. At first this might not seem all that interesting since the DWM already provides a degree of double buffering, but having the ability to buffer painting operations means that you can capture, alter, and then paint the updated bitmap. Of course, this is nothing new and could be achieved manually but the new functions provided by Visual Styles just simplify the task and solves our immediate problem quite nicely.

The Buffered Paint API

The buffered paint API provides a set of functions to provide efficient off-screen painting to a device context (DC). Since painting is done to a DC, it means that your investment in GDI is not lost. That’s right folks, you don’t have to port your entire application to Windows Presentation Foundation just yet.

To start, you should call the BufferedPaintInit function at least once per thread to initialize the API for use. Each call to BufferedPaintInit must eventually be matched with a call to BufferedPaintUnInit on the same thread.

To begin a buffered paint operation, simply call the BeginBufferedPaint function. It accepts a target DC and target rectangle identifying the eventual location that the buffer will be copied to. A few additional paramteters allow you to control the buffer characteristics. One of these is the format of the buffer and thankfully, it supports device-independent bitmaps (DIBs) necessary for most alpha blending operations. BeginBufferedPaint then returns a handle that can be passed to various other buffered paint API functions as well as a DC that you can use to paint to the buffer.

One such function that you can pass the buffered paint handle to is BufferedPaintSetAlpha. It allows you to easily update the alpha channel for the entire buffer and set it to a single value to control the level of transparency/opacity. Note that it updates the alpha value for each pixel in the buffer to a single value.

Finally you can copy the buffer to the target DC and free the associated resources allocated by BeginBufferedPaint with a single call to the EndBufferedPaint function.

By now, you can probably imagine where I am going with this. You simply need to use the buffered paint API to create a buffer to paint the edit control into and then set the alpha channel on the buffer before updating the window DC. Let us bring it home with a sample.

The BufferedPaint class

The following class wraps the buffered paint API to simplify its use from C++.

class BufferedPaint
{
public:
 
    BufferedPaint() :
        m_handle(0)
    {
        COM_VERIFY(::BufferedPaintInit());
    }
 
    ~BufferedPaint()
    {
        COM_VERIFY(::BufferedPaintUnInit());
    }
 
    HRESULT Begin(HDC targetDC,
                  const RECT& targetRect,
                  BP_BUFFERFORMAT format,
                  __in_opt BP_PAINTPARAMS* options,
                  __out CDCHandle& bufferedDC)
    {
        ASSERT(0 == m_handle);
 
        m_handle = ::BeginBufferedPaint(targetDC,
                                        &targetRect,
                                        format,
                                        options,
                                        &bufferedDC.m_hDC);
 
        HRESULT result = S_OK;
 
        if (0 == m_handle)
        {
            result = HRESULT_FROM_WIN32(::GetLastError());
        }
 
        return result;
    }
 
    HRESULT End(bool updateTargetDC)
    {
        ASSERT(0 != m_handle);
 
        HRESULT result = ::EndBufferedPaint(m_handle,
                                            updateTargetDC);
 
        m_handle = 0;
        return result;
    }
 
    HRESULT SetAlpha(__in_opt const RECT* rect,
                     BYTE alpha)
    {
        ASSERT(0 != m_handle);
 
        return ::BufferedPaintSetAlpha(m_handle,
                                       rect,
                                       alpha);
    }
 
private:
 
    HPAINTBUFFER m_handle;
 
};

The OpaqueEdit class

The following C++ class is used to subclass the system’s edit control so that we can more easily take over its painting duties.

class OpaqueEdit :
    public CWindowImpl<OpaqueEdit, CEdit>
{
public:
 
    BEGIN_MSG_MAP_EX(OpaqueEdit)
        MESSAGE_HANDLER(WM_PAINT, OnPaint)
        REFLECTED_COMMAND_CODE_HANDLER_EX(EN_CHANGE, OnChange)
        DEFAULT_REFLECTION_HANDLER()
    END_MSG_MAP()
 
private:
 
    LRESULT OnPaint(UINT /*message*/,
                    WPARAM /*wParam*/,
                    LPARAM /*lParam*/,
                    BOOL& /*handled*/)
    {
        CPaintDC targetDC(m_hWnd);
        CDCHandle bufferedDC;
 
        if (SUCCEEDED(m_bufferedPaint.Begin(targetDC,
                                            targetDC.m_ps.rcPaint,
                                            BPBF_TOPDOWNDIB,
                                            0, // options
                                            bufferedDC)))
        {
            SendMessage(WM_PRINTCLIENT,
                        reinterpret_cast<WPARAM>(bufferedDC.m_hDC),
                        PRF_CLIENT);
 
            COM_VERIFY(m_bufferedPaint.SetAlpha(0, // entire buffer
                                                255)); // 255 = opaque
 
            // Copy buffered DC to target DC
            COM_VERIFY(m_bufferedPaint.End(true));
        }
 
        return 0;
    }
 
    void OnChange(UINT /*notifyCode*/,
                  int /*control*/,
                  HWND /*window*/)
    {
        VERIFY(InvalidateRect(0, // entire window
                              FALSE)); // don't erase background
    }
 
    BufferedPaint m_bufferedPaint;
 
};

As you can see in my WM_PAINT handler, I send the edit control the WM_PRINTCLIENT message, instructing it to paint into the buffered DC. I then set the buffer’s alpha channel to 255 (completely opaque) and update the target DC. What might come as a surprise is the need for the EN_CHANGE handler. Since the edit control paints outside of the WM_PAINT message, I need to paint “over” the extra painting that occurs when the control’s text changes. In this example, I simply invalidate the control, which forces it to receive a new WM_PAINT message. I could probably optimize this further but it is sufficient for this example. Keep in mind that since the DWM provides double buffering automatically you should not notice any flicker due to the repeated painting.

The SampleDialog class

The following class simply ensures that the window is represented as a “sheet” of glass and uses the OpaqueEdit class to subclass the edit control.

class SampleDialog :
    public CDialogImpl<SampleDialog>
{
public:
 
    enum { IDD = IDD_SAMPLE };
 
    BEGIN_MSG_MAP(MainWindow)
        MSG_WM_INITDIALOG(OnInitDialog)
        MSG_WM_ERASEBKGND(OnEraseBackground)
        COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
        REFLECT_NOTIFICATIONS()
    END_MSG_MAP()
 
private:
 
    bool OnInitDialog(HWND /*control*/,
                      LPARAM /*lParam*/)
    {
        const MARGINS margins = { -1 };
 
        COM_VERIFY(::DwmExtendFrameIntoClientArea(m_hWnd,
                                                  &margins));
 
        VERIFY(m_edit.SubclassWindow(GetDlgItem(IDC_CONTROL)));
 
        return true; // Yes, go ahead and set the keyboard focus.
    }
 
    bool OnEraseBackground(CDCHandle dc)
    {
        CRect rect;
        VERIFY(GetClientRect(&rect));
 
        dc.FillSolidRect(&rect,
                         RGB(0,0,0));
 
        return true; // Yes, I erased the background.
    }
 
    LRESULT OnCancel(WORD /*notifyCode*/,
                     WORD identifier,
                     HWND /*window*/,
                     BOOL& /*handled*/)
    {
        VERIFY(EndDialog(identifier));
        return 0;
    }
 
    OpaqueEdit m_edit;
};

That is all there is to it. I hope the techniques described in this article will help in your path to adopting Windows Vista as a developer.


© 2007 Kenny Kerr 

 

Published Tuesday, January 23, 2007 11:07 PM by KennyKerr

Comments

# re: Controls and the Desktop Window Manager

Saturday, January 27, 2007 8:46 PM by Chris Sells

I give up -- why don't you work at Microsoft? Want to?

# re: Controls and the Desktop Window Manager

Monday, January 29, 2007 6:11 PM by Nish

I second what Karin Kerr has said. I've known Kenny for a couple of years now through his blog and via chat, and I met Kenny once over lunch in Toronto, and I believe he'd be an awesome asset for Microsoft to have.

# re: Controls and the Desktop Window Manager

Monday, January 29, 2007 11:19 PM by hayate

What a pity. I think bill himself is self taught and has no university qualifications, and such a BIG company like Microsoft must hire all kinds of talent people in any way.

I support U Kenny. Good luck.

# re: Controls and the Desktop Window Manager

Saturday, February 3, 2007 10:56 AM by Aaron Weiker

I feel for you!! I know that Microsoft does in fact hire people without degrees as I am one of them. I didn't have the same problem you did as I was born in the US; but I do empathize with you as I have dealt with my fair share of ignorant people who think a degree makes you qualified. I have met many people where the oposite is true. I have even interviewed candidates that have a phd and I still wouldn't find them qualified.

So maybe Bush did have a good idea in his worker visa program that everyone is rejecting??

Just hang in there, I hope that things will work out for you.

# re: Controls and the Desktop Window Manager

Thursday, March 29, 2007 1:56 AM by KennyKerr

If all you want is to paint a bitmap, and not a control as in my example, you can simply use the BufferedPaint class from my sample in your WM_PAINT handler. First draw the bitmap onto the buffered DC and then copy the buffered DC to the target paint DC as shown in the example. If your source image happens to have an alpha channel, such as a 32-bit DIB bitmap then you can use the AlphaBlend function directly, which is one of the few GDI functions that honor the alpha channel.