Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

List view customizations

0.00/5 (No votes)
23 Apr 2001 1  
List view sort on header click and showing 'No items' with 'More' tooltip

Sample Image - LVSample.jpg

Introduction

WinMain simply calls WinMain_Internal. WinMain_Internal does the following:

  1. Initializes common controls.
    INITCOMMONCONTROLSEX InitCC;
    
    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
    
        0;
    InitCC.dwSize    = sizeof(INITCOMMONCONTROLSEX);
    
    if(!InitCommonControlsEx(&InitCC))
        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.

    Notes:

    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.

  • 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));
        SetRectEmpty(lpRectMore);
        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.
      ListView_SetExtendedListViewStyle
      (
          hwndLV,
          LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES | 
                     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; 
        iColCount++);
      
      for(iColIdx = 0; iColIdx < iColCount; iColIdx++)
      {
          _tcscpy(lpszBuffer, g_pcSampleListColumns[iColIdx].lpszName);
          lvCol.cx            = 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).
      BOOL 
      MainDlg_OnListColumnClick(HWND hWnd, LPARAM lParam)
      {
          LPNMLISTVIEW    lpNMLV;
          int        iColumnClicked;
      
          lpNMLV = (LPNMLISTVIEW)lParam;
          iColumnClicked = lpNMLV->iSubItem;
      
          MainDlg_SetSortOnColumn(hWnd, iColumnClicked);
          MainDlg_SortList(hWnd);
      
          return FALSE;
      }

      Let's take a closer look:

      VOID
      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);
          }
          else
          {
              // 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