Search code examples
c#listviewimagelist

Removing ListViewItem with image from ListView


I'm trying to dynamically change a ListView in my program. Every item has an ImageKey and I use a SmallImageList for them.

The problem is whenever I delete an item, the problem which was mentioned in this question appears:

Before and after deleting an item:

BeforeAfter

Code used:

// Add the images from an array of paths
foreach (string xFile in files)
{
    thumbnails_imageList.Images.Add(xFile, images[xFile]);
    files_lst.Items.Add(xFile, Path.GetFileNameWithoutExtension(xFile), xFile);
}

// Delete the selected key(s)
foreach (ListViewItem xItem in files_lst.SelectedItems)
{
    files_lst.Items.Remove(xItem);
    thumbnails_imageList.Images.RemoveByKey(xItem.Name);
}

The answer in the question (which recommends not to remove images from the ImageList) doesn't meet my requirements because I add items with the same ImageKey after removing, so, more than one Images in SmallImageList.Images get the same ImageKey, therefore images become inconsistent. Also the answer ignores the obvious memory leak.


Solution

  • Unfortunately the removal of an Image from the ImageList indeed causes the indices of the Items to move up. This means that internally the Keys are no longer used but are mapped to the indices when adding or setting and then no longer kept intact.

    So to workaround you can either..:

    • Keep all Images in the ImageList and live with the unnecessary Images. At 256x256pixels x 4 byte an Image can have only 256k, so the memory waste will not be so big. (Note that no GDI+ resources are wasted, since an ImageList doesn't create any handles for its Images.) But given enough adding/removing the growing size of the ImageList may become a problem..

    • Or you can work around by storing and resetting the ImageKeys.

    Here is an example:

    private void DeleteButton_Click(object sender, EventArgs e)
    {
        foreach (ListViewItem xItem in listView1.SelectedItems)
        {
            // maybe insert the loop below here (*)
            listView1.Items.Remove(xItem);
            // only delete when no longer in use:
            string key = xItem.ImageKey;
            if (listView1.Items.Cast<ListViewItem>().Count(x => x.ImageKey == key) == 0)
                imageList1.Images.RemoveByKey(key);
    
        }
        // after deletions, restore the ImageKeys
        // maybe add a check for Tag == null
        foreach (ListViewItem xItem in listView1.Items)
            xItem.ImageKey = xItem.Tag.ToString();
    
    }
    

    For this to work you need to store the correct key strings. I chose to do so in the IListViewItem.Tag property. You can do it when adding the Items or right before the deleting:

    foreach (ListViewItem xItem in listView1.Items)
            xItem.Tag = xItem.ImageKey;               // (*)