I have a ListView (WinForms) in which i want to move items up and down with the click of a button. The items to be moved are the ones who are checked. So if item 2, 6 and 9 are selected, they will become 1, 5 and 8 when I press the button for movement upwards and the items that were on those places are moved down a step.
I feel that I have made this unnecessarily complicated, as you can see below. The second SubItem of each ListViewItem is a number which represents its place in the list (starts on 1).
I blame lack of sleep and coffee for the following code, but if you could figure out a simpler way to complete this task I would be thankful.
private void sourceMoveUpButton_Click(object sender, EventArgs e)
{
List<Int32> affectedNumbers = new List<Int32>();
bool foundNonChecked = false;
List<KeyValuePair<int, ListViewItem>> newList = new List<KeyValuePair<int, ListViewItem>>();
foreach (ListViewItem item in this.sourceListView.CheckedItems)
{
int newNum = int.Parse(item.SubItems[1].Text) - 1;
if (newNum >= 1)
{
foreach (ListViewItem testItem in this.sourceListView.Items)
{
if (int.Parse(testItem.SubItems[1].Text) == newNum && !testItem.Checked)
{
foundNonChecked = true;
}
}
if (foundNonChecked)
{
item.SubItems[1].Text = newNum.ToString();
affectedNumbers.Add(newNum);
}
}
}
foreach (ListViewItem item in this.sourceListView.Items)
{
int num = int.Parse(item.SubItems[1].Text);
if (affectedNumbers.Contains(num) && !item.Checked)
{
item.SubItems[1].Text = (num + affectedNumbers.Count).ToString();
}
newList.Add(new KeyValuePair<int, ListViewItem>(int.Parse(item.SubItems[1].Text), item));
item.Remove();
}
newList.Sort((firstPair, secondPair) =>
{
return firstPair.Key.CompareTo(secondPair.Key);
}
);
foreach (KeyValuePair<int, ListViewItem> pair in newList)
{
this.sourceListView.Items.Add(pair.Value);
}
}
EDIT
I have shorted it down to the following:
foreach (ListViewItem item in this.sourceListView.CheckedItems)
{
if (item.Index > 0)
{
int newIndex = item.Index - 1;
this.sourceListView.Items.RemoveAt(item.Index);
this.sourceListView.Items.Insert(newIndex, item);
}
}
int index = 1;
foreach (ListViewItem item in this.sourceListView.Items)
{
item.SubItems[1].Text = index.ToString();
index++;
}
But now, if I select the two topmost items (or similar) they will switch place when I click the button for upwards movement.
SECOND EDIT
Everything works fine for upwards movement with the following:
if (this.sourceListView.CheckedItems[0].Index != 0)
{
this.sourceListView.BeginUpdate();
foreach (ListViewItem item in this.sourceListView.CheckedItems)
{
if (item.Index > 0)
{
int newIndex = item.Index - 1;
this.sourceListView.Items.RemoveAt(item.Index);
this.sourceListView.Items.Insert(newIndex, item);
}
}
this.updateListIndexText();
this.sourceListView.EndUpdate();
}
But for downward movement I can’t seem to get it right:
if (this.sourceListView.CheckedItems[this.sourceListView.CheckedItems.Count - 1].Index < this.sourceListView.Items.Count - 1)
{
this.sourceListView.BeginUpdate();
foreach (ListViewItem item in this.sourceListView.CheckedItems)
{
if (item.Index < this.sourceListView.Items.Count - 1)
{
int newIndex = item.Index + 1;
this.sourceListView.Items.RemoveAt(item.Index);
this.sourceListView.Items.Insert(newIndex, item);
}
}
this.updateListIndexText();
this.sourceListView.EndUpdate();
}
It works for moving single items down, but when I select more than one, it doesn’t.
Try something like this:
Basically just removes the item then inserts it above of where it used to be. The ListView automatically handles reshuffling the items down the order after an insert so no worries.
Edit:
The reason the two topmost items swap is that the top item will never move (i.e I haven’t implemented a
wrap-aroundmove. The 2nd item, however, is free to move and thus goes to the top of the list.To resolve this, you can do 1 of 2 things:
As for the re-doing of the text, just do it in the original loop.
Implementation with wraparound:
Edit 2:
Static helper implementation, no wrap-around:
Example: