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