Click here to Skip to main content
15,887,429 members
Articles / Desktop Programming / MFC
Article

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

Rate me:
Please Sign up or sign in to vote.
4.67/5 (6 votes)
16 Aug 20012 min read 138.2K   32   10
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

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
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAnd of course, hListView and hMainWnd are HWND variables. Pin
carabutnicolae123421-Nov-07 11:06
carabutnicolae123421-Nov-07 11:06 
GeneralSmall detail Pin
sh33pm4n23-Jan-07 4:47
sh33pm4n23-Jan-07 4:47 
GeneralVery helpful code Pin
cvarak11-Sep-06 3:08
cvarak11-Sep-06 3:08 
GeneralGrid Mod Pin
Andy_AJ17-Feb-06 6:50
Andy_AJ17-Feb-06 6:50 
GeneralImageList_Merge requires ImageList to have masks Pin
codemincer3-Nov-05 10:23
codemincer3-Nov-05 10:23 
QuestionOnly the first itemtext was moved? Pin
John Wong8-Sep-02 0:57
John Wong8-Sep-02 0:57 
Generalcrashes when big number of items selected Pin
sebir18-Jul-02 1:50
sebir18-Jul-02 1:50 
Generaldrag & drop between two listview Pin
Magnan Pierre Henri29-Jan-02 4:22
Magnan Pierre Henri29-Jan-02 4:22 
GeneralLittle but critical error in WM_LBUTTONUP Pin
17-Aug-01 1:26
suss17-Aug-01 1:26 
GeneralRe: Little but critical error in WM_LBUTTONUP Pin
17-Aug-01 17:06
suss17-Aug-01 17:06 

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.