'How to move a ListViewItem part of a Group in place of another ListViewItem using the same ListView?

Below I'm trying to move item4 in place of item5, the action I expected was for item 4 to be on top of item5 and item5 under item4:

ListViewItem Move to Group

Below I'm trying to move item4 in place of item5, the action I expected was for item4 to be on top of item5 and item5 under item4:

ListViewItem Move to wrong position

I also can't move one item in place of another, can anyone help?

I leave the complete code here, including the design:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        private ListView listView;
        private ListViewItem currentVieItem;
        public Form1()
        {
            InitializeComponent();
            
            createListview();
        }

        public void createListview()
        {
            listView = new ListView();
            listView.AllowDrop = true;

            listView.ItemDrag += new ItemDragEventHandler(OnItemDrag);
            listView.DragOver += new DragEventHandler(OnDragOver);
            listView.DragDrop += new DragEventHandler(OnDragDrop);

            // MY CODE HERE
            ColumnHeader columnHeader = new ColumnHeader();
            listView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
            columnHeader});
            listView.HideSelection = false;
            int indexGroup = 1;
            ListViewGroup group = new ListViewGroup();
            for (int i = 0; i < 100; i++)
            {
                if (i % 5 == 0)
                {
                    string nmGroup = $"Group {indexGroup}";
                    group = new ListViewGroup() { Header = nmGroup};
                    listView.Groups.Add(group);
                    indexGroup++;
                }
                listView.Items.Add(new ListViewItem() { Text = $"Item {i}", Group = group });
            }
            listView.Location = new System.Drawing.Point(12, 12);
            listView.Name = "listView1";
            listView.Size = new System.Drawing.Size(436, 494);
            listView.TabIndex = 0;
            listView.UseCompatibleStateImageBehavior = false;
            listView.View = System.Windows.Forms.View.Details;
            // 
            // columnHeader1
            // 
            columnHeader.Text = "Items";
            columnHeader.Width = 382;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(937, 600);
            this.Controls.Add(listView);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }

        public void OnDragOver(object sender, DragEventArgs e)
        {
            var pos = listView.PointToClient(new Point(e.X, e.Y));
            var hit = listView.HitTest(pos);
            this.currentVieItem = hit.Item;
            this.Text = hit.Item?.Index.ToString();

            if (e.Data.GetDataPresent(typeof(ListView.SelectedListViewItemCollection)))
            {
                e.Effect = e.AllowedEffect;
            }
        }

        public void OnDragDrop(object sender, DragEventArgs e)
        {
            if (currentVieItem == null) return;

            int index = currentVieItem.Index;
            this.Text = index.ToString();

            if (e.Data.GetDataPresent(typeof(ListView.SelectedListViewItemCollection)))
            {
                if (e.Effect == DragDropEffects.Move)
                {
                    foreach (ListViewItem current in (ListView.SelectedListViewItemCollection)e.Data.GetData(typeof(ListView.SelectedListViewItemCollection)))
                    {
                        current.Remove();
                        current.Group = currentVieItem.Group;
                        listView.Items.Insert(index, current);
                        index++;
                    }
                }                   
            }
        }

        public void OnItemDrag(object sender, ItemDragEventArgs e)
        {
            listView.DoDragDrop(listView.SelectedItems, DragDropEffects.Move);
        }
    }
}


Solution 1:[1]

While the ListViewItem.Group.Items.Insert() method works as intended (the Item is inserted at the specified position), the Group itself doesn't sort its items based on the Index (the Group Index) assigned to the new Item.
The existing order of Items takes precedence, so the new Item is added last.

You can redefine this behavior if you assign to the ListView a custom ListViewItemSorter, based on your own criteria, when you change the order of the Items.

Note that when you assign an Item to a Group, you don't need to remove the Item from the ListView or the original Group, just insert the Item into a different Group (i.e., [ListViewItem].Remove() is not required).
You can insert the dragged Item(s) to the Group that contains the Item you're dragging over.

You can remove the currentVieItem reference, it's also not necessary. See the code.
The DragOver event handler is not used here (it's not useful).
It could be used to show an InsertionMark.

Keep the initialization of the ListView, changing the names of the handlers:

// [...]
listView.ItemDrag += OnLVItemDrag;
listView.DragEnter += OnLVDragEnter;
listView.DragDrop += OnLVDragDrop;
// [...]

Replace the code in the event handlers:

public void OnLVItemDrag(object sender, ItemDragEventArgs e)
{
    var lv = sender as ListView;
    lv.DoDragDrop(lv.SelectedItems, DragDropEffects.Move);
}

private void OnLVDragEnter(object sender, DragEventArgs e) => e.Effect = e.AllowedEffect;

public void OnLVDragDrop(object sender, DragEventArgs e)
{
    var data = e.Data.GetData(typeof(ListView.SelectedListViewItemCollection)) as ListView.SelectedListViewItemCollection;
    if (data is null) return;

    var lv = sender as ListView;
    var nearestItem = lv.HitTest(lv.PointToClient(new Point(e.X, e.Y))).Item;
    if (nearestItem is null) return;

    var groupIndex = nearestItem.Group.Items.IndexOf(nearestItem);

    if (e.Effect == DragDropEffects.Move) {
        foreach (ListViewItem item in data) {
            nearestItem.Group.Items.Insert(groupIndex, item);
            groupIndex++;
        }
        lv.ListViewItemSorter = new ListViewSorter(sortByIndex: true, useGroupIndex: true);
    }
    // else{} Handle other operations
}

Custom ListViewItemSorter object:

This custom object can sort a ListView based on the SubItem's Text Property (default), or the Items' Index or the Group Index.

using System.Collections;

class ListViewSorter : IComparer {
    int columnIdx = 0;
    bool indexSort = false;
    bool sortGroups = false;

    public ListViewSorter() { }
    public ListViewSorter(int column) => columnIdx = column;

    public ListViewSorter(bool sortByIndex, bool useGroupIndex) { 
        sortGroups = useGroupIndex;
        indexSort = sortByIndex;
    }

    public int Compare(object lvi1, object lvi2)
    {
        var item1 = lvi1 as ListViewItem;
        var item2 = lvi2 as ListViewItem;
        if (indexSort) {
            if (sortGroups && item1.Group != null && item2.Group != null) {
                int idx1 = item1.Group.Items.IndexOf(item1);
                int idx2 = item2.Group.Items.IndexOf(item2);
                return idx1 - idx2;
            }
            else {
                return item1.Index - item2.Index;
            }
        }
        else {
            return string.Compare(
                item1.SubItems[columnIdx].Text, 
                item2.SubItems[columnIdx].Text);
        }
    }
}

This is how it works:

ListViewItem Groups DragDrop

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1