0
votes

Winforms, C#, VS2017

ImageList does not have an Insert method (however ListViewItemCollection does). I have tried a few different ways to insert a new image into the middle of a ListView and it's LargeImageList, but not getting it to work quite properly.

Anyone have any tried and true code that works properly?

This is what I have, but the images don't get synced properly to the items in the list.

    protected void InsertThumbnail(string key, string keySelected)
    {
        var newImageList = new ImageList()
        {
            ImageSize = new Size(thumbWidth, thumbHeight)
        };
        var itemNew = new ListViewItem();
        var foundSelected = false;

        //lvAllPages.BeginUpdate();

        for (int i = 0; i < lvAllPages.Items.Count; i++)
        {
            var item = lvAllPages.Items[i];
            newImageList.Images.Add(item.Tag.ToString(), lvAllPages.LargeImageList.Images[i]);

            if (item.Tag.ToString() == keySelected)
            {
                var image = batch.GetThumbnail(key);
                newImageList.Images.Add(key, image);
                itemNew = new ListViewItem()
                {
                    BackColor = Color.Aquamarine,
                    ImageIndex = i,
                    Tag = key,
                };

                if (isLocal)
                    itemNew.Text = $"{GetFileName(key)} (insert) - {itemNew.ImageIndex}";

                foundSelected = true;
            }

            if (foundSelected)
            {
                item.ImageIndex = item.ImageIndex + 1;
                if (isLocal)
                    item.Text = $"{GetFileName(item.Tag.ToString())} - {item.ImageIndex}";
            }
        }

        lvAllPages.LargeImageList.Dispose();
        lvAllPages.LargeImageList = newImageList;
        lvAllPages.Items.Insert(itemNew.ImageIndex, itemNew);
    }

One more related thing, but not pertinent to the problems I am having. For anyone looking at this question and having similar issues, this helped with the issue of sorting items after inserting a new one. Default behavior when you insert a new ListViewItem at a given index, it will appear at the bottom of the list. I found this handy class to keep items sorted by index, which solved that problem:

class CompareByIndex : IComparer { private readonly ListView _listView;

public CompareByIndex(ListView listView)
{
    this._listView = listView;
}
public int Compare(object x, object y)
{
    int i = this._listView.Items.IndexOf((ListViewItem)x);
    int j = this._listView.Items.IndexOf((ListViewItem)y);
    return i - j;
}

}

And in the form load:

lvAllPages.ListViewItemSorter = new CompareByIndex(lvAllPages);

1
I would ignore the index and just use the string keys. - LarsTech
I think I tried just using the string keys, but I needed index for other methods that either rotate or replace the image... I may revisit that idea, though. - Kershaw
The thing with ListViewItem is that Key and ImageIndex are mutually exclusive, you can only use one or the other. If you set both, one is defaulted back to 0 (ImageIndex), or empty string (Key). - Kershaw
Right. Ignore the index and use the string key. - LarsTech
And because I need both, I store the filepath in ListViewItem.Tag, and also use the ListViewItem.ImageIndex. I have other methods that either rotate or replace the image, and I think I need the index for those... I at least need the index for sorting being that the ListView doesn't sort itself. - Kershaw

1 Answers

0
votes

Obviously, that's a design decision. ImageList.Images is a ImageCollection and as such, it implements the IList interface.

Unfortunately, the Insert() method is allowed to throw a NotSupportedException. And that's what the list will do when used like a IList:

((IList)imageList.Images).Insert(5, new Bitmap(10,10));

System.NotSupportedException: 'Specified method is not supported.'

In order to have the images shown in a specific order, use the Add() method which takes the key:

imageList.Images.Add("1", new Bitmap(100,100));

That should also enable you to replace the image:

imageList.Images.RemoveByKey("1");
imageList.Images.Add("1", new Bitmap(200,200));

For that to work, you need to set the Sorting property:

listView1.Sorting = SortOrder.Ascending;

For storing additional information like path etc. use anotther data structure with the same key.


Here's the code:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    ImageList imageList = new ImageList();
    Dictionary<string, Metadata> metadata = new Dictionary<string, Metadata>();
    private string dir = @"H:\temp";

    private void button1_Click(object sender, EventArgs e)
    {
        // You would set this in the designer, probably
        listView1.Sorting = SortOrder.Ascending;
        listView1.View = View.LargeIcon;
        listView1.LargeImageList = imageList;

        // Make sure we start from the beginning
        listView1.Items.Clear();
        imageList.Images.Clear();
        metadata.Clear();

        // Add items
        for (int i = 0; i < 10; i++)
        {
            var filename = "1 ("+(i+1)+").png"; // Just strange names I have
            var fullFileName = Path.Combine(dir, filename);
            imageList.Images.Add(i.ToString(), Bitmap.FromFile(fullFileName));
            metadata.Add(i.ToString(), new Metadata{Path = fullFileName});

            listView1.Items.Add(i.ToString(), "Image " + i, i.ToString());
        }

        // Update view
        listView1.Refresh();
        listView1.Invalidate();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        for (int i = 3; i < 6; i++)
        {
            var filename = "1 ("+(i+2)+").png";
            var fullFileName = Path.Combine(dir, filename);
            // Change image
            imageList.Images.RemoveByKey(i.ToString());
            imageList.Images.Add(i.ToString(), Bitmap.FromFile(fullFileName));
            // Match metadata and image
            metadata[i.ToString()] = new Metadata{Path = fullFileName};
        }
        listView1.Refresh();
    }

    private void listView1_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (listView1.SelectedItems.Count > 0)
        {
            var key = listView1.SelectedItems[0].ImageKey;

            label1.Text = metadata[key].Path;
        }
        else
        {
            label1.Text = "No image selected";
        }
    }
}

internal class Metadata
{
    internal string Path;
}