65.9K
CodeProject is changing. Read more.
Home

Rearrange rows in a ListView control by drag-and-drop without MFC/C++

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (6 votes)

Aug 10, 2001

2 min read

viewsIcon

139309

Teach you how to rearrange rows in a ListView control by drag-and-drop without MFC/C++

Introduction

There have been already several articles about this topic. But I saw none written in C (no C++ code). For those who insist in traditional C programming, I'd like to provide a from-A-to-Z tutorial to make you easy to add this user-friendly feature to your application.

The code

All the below codes are assumed to be in your MainWndProc() function.

When user keeps pressing the mouse button down, and moves his mouse a little, a notification message (LVN_BEGINDRAG) will be sent to your MainWndProc() via WM_NOTIFY. You should handle this message by:

  1. Create a drag-image for all selected items. ListView provides a function to create a single-row drag-image. We will use this function to create multiple drag-images for each row, and merge them together.
  2. Call ImageList_Begin to initialize a drag-and-drop, then ImageList_DragEnter to start a drag action. You must call SetCapture() to get the subsequent mouse message inside your application.

Example code segment:

case WM_NOTIFY:

switch (((LPNMHDR)lParam)->code) {

    case LVN_BEGINDRAG:

    // You can set your customized cursor here

    p.x = 8;
    p.y = 8;

    // Ok, now we create a drag-image for all selected items
    bFirst = TRUE;
    iPos = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
    while (iPos != -1) {
        if (bFirst) {
            // For the first selected item,
            // we simply create a single-line drag image
            hDragImageList = ListView_CreateDragImage(hListView, iPos, &p);
            ImageList_GetImageInfo(hDragImageList, 0, &imf);
            iHeight = imf.rcImage.bottom;
            bFirst = FALSE;
        }else {
            // For the rest selected items,
            // we create a single-line drag image, then
            // append it to the bottom of the complete drag image
            hOneImageList = ListView_CreateDragImage(hListView, iPos, &p);
            hTempImageList = ImageList_Merge(hDragImageList, 
                             0, hOneImageList, 0, 0, iHeight);
            ImageList_Destroy(hDragImageList);
            ImageList_Destroy(hOneImageList);
            hDragImageList = hTempImageList;
            ImageList_GetImageInfo(hDragImageList, 0, &imf);
            iHeight = imf.rcImage.bottom;
        }
        iPos = ListView_GetNextItem(hListView, iPos, LVNI_SELECTED);
    }

    // Now we can initialize then start the drag action
    ImageList_BeginDrag(hDragImageList, 0, 0, 0);

    pt = ((NM_LISTVIEW*) ((LPNMHDR)lParam))->ptAction;
    ClientToScreen(hListView, &pt);

    ImageList_DragEnter(GetDesktopWindow(), pt.x, pt.y);

    bDragging = TRUE;

    // Don't forget to capture the mouse
    SetCapture(hWndMain);

    break;

Then we handle WM_MOUSEMOVE message to move the drag-image along the mouse movement. There is no need to explain the example code segment:

    case WM_MOUSEMOVE:

        if (!bDragging)
            break;

        p.x = LOWORD(lParam);
        p.y = HIWORD(lParam);

        ClientToScreen(hWndMain, &p);
        ImageList_DragMove(p.x, p.y);
        break;

OK, now we reach the final step. When user releases the mouse button, we will end the drag-and-drop action and do the rearrangement work.

  1. Call ImageList_DragLeave and Image_EndDrag to end the drag-and-drop process. Don't forget to release the mouse capture and destroy the drag image to free the memory it uses.
  2. Determine the item onto where the user drops the selected items. If the dropped item is selected, you should terminate.
  3. Move all the selected items to the dropped item's position by copying and deleting.

Here's the example code segment:

    case WM_LBUTTONUP:

        // End the drag-and-drop process
        bDragging = FALSE;
        ImageList_DragLeave(hListView);
        ImageList_EndDrag();
        ImageList_Destroy(hDragImageList);

        ReleaseCapture();

        // Determine the dropped item
        lvhti.pt.x = LOWORD(lParam);
        lvhti.pt.y = HIWORD(lParam);
        ClientToScreen(hWndMain, &lvhti.pt);
        ScreenToClient(hListView, &lvhti.pt);
        ListView_HitTest(hListView, &lvhti);

        // Out of the ListView?
        if (lvhti.iItem == -1)
            break;
        // Not in an item?
        if ((lvhti.flags & LVHT_ONITEMLABEL == 0) && 
                  (lvhti.flags & LVHT_ONITEMSTATEICON == 0))
            break;

        // Dropped item is selected?
        lvi.iItem = lvhti.iItem;
        lvi.iSubItem = 0;
        lvi.mask = LVIF_STATE;
        lvi.stateMask = LVIS_SELECTED;
        ListView_GetItem(hListView, &lvi);

        if (lvi.state & LVIS_SELECTED)
            break;

        // Rearrange the items
        iPos = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
        while (iPos != -1) {
            // First, copy one item
            lvi.iItem = iPos;
            lvi.iSubItem = 0;
            lvi.cchTextMax = MAX_TARGET_LEN;
            lvi.pszText = buf;
            lvi.stateMask = ~LVIS_SELECTED;
            lvi.mask = LVIF_STATE | LVIF_IMAGE 
                        | LVIF_INDENT | LVIF_PARAM | LVIF_TEXT;
            ListView_GetItem(hListView, &lvi);
            lvi.iItem = lvhti.iItem;
            // Insert the main item
            iRet = ListView_InsertItem(hListView, &lvi);
            if (lvi.iItem < iPos)
                lvhti.iItem++;
            if (iRet <= iPos)
                iPos++;
            // Set the subitem text
            for (i = 1; i < JOB_WIN_COLUMN_NUM; i++) {
                ListView_GetItemText(hListView, iPos, 
                               i, buf, MAX_TARGET_LEN);
                ListView_SetItemText(hListView, iRet, i, buf);
            }
            // Delete from original position
            ListView_DeleteItem(hListView, iPos);
            iPos = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
        }

        break;

Here are variable definitions:

int                 iHeight;
static HIMAGELIST   hDragImageList;
HIMAGELIST          hOneImageList, hTempImageList;
LPNMHDR             pnmhdr;
static BOOL         bDragging;
LVHITTESTINFO       lvhti;
BOOL                bFirst;
IMAGEINFO           imf;

And of course, hListView and hMainWnd are HWND variables.

Phew ... Implementing all of these in Win32 API is not that difficult, is it?

History

  • Last Updated: Aug 16, 2001
  • Date Posted: Aug 10, 2001