Vista Aero DWM seems to optimize out GDI paint calls

In this post: Foxpro Menu items, combo boxes not refreshing selected item under Aero in Vista I describe a problem in Foxpro where menu and list items that are supposed to be non-selected aren’t painted correctly. I described a workaround: call the GdiSetBatchLimit API, which limits the GDI paint functions that are batched. Apparently some calls are being optimized out or not executed.

The problem occurs whether run as Administrator or not, but requires Desktop Composition enabled.

(Vista Aero can have DwmIsCompositionEnabled enabled or disabled. See Control Panel->System and Maintenance->System->Advanced System Settings->Advanced->Performance->Settings->Visual Effects->Enable desktop composition)

On Windows XP, the program runs as expected.

I’ve managed to create a standalone simple C++ program that demonstrates the behavior. The entire VS 2005 project is available here

The program displays a dozen items with the first item selected (inverted colors). The user can hit the down or up arrows to move the selection. If you hit the down arrow once, the first item is painted unselected, and the next item is painted selected. That’s fine. If you hit down arrow a few more times, the prior item doesn’t get repainted unselected.

I’ve put a call to GdiSetBatchLimit on Left or Right Arrow. Left will set it to 1, Right to 20 (the default). If you hit the left arrow, then the list selection behaves normally (you don’t even have to restart the app). Right arrow reproduces the problem behavior.

The problem seems related to calling SelectClipRgn

See also: Windows Vista Aero BorderStyle Paint problem as non Administrator

To create the sample project:

Start VS 2005: choose File->New Project-> Visual C++ Win32 Win32 Project. Call it VistaItems

In the Win32 Application Wizard, just choose all the defaults and Finish.

In the WndProc, add this case in the Switch to handle the WM_CREATE message:

      case WM_CREATE:

            {

                  RECT rect;

                  GetClientRect(hWnd,&rect);

                  rect.right/=2;

                  rect.left= 50;

                  CMenuWin *pCMenuWin = new (CMenuWin);

                  HWND hwndChild =

                  pCMenuWin->Create(hWnd, &rect,L"CMenuWin",

                        WS_VISIBLE | WS_CLIPCHILDREN | WS_POPUP

                  , WS_EX_NOPARENTNOTIFY);

                  if (hwndChild == 0)

                  {

                        int n = GetLastError();

                        DebugBreak();

                       

                  }

                  SetParent(hwndChild, hWnd);

                  g_hwndChild = hwndChild;

            }

            break;

      case WM_KEYDOWN:

            return SendMessage(g_hwndChild, message, wParam, lParam);

        break;

Add these lines after this line #include "VistaItems.h"

#include "atlbase.h"

#include "atlwin.h"

#define NUMITEMS 12

#define MAXITEMSIZE 10

 

struct DATA

{

      TCHAR strText[MAXITEMSIZE];

      int nState; // 0 or 1: selected or not

} Data[NUMITEMS] = {L"One",1,L"Two",0,L"Three",0,L"Four",0,

                              L"Five",0,L"Six",0,L"Seven",0,L"Eight",0,

                              L"Nine",0,L"Ten",0,L"Eleven",0,L"Twelve",0

                              };

 

 

int g_nCurItem = 0;

HDC g_hdc=0;

void GetRect(int nIndex, RECT * rect)

{

  rect->left=10;

  rect->right = 400;

  rect->top = nIndex * 20;

  rect->bottom = rect->top + 20;

}

void ShowItems(HWND hWnd, int nIndex, HDC hdc)

{

      RECT rect;

      GetRect(nIndex, &rect);

      HFONT hFont = (HFONT)GetStockObject(ANSI_VAR_FONT);

      COLORREF clr, clrold;

      Data[nIndex].nState = 1 - Data[nIndex].nState ; // invert state

      if (Data[nIndex].nState == 0)

      {

                  clr = RGB(255,0,0);

                  SetBkColor(hdc, RGB(0,0,0));

      } else

      {

                  clr = RGB(0,0,255);

                  SetBkColor(hdc, RGB(255,255,255));

      }

      clrold = SetTextColor(hdc, clr);

      ExtTextOut(hdc,

                        rect.left, rect.top, ETO_CLIPPED | ETO_OPAQUE ,

                        &rect, Data[nIndex].strText,(int)wcslen(Data[nIndex].strText), 0);

      SetTextColor(hdc, clrold);

}

HRGN g_hRgn = 0;

HWND g_hwndChild = 0;

class CMenuWin : public CWindowImpl<CMenuWin>

{

public:

// DECLARE_WND_CLASS_EX(L"CMenuWin", 0 /*CS_OWNDC*/, COLOR_MENUHILIGHT) // backcolor

      BEGIN_MSG_MAP(CMenuWin)

            MESSAGE_HANDLER(WM_KEYDOWN,OnKeyDown)

            MESSAGE_HANDLER(WM_PAINT,OnPaint)

      END_MSG_MAP()

      LRESULT OnKeyDown(UINT uMsg,WPARAM wParam, LPARAM lParam, BOOL & bHandled)

      {

            HDC hdc;

        hdc = GetDC();

            if (g_hRgn) {

                  DeleteObject(g_hRgn);

                  g_hRgn = 0;

            }

            RECT rect;

            GetRect(g_nCurItem, &rect);

            g_hRgn = CreateRectRgnIndirect(&rect);

            SelectClipRgn(hdc,g_hRgn);

        ShowItems(m_hWnd, g_nCurItem, hdc);

            switch(wParam)

            {

            case VK_LEFT: // hit left arrow

                  GdiSetBatchLimit(1); // this will make it behave as expected

                  break;

            case VK_RIGHT: // hit right arrow

                  GdiSetBatchLimit(20); // this will show the broken behavior

                  break;

            case VK_DOWN: // down arrow

                  if (++g_nCurItem == NUMITEMS)

                  {

                  g_nCurItem=0;

                  }

                  break;

            case VK_UP:

                  if (g_nCurItem-- == 0)

                  {

                        g_nCurItem=NUMITEMS-1;

                  }

                  break;

            }

            if (g_hRgn) {

                  DeleteObject(g_hRgn);

                  g_hRgn = 0;

            }

            GetRect(g_nCurItem, &rect);

            g_hRgn = CreateRectRgnIndirect(&rect);

            SelectClipRgn(hdc,g_hRgn);

        ShowItems(m_hWnd, g_nCurItem, hdc);

            ReleaseDC(hdc);

            return 0;

      }

      LRESULT OnPaint(UINT uMsg,WPARAM wParam, LPARAM lParam, BOOL & bHandled)

      {

            PAINTSTRUCT ps;

        HDC hdc = BeginPaint(&ps);

        for (int i = 0 ; i < NUMITEMS ; i++)

        {

              ShowItems(m_hWnd, i, hdc);

        }

            EndPaint(&ps);

            return 0;

      }

};

End of code