Partager via


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

Comments

  • Anonymous
    May 03, 2007
    It might have something to do with painting stuff outside of the WM_PAINT handler. In the WM_KEYDOWN handler, try just updating state doing an InvalidateRect to force a WM_PAINT and see if that fixes it. The problem is, with the DWM, it needs to know when it has to copy the in-memory bitmap into the texture it actually draws to the screen. The most obvious place is after a WM_PAINT (but there are other times that it does it -- the rules are complex) and this can often lead to such problems. The most common thing I've noticed is apps which draw directly to a DC during input handlers (to show things like selections and so on) and this looks exactly like what you've done here.

  • Anonymous
    May 04, 2007
    Dean: thanks for your comments. Adding an InvalidateRect causes low priority WM_PAINT messages to cause the rect to redraw, but if the user holds down the down arrow key, the highlight behaves very sluggishly because input messages have higher priority: several items appear highlighted at the same time. Adding a GdiFlush() after painting the deselected item causes the problem to go away. This code has been working for around 15 years in various versions of Windows: no doubt many apps are still working in older versions of Foxpro.

  • Anonymous
    May 09, 2007
    The comment has been removed

  • Anonymous
    May 24, 2007
    Please try this code DEFINE POPUP _popShortcutMenu ; FROM MROW(),MCOL() ; MARGIN ; RELATIVE ; SHORTCUT DEFINE POPUP _SubMenu ; SHORTCUT RELATIVE DEFINE BAR 1 OF _popShortcutMenu PROMPT "Item 1" DEFINE BAR 2 OF _popShortcutMenu PROMPT "Item 2 With sub" DEFINE BAR 3 OF _popShortcutMenu PROMPT "Item 3" ON BAR 2 OF _popShortcutMenu ACTIVATE Popup _SubMenu Define Bar 1 Of _SubMenu Prompt "SubMenu 1" Define Bar 2 Of _SubMenu Prompt "SubMenu 2" ACTIVATE POPUP _popShortcutMenu *RELEASE POPUP _popShortcutMenu *RELEASE POPUP _SubMenu WinXP shows correctly the submenu at the right side of the bar "Item 2 With sub". Try the same code with Win Vista with Aero interface. The submenu is always shown at the LEFT SIDE of the bar (which is ugly), and, if the shortcut menu is activated near the left side of the monitor, it becomes unusable, since the user can't read the submenu itself. Any workaround?

  • Anonymous
    July 18, 2007
    ya, my FoxPro 8.0 application is doing the same thing, In Vista it will pop up to the left side even if it's running out of space but work perfectly in WinXP. Did you find any workaround on this case? Thanks!!

  • Anonymous
    August 10, 2007
    Here are some of the links that I often visit regarding VFP9 SP2 and Sedna. The official Microsoft Visual...

  • Anonymous
    June 30, 2008
    Hi We are facing problem with image filling in List View in VB6. It works very well with XP; but with vista - images are disproportionate in List View. We create DIB and apply wrapper behind the image (to make them all of uniform size) and load them into imagelist. Imagelist is bound to List View. Any solutions ..if someone can offer around gdi+, will be great.