2

I'm using an imagelist with about 100 different icons to be used in a listview in a C# project using Winforms

The images are referred to by the listview by their index.

Now there has been a graphical overhaul, and all of the icons need to be replaced. What I did so far was open up the imagelist in the Visual Studio editor, delete image x with index 5, and then add a new version of image x.

enter image description here

The problem with approach is that when deleting image x, all other images with an index higher than 5 are shifted. And after adding the new version of the icon, I have to click the UP arrow about 95 times to get my new version at index 5 again.

Does anyone know a less painful, and less error prone method to achieve the same result?

Editing the .designer.cs file doesn't really help as it only lists the names of the icons and their index

    // 
    // NavigatorImageList
    // 
    this.NavigatorImageList.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("NavigatorImageList.ImageStream")));
    this.NavigatorImageList.TransparentColor = System.Drawing.Color.Transparent;
    this.NavigatorImageList.Images.SetKeyName(0, "dummy.png");
    this.NavigatorImageList.Images.SetKeyName(1, "Attribute.png");
    this.NavigatorImageList.Images.SetKeyName(2, "Operation.png");
    this.NavigatorImageList.Images.SetKeyName(3, "Element.png");
    this.NavigatorImageList.Images.SetKeyName(4, "Diagram.png");
    this.NavigatorImageList.Images.SetKeyName(5, "Package_element.png");
    // ...continues like this for all items ....

The actual images are stored in the .resx file, but they are in a binary format, so also not really an option to edit them there

  <data name="NavigatorImageList.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
    <value>
        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
        LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
        ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAB2
        1gAAAk1TRnQBSQFMAgEBYwEAAYgBAgGIAQIBEAEAARABAAT/AREBAAj/AUIBTQE2BwABNgMAASgDAAFA
        AwABkAEBAgABAQEAARAGAAHIJgABeAEtAfABHAHwARwB8AEcAfABHAHwARwWAAG4ATUBMAElATABJQEw
... and many many more lines like this...

I know referring to the images by their index might have been a mistake, but it's a bit late in the game to change that now

TL;DR

I'm looking for an easy way to replace the images in my imagelist without having to change my existing working code that relies on the image indexes.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Geert Bellekens
  • 12,788
  • 2
  • 23
  • 50
  • 1
    If this is a .NET project where the ImageList is in a `*.resx` file, why not just edit the `*.resx` file by hand? Or if it's a WinForms project, edit the `*designer.cs` file manually. – Dai Nov 18 '19 at 06:29
  • Thanks for the input. I've added some details regarding these to the question. – Geert Bellekens Nov 18 '19 at 08:13
  • If I were in your position, I'd write a Linqpad program using `XmlDocument` to make en-mass changes to the resx file (Linqpad is free, but you need to pay for the useful features). That said, I think you can get a resx file to use linked files rather than storing them directly in the resx file as Base64 in the XML. – Dai Nov 18 '19 at 08:26
  • The Image Collection Editor doesn't really give me any options to use linked files. I can only select an image file and it gets added to the resx file like that. – Geert Bellekens Nov 18 '19 at 08:42
  • 2
    You don't necessarily have to add these images in the designer. Make a `Dictionary`, with pairs of index -> ResourceName, add all the Images as resources to a resource-only Library (a dll that only contains resources and a public method that returns the Bitmaps by name or by index or range of indexes) and fill the ImageList at run-time. If you change something in the resource Library, you probably don't have to change anything in the method that fills the ImageList, if you just use the index to define the Image source of an object. – Jimi Nov 18 '19 at 15:39
  • There is no easy way. I would just create a new ImageList. – Hans Passant Dec 04 '19 at 08:26
  • @GeertBellekens ImageList.Images property has an indexer for setting and getting the image at a given index and a key. check this msdn link: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.imagelist.imagecollection.item?view=netframework-4.8#System_Windows_Forms_ImageList_ImageCollection_Item_System_Int32_ – S2S2 Dec 04 '19 at 08:38
  • If you have a lot f huge image lists, you may want to write a small run-time tool to help you during this process. You have the image list, the tool should export the image list to a folder, then you replace all images in the folder, then the tool should generate the xml and you replace the XML in designer resource file. Another option is designing a new design-time collection editor for ImageList. – Reza Aghaei Dec 04 '19 at 08:42
  • @RezaAghaei thanks for you idea. It's just the one imagelist, and I will probably not need to do this again for quite a few years (until Sparx decides to do a design overhaul again), so it's probably not worth it. – Geert Bellekens Dec 04 '19 at 09:15
  • I think my advice would actually be to go ahead and bite the bullet and replace all the index references with key references first. You should be able to make that process quite a bit less frustrating by using visual studio's find and replace tool to replace code across the entire solution. You'll have to do that 100 times, but you'll feel quite a bit better about your code afterwards and then the changes you need to make will be straightforward. – ashbygeek Dec 04 '19 at 16:29
  • I also believe it's much better idea to use image key instead of image index; however it's hard to apply this change to all the places which use the list view. Anyhow, apart from this general advice, the answer which I provided enables you to replace the image in image list instead of add/remove. – Reza Aghaei Dec 05 '19 at 08:24

1 Answers1

3

The Image List Editor has an Image property as well which is not browsable by default. As an option you can make that property visible and then you can easily replace the image at design time without any problem.

Look at the following picture and see Image property:

enter image description here

Here is My Image Collection Editor. It's basically the code of the original Image Collection editor with a small change, finding the collection editor's PropertyGrid and resetting its BrowsableAttributes property.

public class MyImageListEditor : CollectionEditor
{
    Type ImageListImageType;
    public MyImageListEditor(Type type) : base(type)
    {
        ImageListImageType = typeof(ControlDesigner).Assembly
            .GetType("System.Windows.Forms.Design.ImageListImage");
    }
    protected override string GetDisplayText(object value)
    {
        if (value == null)
            return string.Empty;
        PropertyDescriptor property = TypeDescriptor.GetProperties(value)["Name"];
        if (property != null)
        {
            string str = (string)property.GetValue(value);
            if (str != null && str.Length > 0)
                return str;
        }
        if (value.GetType() == ImageListImageType)
            value = (object)((dynamic)value).Image;
        string name = TypeDescriptor.GetConverter(value).ConvertToString(value);
        if (name == null || name.Length == 0)
            name = value.GetType().Name;
        return name;
    }
    protected override object CreateInstance(Type type)
    {
        return ((UITypeEditor)TypeDescriptor.GetEditor(ImageListImageType,
            typeof(UITypeEditor))).EditValue(Context, null);
    }
    protected override CollectionEditor.CollectionForm CreateCollectionForm()
    {
        CollectionEditor.CollectionForm collectionForm = base.CreateCollectionForm();
        collectionForm.Text = "My Image Collection Editor";
        var overArchingTableLayoutPanel =  
            (TableLayoutPanel)collectionForm.Controls["overArchingTableLayoutPanel"];
        var propertyBrowser =  
            (PropertyGrid)overArchingTableLayoutPanel.Controls["propertyBrowser"];
        propertyBrowser.BrowsableAttributes = new AttributeCollection();

        return collectionForm;
    }
    protected override IList GetObjectsFromInstance(object instance)
    {
        return (IList)(instance as ArrayList) ?? (IList)null;
    }
}

To register this editor for image list, the easiest solution is registering it inside the constructor of the base class:

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

public class MyBaseForm : Form
{
    public MyBaseForm()
    {
        TypeDescriptor.AddAttributes(typeof(ImageList.ImageCollection),
            new Attribute[] { 
                new EditorAttribute(typeof(MyImageListEditor), typeof(UITypeEditor)) });
    }
}

Then just close all designers, rebuild solution, close and reopen VS.

That's all!

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Wow, that looks very promising, cool! I'll try it out and report back... – Geert Bellekens Dec 04 '19 at 10:43
  • No worries. Applying the solution is very easy. Spend enough time to test it; however I've already tested and worked very well for me. If you find any problem, let me know. – Reza Aghaei Dec 04 '19 at 10:45
  • The way that I applied `MyImageListEditor` is not the only way, but is the easiest way. It relies in a fact: Windows Forms Designer runs the code in constructor of the base form of the designing form. That's why I added a dummy base form and just put the registration code there. To learn more about how designer works you can take a look at [this post](https://stackoverflow.com/a/32299687/3110834). – Reza Aghaei Dec 04 '19 at 10:49
  • The other option for registering the editor (which you don't need to use) is doing the registration in a VS package, like [what I did here](https://stackoverflow.com/a/36096989/3110834) to change the `ColorEditor` to show some custom colors. – Reza Aghaei Dec 04 '19 at 10:50
  • Thanks, this works super! **Exactly** what I was looking for! – Geert Bellekens Dec 06 '19 at 08:00