views:

61

answers:

2

Consider a plain Win32 dialog with listview control (in report mode) written in C++. Upon a certain event all items and all columns are deleted and new columns and items are created. Basically, as content changes, columns are automatically generated based on content.

When old items/columns are removed and new ones added, listview flickers like hell. I have tried WM_SETREDRAW and LockWindowUpdate() with no change to visual experience.

I have even set extended listview style LVS_EX_DOUBLEBUFFER and that didn't help at all.

The parent dialog has WS_CLIPCHILDREN set.

Any suggestions how to make this work with as little flicker as possible? I am thinking of using two listviews, alternating visibility, using the hidden one as a back buffer but this sounds like an overkill. There must be an easy way.

+2  A: 

The default list control painting is pretty flawed. But there is a simple trick to implement your own double-buffering technique:

CMyListCtrl::OnPaint()
{
    CRect rcClient;
    GetClientRect(rcClient);

    CPaintDC dc(this);
    CDC dcMem;
    dcMem.CreateCompatibleDC(&dc);

    CBitmap bmMem;
    bmMem.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
    CBitmap* pbmOld = dcMem.SelectObject(&bmMem);

    dcMem.FillSolidRect(rcClient, ::GetSysColor(COLOR_WINDOW));

    this->DefWindowProc(WM_PAINT, (WPARAM)dcMem.m_hDC, (LPARAM)0);

    dc.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dcMem, 0, 0, SRCCOPY);
    dcMem.SelectObject(pbmOld);

    CHeaderCtrl*    pCtrl = this->GetHeaderCtrl();
    if (::IsWindow(pCtrl->GetSafeHWnd())
    {
        CRect   aHeaderRect;
        pCtrl->GetClientRect(&aHeaderRect);
        pCtrl->RedrawWindow(&aHeaderRect);
    }
}

This will create a bitmap and then call the default window procedure to paint the list control into the bitmap and then blitting the contents of the bitmap into the paint DC.

You should also add a handler for WM_ERASEBKGND:

BOOL CMyListCtrl::OnEraseBkgnd(CDC* pDC)
{
    return TRUE;
}

This will stop the control from always erasing the background before a redraw. You can optimize the OnPaint further if you add a member variable for the bitmap and only (re)create it when the size of the window changed (because always creating a bitmap may be costly depending on the size of the window).

This should work pretty well.

humbagumba
I have worked with ListViews quite a bit - and there aren't any short cuts to making it work better - you really need to double buffer it to make it not flicker.
Mordachai
I have tried this after converting everything from MFC to plain Win32 but it works only partially. I have two problems. The minor one is that horizontal scrollbar flashes as columns are removed and added. But I could live with this as most of the flicker problem is solved. The second major problem is listview not repainting when columns are resized. I suppose the proper way is to listen to adequate notifications and repaint manually.
wpfwannabe
The only thing which should flicker with this trick is the header control. Did you perhaps subclass your own header which may cause the redraw problems?
humbagumba
No, I merely subclassed the listview and handled `WM_ERASEBKGND` and `WM_PAINT` as you suggested. I tried handling `HDN_TRACK` so I can repaint but it is never sent by the header control. The funny thing is that even without `WM_ERASEBKGND` and `WM_PAINT` handlers, merely subclassing the listview stops repainting on header resize.
wpfwannabe
What window styles do you have set? Try removing the WM_CLIPCHILDREN and/or WM_CLIPSIBLINGS. The list control I'm using has the following styles set according to Spy++: WS_CHILDWINDOW, WS_VISIBLE, WS_HSCROLL, WS_TABSTOP, LVS_REPORT, LVS_ALIGNLEFT and LVS_NOSORTHEADER. No redraw problems there.
humbagumba
Sorry about the confusion. There was a problem in my code in another place.
wpfwannabe
Set to answer for hard work and ideas that lead to solution. Thanks!
wpfwannabe
A: 

After trying many things and most of all humbagumba's suggestions I have come to a very simple conclusion. LockWindowUpdate is everybody's friend in this sort of situation. I am not sure how come it failed to work for me the first time but after custom painting failed to deliver in all situations I have tried LockWindowUpdate once again and it worked!

Basically, just wrap all work on listview in a LockWindowUpdate(hWnd) and LockWindowUpdate(NULL) and things work beautifully. There is not even a scrollbar flicker any more.

Just make sure not to nest LockWindowUpdate as only one window can be locked at a time.

wpfwannabe
Unless the user is dragging something with the mouse, LockWindowUpdate is NOT your friend, see http://blogs.msdn.com/b/oldnewthing/archive/2007/02/21/1735472.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2007/02/22/1742084.aspx
Anders