tags:

views:

23

answers:

2

Hi all,

I'm building a desktop to-do list app, and in my UI I have a ListView control which lists all the items in each list. Each item/row has a checkbox which updates the status of that item in the database when it is checked or unchecked. So far so good!

What I am attempting to do is re-sort the list whenever a checkbox is clicked, so that the list is always sorted with the unchecked items at the top, and then by ID (which is an int value stored in the Tag property of each ListViewItem when the list is loaded).

I've written a custom comparer implementing IComparer and call Sort() on the ListView in the ItemChecked event handler:

/// <summary>
/// Complete or uncomplete a todo item when it's checked/unchecked
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _taskList_ItemChecked(object sender, ItemCheckedEventArgs e)
{
    var list = sender as ListView;
    var itemId = e.Item.Tag.ToString();

    if(e.Item.Tag != null)
    {
        if(e.Item.Checked)
            // Do some database stuff here to mark as complete
        else
            // Do some database stuff here to mark as not completed
    }

    // Resort the listview
    list.ListViewItemSorter = new TaskListComparer();
    list.Sort();
}

Here is my comparer:

public class TaskListComparer : IComparer
{
    public TaskListComparer()
    {
    }

    public int Compare(object a, object b)
    {
        // Convert the two passed values to ListViewItems
        var item1 = a as ListViewItem;
        var item2 = b as ListViewItem;

        // Get the unique ID's of the list items (stored in the Tag property)
        var item1Id = Convert.ToInt32(item1.Tag);
        var item2Id = Convert.ToInt32(item2.Tag);

        // First sort on the Checked property (unchecked items should be at the top)
        if (item1.Checked && !item2.Checked)
            return 1;
        else if (!item1.Checked && item2.Checked)
            return -1;

        // If both items were checked or both items were unchecked, 
        // sort by the ID (in descending order)
        if (item1Id > item2Id)
            return 1;
        else if (item1Id < item2Id)
            return -1;
        else
            return 0;
    }
}

However, when I check an item, the following exception is thrown when the sort is attempted:

System.ArgumentOutOfRangeException was unhandled
    Message="InvalidArgument=Value of '-1' is not valid for 'index'.\r\nParameter name: index"
    Source="System.Windows.Forms"
    ParamName="index"

And in the debugger, if I inspect the item1.Checked property in the compare routine I see:

'item1.Checked' threw an exception of type 'System.ArgumentOutOfRangeException'

The other item's Checked property shows up fine. What am I doing wrong here?

+2  A: 

It looks like the Index of your ListViewItem is -1 (the Checked property uses Index internally). That's normally the case only before the ListViewItem is added to the ListView.

In your case, the item might have been temporarily removed from the list during sorting. In other words, it does not look safe to access the Checked property from a sort routine.

Frédéric Hamidi
Yes, he should save something like a `Dictionary<ListViewItemId,CheckStatus>` in the `ItemChecked` handler, pass it to the comparer and then use it to get the checked information instead of using the `Checked` property.
digEmAll
Thanks guys, this answer and comment were both helpful.
Mark B
A: 

Try to return 0 instead if -1 because it seems that the value returned by Compare() becomes an index of the item.

abatishchev