Of what use is the RDW_INTERNALPAINT flag?


For motivational purposes, let's start with a program that displays a DWM thumbnail.

Start with the scratch program and add the following:

#include <dwmapi.h>

HWND g_hwndThumbnail;
HTHUMBNAIL g_hthumb;

void UpdateThumbnail(HWND hwndFrame, HWND hwndTarget)
{
 if (g_hwndThumbnail != hwndTarget) {
  g_hwndThumbnail = hwndTarget;
  if (g_hthumb != nullptr) {
   DwmUnregisterThumbnail(g_hthumb);
   g_hthumb = nullptr;
  }

  if (hwndTarget != nullptr) {
   RECT rcClient;
   GetClientRect(hwndFrame, &rcClient);
   if (SUCCEEDED(DwmRegisterThumbnail(hwndFrame,
                         g_hwndThumbnail, &g_hthumb))) {
    DWM_THUMBNAIL_PROPERTIES props = {};
    props.dwFlags = DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE;
    props.rcDestination = rcClient;
    props.rcDestination.top += 50;
    props.fVisible = TRUE;
    DwmUpdateThumbnailProperties(g_hthumb, &props);
   }
  }
 }
}

The Update­Thumbnail function positions a thumbnail of the target window inside the frame window. There's a small optimization in the case that the target window is the same one that the thumbnail is already viewing. Overall, no big deal.

void
OnDestroy(HWND hwnd)
{
 UpdateThumbnail(hwnd, nullptr);
 PostQuitMessage(0);
}

When our window is destroyed, we need to clean up the thumbnail, which we do by updating it to a null pointer.

For the purpose of illustration, let's say that pressing the 1 key changes the thumbnail to a randomly-selected window.

struct RANDOMWINDOWINFO
{
 HWND hwnd;
 int cWindows;
};

BOOL CALLBACK RandomEnumProc(HWND hwnd, LPARAM lParam)
{
 if (hwnd != g_hwndThumbnail &&
     IsWindowVisible(hwnd) &&
     (GetWindowStyle(hwnd) & WS_CAPTION) == WS_CAPTION) {
  auto prwi = reinterpret_cast<RANDOMWINDOWINFO *>(lParam);
  prwi->cWindows++;
  if (rand() % prwi->cWindows == 0) {
   prwi->hwnd = hwnd;
  }
 }
 return TRUE;
}

void ChooseRandomWindow(HWND hwndFrame)
{
 RANDOMWINDOWINFO rwi = {};
 EnumWindows(RandomEnumProc, reinterpret_cast<LPARAM>(&rwi));
 UpdateThumbnail(hwndFrame, rwi.hwnd);
}

void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
{
 switch (ch) {
 case TEXT('1'):
  ChooseRandomWindow(hwnd);
  break;
 }
}

 HANDLE_MESSAGE(hwnd, WM_CHAR, OnChar);

The random window selector selects among windows with a caption which are visible and which are not already being shown in the thumbnail. (That last bit is so that when you press 1, it will always pick a different window.)

Run this program, and yippee, whenever you press the 1 key, you get a new thumbnail.

Okay, but usually your program shows more than just a thumbnail. It probably incorporates the thumbnail into its other content, so let's draw some other content, too. Say, a single-character random message.

TCHAR g_chMessage = '?';

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
 if (!IsRectEmpty(&pps->rcPaint)) {
  RECT rcClient;
  GetClientRect(hwnd, &rcClient);
  DrawText(pps->hdc, &g_chMessage, 1, &rcClient,
           DT_TOP | DT_CENTER);
 }
}

void ChooseRandomMessage(HWND hwndFrame)
{
 g_chMessage = rand() % 26 + TEXT('A');
 InvalidateRect(hwndFrame, nullptr, TRUE);
}

void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
{
 switch (ch) {
 case TEXT('1'):
  ChooseRandomWindow(hwnd);
  break;
 case TEXT('2'):
  ChooseRandomMessage(hwnd);
  break;
 }
}

Now, if you press 2, we change the random message. There is a small optimiztion in Paint­Content that skips the rendering if the paint rectangle is empty. Again, no big deal.

Okay, but sometimes there are times where your program wants to update the thumbnail and the message at the same time. Like this:

void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
{
 switch (ch) {
 case TEXT('1'):
  ChooseRandomWindow(hwnd);
  break;
 case TEXT('2'):
  ChooseRandomMessage(hwnd);
  break;
 case TEXT('3'):
  ChooseRandomWindow(hwnd);
  ChooseRandomMessage(hwnd);
  break;
 }
}

Run this program and press 3 and watch the thumbnail and message change simultaneously.

And now we have a problem.

You see, the Choose­Random­Window function updates the thumbnail immediately, since the thumbnail is presented by DWM, whereas the Choose­Random­Message function updates the message, but the new message doesn't appear on the screen until the next paint cycle. This means that there is a window of time where the new thumbnail is on the screen, but you still have the old message. Since painting is a low-priority activity, the window manager is going to deliver other messages to your window before it finally gets around to painting, and the visual mismatch between the thumbnail and the message can last for quite some time. (You can exaggerate this in the sample program by inserting a call to Sleep.) What can we do to get rid of this visual glitch?

One solution would be to delay updating the thumbnail until the next paint cycle. At the paint cycle, we update the thumbnail and render the new message. Now both updates occur at the same time, and you get rid of the glitch. To trigger a paint cycle, we can invalidate a dummy 1×1 pixel in the window.

HWND g_hwndThumbnailWanted;

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
 UpdateThumbnail(hwnd, g_hwndThumbnailWanted);

 if (!IsRectEmpty(&pps->rcPaint)) {
  RECT rcClient;
  GetClientRect(hwnd, &rcClient);
  DrawText(pps->hdc, &g_chMessage, 1, &rcClient,
           DT_TOP | DT_CENTER);
 }
}

void ChooseRandomWindow(HWND hwndFrame)
{
 RANDOMWINDOWINFO rwi = {};
 EnumWindows(RandomEnumProc, reinterpret_cast(&rwi));
 g_hwndThumbnailWanted = rwi.hwnd;
 RECT rcDummy = { 0, 0, 1, 1 };
 InvalidateRect(hwndFrame, &rcDummy, FALSE);
}

Now, when we want to change the thumbnail, we just remember what thumbnail we want (the "logical" current thumbnail) and invalidate a dummy pixel in our window. The invalid dummy pixel triggers a paint cycle, and in our paint cycle, we call Update­Thumbnail to synchronize the logical current thumbnail with the physical current thumbnail. And then we continue with our regular painting (in case there is also painting to be done, too).

But it sure feels wasteful invalidating a pixel and forcing the Draw­Text to occur even though we really didn't update anything. Wouldn't it be great if we could just say, "Could you fire up a paint cycle for me, even though there's technically nothing to paint? Because I actually do have stuff to paint, it's just something outside your knowledge since it is not rendered by GDI."

Enter the RDW_INTERNAL­PAINT flag.

If you pass the RDW_INTERNAL­PAINT flag to Redraw­Window, that means, "Set the 'Yo, there's painting to be done!' flag. I know you think there's no actual painting to be done, but trust me on this." (It's not actually a flag, but you can think of it that way.)

When the window manager then get around to deciding whether there is any painting to be done, before it concludes, "Nope, this window is all valid," it checks if you made a special RDW_INTERNAL­PAINT request, and if so, then it will generate a dummy WM_PAINT message for you.

Using this new flag is simple:

 g_hwndThumbnailWanted = rwi.hwnd;
 // RECT rcDummy = { 0, 0, 1, 1 };
 // InvalidateRect(hwndFrame, &rcDummy, FALSE);
 RedrawWindow(hwndFrame, nullptr, nullptr,
              RDW_INTERNALPAINT);

Now, when the program wants to update its thumbnail, it just schedules a fake-paint message with the window manager. These fake-paint messages coalesce with real-paint messages, so if you do an internal paint and an invalidation, only one actual paint message will be generated. If the paint message is a fake-paint message, the rcPaint will be empty, and you can test for that in your paint handler and skip your GDI painting.

Comments (11)
  1. Deduplicator says:

    How does RDW_INTERNALPAINT interact with UpdateWindow? I hope MSDN just dropped the ball and internal paints are honored there too…

    And is there a corresponding mechanism to Validate Rect for saying thanks, but I changed my mind? Could not find anything.

    Thanks, and keep it up please.

    [Update­Window respects RDW_INTERNAL­PAINT. Basically, setting the internal paint flag means "Act as if the update region is nonempty (even though it might actually be empty)." -Raymond]
  2. Deduplicator says:

    Sorry, withdrawing my 2nd question, just found a flag for RedrawWindow.

    BTW: Could you enlighten me why Microsoft splintered the API reference, so there are separate and conflicting copies mostly focused for all those versions? That makes it quite easy to look at the wrong docs and not realizing that parts dont apply to you and which ones. It also burries the right docs under nefarious false friends.

    [Can you point me to examples of multiple conflicting copies? I know the Windows CE team created their own copy since their OS is a Windows subset, but their docs are clearly labeled "Windows CE." -Raymond]
  3. Deduplicator says:

    Well, haven't found two conflicting pages claiming the exact same systems yet. But taking RedrawWindow as an example, there are at least 6 pages, one each for: desktop, ce5, ce6, ce6.5, ce7compact, ce.net. All ce pages seem to contain the same basic info with few differences (dropped min versions/different ratings/possibility to add community content (not found any actual ommunity content for the ce pages)/sometimes linking to _one_ of the other pages/…). The only significant difference seems to be to the desktop page, which lists two more flags than the rest and has community contributions.

    Are you sure if one of those pages is corrected for something, all the others will be done too? And how about missing the vital clue (hidden in community content on one of the pages you are not looking at just then), even if all the other content is completely reproduced? What i personally find most irritating is 1: having to hunt down the differences, complicated by the difficulty of moving from any page to corresponding pages in different sets, especially if porting/developing not only for Windows Y. and 2: Having my search for information on SomeApiFunctionOrFlag spammed by nearly right pages which might be dead wrong. Both parts lead to serious frustrations.

    PS: Thanks for the answers you already gave.

    [When the desktop pages are updated, the Windows CE pages are not, because that would be wrong. For example, suppose a new flag is added to Windows 8. It would be wrong to add that flag to the Windows CE 5 documentation because the flag is not supported in Windows CE 5. If you are not targeting Windows CE, then don't read the Windows CE documentation. -Raymond]
  4. rs says:

    I found RDW_INTERNALPAINT useful when rendering content on demand in WM_PAINT: Whenever the underlying content changes, I post an RDW_INTERNALPAINT message. Then, in WM_PAINT, I compute the updated layout and invalidate the regions affected by the changes before calling BeginPaint.

    One thing I remember is that apparently you couldn't combine RDW_INTERNALPAINT with some other flags in a single call to RedrawWindow (possibly RDW_UPDATENOW, but I don't remember).

  5. Lars says:

    rs, I believe this is what Raymond is alluding to when he says "It's not actually a flag, but you can think of it that way."

    I can't be bothered to look it up in the header file, but perhaps it's a combination of flags which would conflict with yours, or it's a magic constant, or something.

    [I meant that the thing that says "This window needs a WM_PAINT message" is not a flag. RDW_INTERNAL­PAINT is a flag. -Raymond]
  6. msdn libb says:

    Often the first msdn online help page VS shows of API calls when selecting a WinApi function and pressing F1 is a 5-10 year old Windows CE page, even when developing straight win32 apps.

  7. "I meant that the thing that says "This window needs a WM_PAINT message" is not a flag. RDW_INTERNAL­PAINT is a flag. -Raymond"

    I am sure I am not the only one that was initially confused both by the original wording in the article and this "clarification", which merely repeats the mistake of the article text.  When reading: "The thing that says "…" etc", the "thing" I seem to be reading about is RDW_INTERNALPAINT.  This creates an apparent contradiction when on the one hand you said "The thing … is not a flag." and then immediately after "[The thing] IS a flag".

    I took me a couple of reads, both of the original and the clarification, to realise that what you were trying to say was:

    Whilst RDW_INTERNALPAINT is a flag, the effect it has on the window manager is to trigger a message to be sent, not set a flag.

    [Right. There are two flags in question here, the one you pass to Redraw­Window and the one the window manager uses to remember that a paint message is needed. The second one is not actually a flag. -Raymond]
  8. Rick C says:

    @msdn libb, yeah, it's a little annoying when you search Google or Bing and the first result is a CE hit, not a desktop hit.  But they're getting better over time, and moving the desktop result up.  I can't remember OTTOMH but there's slight differences in wordings in the summary you get on the search results page that lets you know whether it's desktop or CE versions of the function.

  9. Deduplicator says:

    In regard to searching: They start favoring the desktop pages over arbitrary ce pages? Good for 'desktop' only developers, even though its not good enough until its reliable that you get the desktop page.

    Still does not help anyone developing for ce-x.y or worse yet needing info from two or more split docs. In the last case you really want the differences highlighted.

    @raymond: I intentionally said corrected not updated beecause updating could mean making things conform to an expanded new version of the api, while correcting only implies faulty docs (which might be duplicated inclusive any errors).

  10. Surely it's much easier and less confusing to just say "There is only one flag.  The mechanism it invokes doesn't involve a flag at all." ?

    Unless you had tongue firmly inserted into cheek of course.  :)

    Not that is matters now.  The ball has long since gone.  :)

    [It's convenient to think that each window has a "Yo, there's painting to be done!" flag, even if internally it is not implemented by a flag. That's all I'm saying. Didn't realize that there would be so much confusion over that. -Raymond]
  11. ZijingWu says:

    Great! I always follow this blog.

    I think it will be very helpful, If you can share the whole application code by an Zip file as an attachement. Investigate the runtime behavior will be very helpful to understanding the code.

Comments are closed.