Insert and Replace in a List Control

Environment: VC6 SP5, NT4 SP6

The normal way to replace items in a list control is to remove an item and then insert a new item. This can get quite tedious, so I needed to allow users to insert or replace items in a list control. To do this, I added functionality to show the user when a replace would be done by highlighting a line in a different colour so that the user could easily tell whether data would be inserted or replaced.

  1. The first step is to overide and create a new class CListCtrlEx that inherits from CListCtrl.
  2. It is then necessary to add code that supports dragging and dropping between lists. I used the Microsoft Exsample found here as the basis of my drag and drop code. I copied the onLButtonUp, OnMouseMove, OnBeginDrag, and DropItemOnList functions changing them from CView to CListCtrl.
  3. I then added code to allow a row to be highlighted in another colour. This is described in the Codeguru article “Selection Highlighting of Entire Row” with accompanying source code here. I used this code as the basis of my row highlighting.
  4. I created an enumerated type to make it easier to understand whether a row will be inserted or replaced. There is also a member variable and a Set function.
  5. Enum EType
    {
      EInsert,
      EReplace
    };
    
    EDropType m_eType;
    void SetDropType(EDropType eType) {m_eType = eType;}
    
  6. Add to OnBeginDrag and the Constructor the default state.
  7.   m_eType = EInsert;
  8. I changed the drawing code by replacing LVIS_SELECTED with LVIS_DROPHILITED in functions IsRowSelected and UseOurHighlighting. This means that the change in colour for row highlighting will occur for dropping rather than selection. Then, I added new code to UseOurHighlighting so that replacing will be shown in a different colour from inserting.
  9. if (m_eType == EReplace)
    {re
      pcd->clrTextBk = m_clrHighlightBack;
    }
    else if (m_eType == EInsert)
    {
      pcd->clrTextBk = GetSysColor(COLOR_HIGHLIGHT);
    }
    

    It should be noted that this obviously requires co-operation between the screen layer and the data layer to decide whether or not to replace something. Although I have provided a sample, there would normally be a greater separation between data and screen.

  10. Add a new function.
  11. void SetDropType(const CPoint& WndPoint,
                           CListCtrlEx* pDropList)
    

    to the class header. It was decided that the replace highlight would be shown when the cursor was above the main body of text with Insert being when the cursor was at the top or bottom of the text. This meant it was necesssary to detect where the mouse was in relation to the highlighting of a row. If it was in the top or bottom two units of the row, it would display a normal insert colour; otherwise, the replacement colour would be used. A new member variable, m_nModifiedDropIndex, is used to hold the drop position as it is now possible to insert above or below a row.

    void CListCtrlEx::SetDropType(const CPoint& WndyPoint,
                                        CListCtrlEx* pDropList)
    {
      //don't allow dragging and dropping on the same list
      if (pDropList == this)
      {
        return;
      }
    
      CPoint ScreenPoint(WndyPoint);
      ClientToScreen(&ScreenPoint);
      CWnd* pWnd = CWnd::WindowFromPoint(ScreenPoint);
      CPoint WndPoint(ScreenPoint);
      pWnd->ScreenToClient(&WndPoint);
    
      CRect rect;
    
      //If a valid row has been selected
      if (m_nDropIndex != -1)
      {
        //Get the rectangle for the highlighted row
        pDropList->GetItemRect(m_nDropIndex, &rect, LVIR_BOUNDS);
      }
      else
      {
        //Trying to insert at the end of the list? So set the
        //Default State
        pDropList->SetDropType(EInsert);
      }
    
      if(rect.PtInRect(WndPoint))
      {
        if (abs(rect.top - WndPoint.y) <= 2)
        {
          pDropList->SetDropType(EInsert);
          m_nModifiedDropIndex = m_nDropIndex;
        }
        else if (abs(rect.bottom - WndPoint.y) <= 2)
        {
          pDropList->SetDropType(EInsert);
          m_nModifiedDropIndex = m_nDropIndex + 1;
        }
        else
        {
          pDropList->SetDropType(EReplace);
          m_nModifiedDropIndex = m_nDropIndex;
        }
      }
    }
    

    At this stage, it is now possible to insert above or below a row rather than the standard above functionality. The replace code is not yet fully in place.

  12. It is now necessary to modify the DropItemOnList function. I need to change this line:
  13.   lvi.iItem = (m_nModifiedDropIndex == -1) ?
                  pDropList->GetItemCount () :
                  m_nModifiedDropIndex;
    

    to use the Modified drop Index and the code beneath that to support Replace and Insert. Normally, there would be a separation between the data layer and the screen layer, but for the purposes of this example they are closely tied together.

      if (pDropList->m_eType == EReplace)
      {
        VERIFY (0 != pDropList->DeleteItem (m_nModifiedDropIndex));
    
        VERIFY (-1 != pDropList->InsertItem (&lvi));
    
        // select the new item we just inserted
        VERIFY (pDropList->SetItemState (lvi.iItem,
                                         LVIS_SELECTED,
                                         LVIS_SELECTED));
    
        // delete the original item (move operation)
        //VERIFY (DeleteItem (m_nDragIndex));
      }
    
      if (pDropList->m_eType == EInsert)
      {
        VERIFY (-1 != pDropList->InsertItem (&lvi));
    
        // select the new item we just inserted
        VERIFY (pDropList->SetItemState (lvi.iItem,
                                         LVIS_SELECTED,
                                         LVIS_SELECTED));
    
        // delete the original item (move operation)
        //VERIFY (DeleteItem (m_nDragIndex));
      }
    
  14. All that is now left to do is to call the function SetDropType that decides whether a Replace or Insert is possible. This is done in OnMouseMove by adding the call before the highlight line.
  15. SetDropType(point, (CListCtrlEx*)m_pDropWnd);

    pList->SetItemState (m_nDropIndex, LVIS_DROPHILITED,
    LVIS_DROPHILITED);

Downloads


Download demo project - 21 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read