tags:

views:

321

answers:

3

I've got a sizable dialog with one child window - a list control. When the dialog is re-sized, I re-size the list control appropriately; it is basically anchored to all 4 edges of the dialog. The problem is that during sizing there is noticeable flicker around the edges of the list control, especially when the scroll bars are present. I am a novice in Win32 GUI stuff, so I don't really know how to handle this. I've seen a lot of articles about flicker-free drawing, but they are all about individual custom-drawn controls and none of them deal with flicker-free drawing of a dialog as a whole. How can I get this to work without flickering so much?

My actual dialog has multiple controls obviously, but here is a minimal code example that reproduces the issue (IDC_LIST1 is a list control in Report view, IDD_DIALOG2 has WS_CLIPCHILDREN style set).

#define NUM_COLUMNS  8
#define NUM_ROWS    32

RECT rcDialog2WindowOriginal;
RECT rcDialog2ClientOriginal;
RECT rcList1ClientOriginal;

INT_PTR Dialog2_OnInitDialog(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
    GetWindowRect(hDlg, &rcDialog2WindowOriginal);
    GetClientRect(hDlg, &rcDialog2ClientOriginal);
    GetWindowRect(GetDlgItem(hDlg, IDC_LIST1), &rcList1ClientOriginal);
    ScreenToClient(hDlg, ((LPPOINT)&rcList1ClientOriginal));
    ScreenToClient(hDlg, ((LPPOINT)&rcList1ClientOriginal) + 1);
    SendDlgItemMessage(hDlg, IDC_LIST1, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
    TCHAR szText[32];
    // add some columns
    LVCOLUMN col;
    ZeroMemory(&col, sizeof(LVCOLUMN));
    col.mask = LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH;
    col.cx = 60;
    col.pszText = szText;
    for(int i = 0; i < NUM_COLUMNS; i++)
    {
     col.iSubItem = i;
     _stprintf_s(szText, 32, _T("Column %d"), col.iSubItem);
     SendDlgItemMessage(hDlg, IDC_LIST1, LVM_INSERTCOLUMN, col.iSubItem, LPARAM)&col);
    }
    // add some items
    LVITEM item;
    ZeroMemory(&item, sizeof(LVITEM));
    item.mask = LVIF_TEXT;
    item.pszText = szText;
    for(int i = 0; i < NUM_ROWS; i++)
    {
     item.iItem = i;
     for(int j = 0; j < NUM_COLUMNS; j++)
     {
      item.iSubItem = j;
      _stprintf_s(szText, 32, _T("Item %d, SubItem %d"), i, j);
      if(j == 0)
      {
       SendDlgItemMessage(hDlg, IDC_LIST1, LVM_INSERTITEM, 0, (LPARAM)&item);
      }
      else
      {
       SendDlgItemMessage(hDlg, IDC_LIST1, LVM_SETITEM, 0, (LPARAM)&item);
      }
     }
    }
    // autosize the columns
    for(int i = 0; i < NUM_COLUMNS; i++)
    {
     SendDlgItemMessage(hDlg, IDC_LIST1, LVM_SETCOLUMNWIDTH, i, LVSCW_AUTOSIZE);
    }
    return TRUE;
}

INT_PTR Dialog2_OnGetMinMaxInfo(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
    LPMINMAXINFO lpMinMaxInfo = (LPMINMAXINFO)lParam;
    // don't allow dialog to be resized smaller than original size
    lpMinMaxInfo->ptMinTrackSize.x = rcDialog2WindowOriginal.right - rcDialog2WindowOriginal.left;
    lpMinMaxInfo->ptMinTrackSize.y = rcDialog2WindowOriginal.bottom - rcDialog2WindowOriginal.top;
    return TRUE;
}

INT_PTR Dialog2_OnSize(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
    int cx = LOWORD(lParam);
    int cy = HIWORD(lParam);
    // anchor the list control to all edges of the dialog
    int left_delta = rcList1ClientOriginal.left - rcDialog2ClientOriginal.left;
    int right_delta = rcDialog2ClientOriginal.right - rcList1ClientOriginal.right;
    int top_delta = rcList1ClientOriginal.top - rcDialog2ClientOriginal.top;
    int bottom_delta = rcDialog2ClientOriginal.bottom - rcList1ClientOriginal.bottom;
    int left = left_delta;
    int top = top_delta;
    int width = cx - left_delta - right_delta;
    int height = cy - top_delta - bottom_delta;
    HWND hWndList1 = GetDlgItem(hDlg, IDC_LIST1);
    SetWindowPos(hWndList1, NULL, left, top, width, height, SWP_NOZORDER);
    return TRUE;
}

INT_PTR Dialog2_OnClose(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
    EndDialog(hDlg, IDOK);
    return TRUE;
}

INT_PTR CALLBACK Dialog2_DialogProc(HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
    switch(nMsg)
    {
    case WM_INITDIALOG:
     return Dialog2_OnInitDialog(hDlg, wParam, lParam);
    case WM_GETMINMAXINFO:
     return Dialog2_OnGetMinMaxInfo(hDlg, wParam, lParam);
    case WM_SIZE:
     return Dialog2_OnSize(hDlg, wParam, lParam);
    case WM_CLOSE:
     return Dialog2_OnClose(hDlg, wParam, lParam);
    }
    return FALSE;
}

Update

After taking a look at many other Windows applications (even ones written by Microsoft), every single one of them suffers the same flickering issues. It is particularly evident when resizing a window with both a status bar and scroll bar from the top left. I guess I will just have to live with it.

+1  A: 

A lot of flicker comes from WM_ERASEBKGRD, handle this message and just return TRUE;

FigBug
The child control doesn't cover the entire dialog, so if you ignore WM_ERASEBKGRD then the dialog background is never painted when it is resized.
Luke
+1  A: 

You can do several things.

  • Handle WM_ERASE yourself. You may still need to erase some of the background, but you can draw 4 rectangles around the edges of the window to fill the background area of the dialog without overdrawing the rectangle in the middle where your child control sits.

  • After calling SetWindowPos, try calling UpdateWindow() to immediately repaint the window (try it on both the list view control and your own window). This minimises the time between you making the size change and the redraw being completed.

  • Another approach is double buffering (you draw the entire window surface to an offscreen bitmap, and only draw it to the screen when it's all finished. This minimises the time between the start and end of the update proicess on screen, so reduces flicker. This is harder to achieve with a dialog and a windows control though, so not really applicable in your case).

  • In general, don't be afraid to experiment with things. Try doing things in different orders, etc and see what the results are. If something (like UpdateWindow()) doesn't give a noticeable improvement, then it's easy to remove the code again, and try something else until you get the best results.

edit - additional ideas

  • see also this SO question

  • When updating controls you can also suspend and resume repainting (BeginUpdate() and EndUpdate()) to stop them drawing more than once when you are adding many items, etc. THis is not likely to help with window resizing though.

Jason Williams
Handling WM_ERASEBKGRD doesn't help; the flickering seems to be occurring during the processing of the list control.UpdateWindow() had no effect; I think SetWindowPos() already sends a WM_PAINT message anyway.I wanted to look at double buffering, but that seems to be a per-control thing; there doesn't seem to be a way to double buffer an entire dialog.Windows Explorer has the same behavior I am seeing with my dialog, so if Microsoft can't be bothered to do it I think I will just have to live with the flicker.
Luke
Try WM_ERASE - WM_ERASEBKGRD may behave differently (I've programmed UI in Win32 for 16 years and have never heard of or used WM_ERASEBKGRD). See also the edit to my answer.
Jason Williams
A: 

After taking a look at many other Windows applications (even ones written by Microsoft), every single one of them suffers the same flickering issues. It is particularly evident when resizing a window with both a status bar and scroll bar from the top left. I guess I will just have to live with it.

Luke