Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move all selected items below specific one with LVM_SORTITEMSEX

INTRODUCTION:

In order to keep this post as brief as possible, let me just say that I need to move all selected items in the listview below certain (unselected) item.

Browsing through listview documentation I came upon LVM_SORTITEMSEX message.

QUESTION:

How to use the above message to achieve my goal.

MY EFFORTS TO SOLVE THIS:

So far, by using this message, I was able to move all selected items to the bottom of the list -> listview is sorted in such a way that unselected items precede selected ones.

I just can not figure out how to implement moving the selected items below certain item.

Below are the images of what I get, and what I want to achieve:

enter image description here

The left image shows what I get when I use the code submitted below, while the right one shows the result I aim for.

Here are the relevant code snippets:

// compare function -> see the documentation
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM hwnd)
{
    LVITEM lvi1 = { 0 }, lvi2 = { 0 };
    // get selected state of the first item
    lvi1.iItem = (int)lParam1;
    lvi1.iSubItem = 0;
    lvi1.mask = LVIF_STATE;
    lvi1.stateMask = LVIS_SELECTED;
    ListView_GetItem((HWND)hwnd, &lvi1);
    // get selected state of the second item
    lvi2.iItem = (int)lParam2;
    lvi2.iSubItem = 0;
    lvi2.mask = LVIF_STATE;
    lvi2.stateMask = LVIS_SELECTED;
    ListView_GetItem((HWND)hwnd, &lvi2);
    // if first is selected and second is not selected, swap them
    if ((lvi1.state & LVIS_SELECTED) && (0 == (lvi2.state & LVIS_SELECTED)))
        return 1;

    return 0;
}

// somewhere in code, on button click for example
ListView_SortItemsEx(hwndListView, CompareFunc, hwndListView);

I have passed listview handle as third parameter to ListView_SortItemsEx so I can use ListView_GetItem in CompareFunc.

like image 731
AlwaysLearningNewStuff Avatar asked Jun 06 '15 02:06

AlwaysLearningNewStuff


2 Answers

If I understand this correctly, you want to rearrange items with drag and drop, and you want the sort function to do it. This could get complicated if it is to be done inside the sort proc. Another solution is to find the arrangement first.

  • Use vector to store items before "redMark"
  • Add selected items which appear after "redMark"
  • Add unselected items which appear after "redMark"
  • Save this order to LVITEM::lParam
  • Call ListView_SortItems (not ListView_SortItemsEx)

The only problem is that maybe lParam was used for other reasons. We have to save lParam, and then restore it after sort is done.

Also it's better if ListView has LVS_SHOWSELALWAYS.

Note, this method moves items before the "redMark". In your example you should set redMark = 3 to move selection before "Item 60"

int CALLBACK CompareFunc(LPARAM lp1, LPARAM lp2, LPARAM)
{
    return lp1 > lp2;
}

void sort()
{
    int redMark = 3;
    int count = ListView_GetItemCount(hwndListView);

    std::vector<int> order;
    std::vector<LPARAM> saveLParam(count);

    //add everything before redMark
    for (int i = 0; i < redMark; i++)
        order.push_back(i);

    //add highlighted items
    for (int i = redMark; i < count; i++)
        if (ListView_GetItemState(hwndListView, i, LVIS_SELECTED))
            order.push_back(i);

    //add the rest
    for (int i = redMark; i < count; i++)
        if (!ListView_GetItemState(hwndListView, i, LVIS_SELECTED))
            order.push_back(i);

    if (order.size() != count)
    {
        assert(0);
        return;
    }

    //set lParam
    for (int i = 0; i < count; i++)
    {
        LVITEM item = { 0 };
        item.iItem = order[i];
        item.mask = LVIF_PARAM;

        //save old LParam value
        ListView_GetItem(hwndListView, &item);
        saveLParam[i] = item.lParam;

        //set new lParam
        item.lParam = i;
        ListView_SetItem(hwndListView, &item);
    }

    ListView_SortItems(hwndListView, CompareFunc, 0);

    //restore old lParam
    for (int i = 0; i < count; i++)
    {
        LVITEM item = { 0 };
        item.iItem = order[i];
        item.mask = LVIF_PARAM;
        item.lParam = saveLParam[order[i]];
        ListView_SetItem(hwndListView, &item);
    }

    ::SetFocus(hwndListView);
}
like image 174
Barmak Shemirani Avatar answered Oct 03 '22 15:10

Barmak Shemirani


Introduction:

  • For LVM_SORTITEMSEX, all items must have unique lParam's
  • To pass multiple parameters to sorting callback, make a struct and pass pointer to it.
  • As soon as you start sorting, the original order of items is lost, and you can no longer refer to it if not saved somewhere.
  • If you can repeat the operation of moving sorted items, then carefully crafted lParam's are also not enough to know the original order of items.
  • You can mess with lParam's instead of preparing desired order of items separately, but this is rather dirty and prone to errors.

Generic solution

  • Ensure all items have unique lParam's
  • Before calling LVM_SORTITEMSEX, prepare a vector of lParam's in desired order:
    1. Enumerate items in list from beginning up to the "red mark" item, add their lParam's into vector.
    2. Enumerate items in list from "red mark" item to the list end, if item is selected, add its lParam into vector.
    3. Enumerate items in list from "red mark" item to the list end, if item is NOT selected, add its lParam into vector.
    4. Now you have the order of lParam's: start of the list, then selected items retaining their original order, then unselected items retaining their original order.
  • Pass that vector to sorting callback
  • In it, lookup positions of two lParam's in vector and make answer based on this positions. For example, if you find that first position is less then second, you return a negative number. The typical approach is return (firstPos - secondPos), that will handle all relative orders of firstPos and secondPos in one line of code.
  • That will have your sorting calllback apply prepared order of items to list.
like image 20
Codeguard Avatar answered Oct 03 '22 15:10

Codeguard