Click here to Skip to main content
15,796,904 members
Articles / Desktop Programming / MFC

List view customizations

Rate me:
Please Sign up or sign in to vote.
4.91/5 (5 votes)
23 Apr 20013 min read 138K   1.4K   56   5
List view sort on header click and showing 'No items' with 'More' tooltip

Sample Image - LVSample.jpg


WinMain simply calls WinMain_Internal. WinMain_Internal does the following:

  1. Initializes common controls.
    InitCC.dwICC =
    InitCC.dwICC =
        ICC_LISTVIEW_CLASSES  |  //  list view
        ICC_PROGRESS_CLASS    |  //  progress bar
        ICC_TREEVIEW_CLASSES  |  //  tree view & tooltip
        ICC_BAR_CLASSES       |  //  toolbar, status bar, trackbar & tooltip
        ICC_UPDOWN_CLASS      |  //  up-down
        ICC_USEREX_CLASSES    |  //  extended combo box
        ICC_TAB_CLASSES       |  //  tab & tooltip
        ICC_COOL_CLASSES      |  //  rebar
        ICC_DATE_CLASSES      |  //  date & time picker
    InitCC.dwSize    = sizeof(INITCOMMONCONTROLSEX);
        return -2;

    -1 is the standard return value for DialogBox failure, so we use -2. Of course, defining error constants with more readable values is recommended.


    1. Although Ole32.lib is linked, only VARIANT comparations and BSTR manipulator are used. So, OleInitialize call is not required.
    2. Not all of the ICC_* constants are required for this sample. You may choose the appropriate set that fits your needs.
    3. The sample assumes the list view in report mode.
  2. Calls DialogBox to show our sample dialog.

The dialog has 4 handlers.

<olo type="a">
  • MainDlg_OnClose: simply calls EndDialog with return code 0. It does not guarantee that the list view content is stable (still modifying its items, style etc.). A cleanup routine that returns a BOOL (TRUE = the window cannot be closed at this time), or forcing the end of current operation(s) should be implemented in order to avoid data corruption or Windows crashes.
  • MainDlg_OnInitDialog initializes the child list view and posts a WM_SIZE message to itself to force the call of the resize handler.

    MainDlg_InitListView, the list view initialization routine:

    1. Calls LV_SetInitial. It does the following:
      • Sets a list view property indicating that the list view has no items (useful for WM_PAINT).
        SetProp(hWnd, _T("HasItems"), (HANDLE)(FALSE));
      • Sets the initial rect for the part of the text ("More...") displayed in the list view when the list has no items:
        lpRectMore = (LPRECT)malloc(sizeof(RECT));
        SetProp(hWnd, _T("RECT_More"), (HANDLE)(lpRectMore));
      • Gets the list view tooltips, add our own tool and set the tooltip window handle as a property of the list view window. (This has a bug - after adding our tool, the label tip functionality is not working - I have to correct this):
        // Set tooltip
        hwndTT = ListView_GetToolTips(hWnd);
        ToolInfo.cbSize    = sizeof(TOOLINFO);
        ToolInfo.uFlags    = TTF_TRANSPARENT | TTF_CENTERTIP;
        ToolInfo.hwnd      = hWnd;
        ToolInfo.uId       = 0;
        ToolInfo.hinst     = NULL;
        ToolInfo.lpszText  = LPSTR_TEXTCALLBACK;
        GetClientRect(hWnd, &ToolInfo.rect);
        ToolTip_AddTool(hwndTT, &ToolInfo);
        ToolTip_TrackActivate(hwndTT, &ToolInfo);
        ToolTip_SetTipMaxWidth(hwndTT, GetRectWidth(ToolInfo.rect) / 2);
        phwndTT = (HWND *)malloc(sizeof(HWND));
        *phwndTT = hwndTT;
        SetProp(hWnd, _T("Tooltip_HWND"), (HANDLE)(phwndTT));
    2. Sets the list view styles to full row select, subitem images, gridlines and labeltip.
                     LVS_EX_GRIDLINES | LVS_EX_LABELTIP
    3. Inserts the columns looking in the g_pcSampleListColumns global variable.
      // invariant
      lvCol.mask            = LVCF_TEXT | LVCF_WIDTH;
      lvCol.pszText        = lpszBuffer;
      for(iColCount = 0; 
        g_pcSampleListColumns[iColCount].lpszName[0] != 0; 
      for(iColIdx = 0; iColIdx < iColCount; iColIdx++)
          _tcscpy(lpszBuffer, g_pcSampleListColumns[iColIdx].lpszName);
            = g_pcSampleListColumns[iColIdx].uCXCol;
          lvCol.cchTextMax    = _tcslen(lpszBuffer);
          ListView_InsertColumn(hwndLV, iColIdx, &lvCol);
    4. Subclasses the list view.
      // Subclass list view
      DefLVProc = SubclassWindow(hwndLV, LVProc);
    5. Gets the header control, subclasses it, and makes all its items owner-drawn:
      // Subclass header
      hwndHdr = ListView_GetHeader(hwndLV);
      DefLVHeaderProc = SubclassWindow(hwndHdr, LVHeaderProc);
      for(iColIdx = 0; iColIdx < iColCount; iColIdx++)
          HDITEM hdItem;
          memset(&hdItem, 0, sizeof(HDITEM));
          hdItem.mask = HDI_FORMAT;
          if(Header_GetItem(hwndHdr, iColIdx, &hdItem))
              hdItem.mask = HDI_FORMAT;
              hdItem.fmt |= HDF_OWNERDRAW;
              Header_SetItem(hwndHdr, iColIdx, &hdItem);
  • MainDlg_OnSize is the resize handler. It repositions the list view centered in the dialog in corner (1, 1) and with size decreased with 2 or width and height, if possible.
  • MainDlg_OnNotify responds to notifications from the list view: the header click (LVN_COLUMNCLICK) and key down (LVN_KEYDOWN).
    1. MainDlg_OnKeyDown intercepts two keys: F5 refreshes the list view (i.e. populates it with some sample items), and DEL erases its content. Nothing ensures that the list view is stable at the moment of operations (i.e. you can press DEL during a list view update). You have to protect the list view on your own. Perhaps the best (and simplest) mechanism is to reflect the notifications (as the ON_NOTIFY_REFLECT does) to the list view, so the control will know how to operate on its data, styles, content etc. and how to avoid side effects or concurrent access.
    2. MainDlg_OnListColumnClick will modify the sort orientation according to the global variable that holds the column information (will set the sort order, priority, or, if the sort became inactive, will affect the priorities of the following column(s) in the sort order).
      MainDlg_OnListColumnClick(HWND hWnd, LPARAM lParam)
          LPNMLISTVIEW    lpNMLV;
          int        iColumnClicked;
          lpNMLV = (LPNMLISTVIEW)lParam;
          iColumnClicked = lpNMLV->iSubItem;
          MainDlg_SetSortOnColumn(hWnd, iColumnClicked);
          return FALSE;

      Let's take a closer look:

      MainDlg_SetSortOnColumn(HWND hWnd, int iColIdx)
          int  iNextPrior;
          int  iSortOrder;
          HWND hwndLV;
          HWND hwndHdr;
          hwndLV  = GetDlgItem(hWnd, IDLV_REPORT);
          hwndHdr = ListView_GetHeader(hwndLV);
          // Get next sort order: none -> ascending, 
          // ascending -> descending, descending -> none
          MainDlg_GetColumnSortPriority(hWnd, iColIdx, &iNextPrior);
          // No sort, so now is ascending and
          // put it last on priority order.
          if(iNextPrior == LVSORTPRIORITY_NONE)
              // Get the next sort index (or first,
              // if is the only column sorted).
              MainDlg_FindNextSortPriority(hWnd, &iNextPrior);
              // Set the column sort to ascending.
              MainDlg_SetColumnSortOrder(hWnd, iColIdx, LVORDER_ASCENDING);
              // Set the column sort priority.
              MainDlg_SetColumnSortPriority(hWnd, iColIdx, iNextPrior);
              // Get the order.
              MainDlg_GetColumnSortOrder(hWnd, iColIdx, &iSortOrder);
              if(iSortOrder == LVORDER_ASCENDING)
                  // Set sort to descending. The priority is not affected.
                  MainDlg_SetColumnSortOrder(hWnd, iColIdx, LVORDER_DESCENDING);
              else // descending
                  // Set sort to to none.
                  // Decrease priority for all the subsequent
                  // columns that follows in the list.
                  MainDlg_SetColumnSortOrder(hWnd, iColIdx, LVORDER_NONE);
                  MainDlg_DecreasePriority(hWnd, iColIdx);
          // Header redraw, the images were changed.
          InvalidateRect(hwndHdr, NULL, TRUE);

      The list sorting routine has nothing special. It looks on the data type it has to compare, converts the cells' data from string to the appropriate type (looking on SAMPLELISTVIEWCOLUMN nSortType member (it can be LVORDER_STRING (VT_BSTR), LVORDER_NUMERIC (VT_I4) or LVORDER_DATETIME (VT_DATE), in this sample) and performs the VARIANT comparison calling VarCmp if the conversion succeeded. If an error occurred (conversion, comparison, etc.), the items are considered equal.

      List view and header

      The list view implements the following:

      • The mouse messages for tooltip (Tooltip_RelayEvent); all of them are routed to the LV_OnMouseMessage.
      • LV_OnDestroy handler removes all the properties added to the list view handle.
      • The LV_OnPaint handler is called only when the list view has no items. If it has, then the default window procedure is called.
      • Finally, LV_OnDrawItem draws the header items (all were set as owner drawn in LV_SetInitial).

      The rest is GDI 'acrobatics'.

    • License

      This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

      A list of licenses authors might use can be found here

      Written By
      Web Developer
      Romania Romania
      This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

      Comments and Discussions

      Generalquestion Pin
      Member 457620314-Sep-10 1:49
      Member 457620314-Sep-10 1:49 
      Questionsummarize particular listview column data Pin
      ayeshika27-Jun-08 9:10
      ayeshika27-Jun-08 9:10 
      GeneralCompiler Error Pin
      cpp_prgmer6-Feb-06 20:40
      cpp_prgmer6-Feb-06 20:40 
      error C2079: 'stINITCOMMONCONTROLSEX' uses undefined struct 'INITCOMMONCONTROLSEX'

      The above is the error I get on using this function. Could I get suggestion on the possible reasons for this error and how does one resolve them?
      GeneralComplier error... Pin
      huangashen14-Jul-04 3:47
      huangashen14-Jul-04 3:47 
      GeneralRe: Complier error... Pin
      Cristian Amarie27-Dec-04 5:39
      Cristian Amarie27-Dec-04 5:39 

      General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

      Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.