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

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

0.00/5 (No votes)
16 Aug 2001 3  
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;
            // 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

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