Merge pull request #487 from mayerwin/patch-1
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / ImageList.cs
index 97c807c3f8035593f96a417ef623bd5749126230..b0f0c4a0272aadd341da2e62cc2c569b0c8d6606 100644 (file)
 // Handle should be an HIMAGELIST returned by ImageList_Create. This
 // implementation uses (IntPtr)(-1) that is a non-zero but invalid handle.
 //
-// MS.NET creates handle when images are accessed. Add methods are caching the
-// original images without modification. This implementation adds images in
-// Add methods so handle is created in Add methods.
+// MS.NET destroys handles using the garbage collector this implementation
+// does the same with Image objects stored in an ArrayList.
 //
 // MS.NET 1.x shares the same HIMAGELIST between ImageLists that were
 // initialized from the same ImageListStreamer and doesn't update ImageSize
-// and ColorDepth that are treated as bugs.
+// and ColorDepth that are treated as bugs and MS.NET 2.0 behavior is
+// implemented.
+//
+// MS.NET 2.0 does not clear keys when handle is destroyed that is treated as
+// a bug.
 //
 
 using System.Collections;
+using System.Collections.Specialized;
 using System.ComponentModel;
+using System.ComponentModel.Design.Serialization;
 using System.Drawing;
+using System.Drawing.Design;
 using System.Drawing.Imaging;
+using System.Globalization;
 using System.Runtime.InteropServices;
 
 namespace System.Windows.Forms
 {
        [DefaultProperty("Images")]
-       [Designer("System.Windows.Forms.Design.ImageListDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
-       [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
-       [TypeConverter("System.Windows.Forms.ImageListConverter, " + Consts.AssemblySystem_Windows_Forms)]
+       [Designer("System.Windows.Forms.Design.ImageListDesigner, " + Consts.AssemblySystem_Design)]
+       [DesignerSerializer("System.Windows.Forms.Design.ImageListCodeDomSerializer, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.Serialization.CodeDomSerializer, " + Consts.AssemblySystem_Design)]
+       [ToolboxItemFilter("System.Windows.Forms")]
+       [TypeConverter(typeof(ImageListConverter))]
        public sealed class ImageList : System.ComponentModel.Component
        {
                #region Private Fields
-               private EventHandler recreateHandle;
+               private const ColorDepth DefaultColorDepth = ColorDepth.Depth8Bit;
+               private static readonly Size DefaultImageSize = new Size(16, 16);
+               private static readonly Color DefaultTransparentColor = Color.Transparent;
+               private object tag;
                private readonly ImageCollection images;
                #endregion // Private Fields
 
                #region Sub-classes
-               [Editor("System.Windows.Forms.Design.ImageCollectionEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))]
+               [Editor("System.Windows.Forms.Design.ImageCollectionEditor, " + Consts.AssemblySystem_Design, typeof(UITypeEditor))]
                public sealed class ImageCollection : IList, ICollection, IEnumerable
                {
                        private const int AlphaMask = unchecked((int)0xFF000000);
 
-                       [StructLayout(LayoutKind.Explicit)]
-                       private struct ArgbColor
-                       {
-                               [FieldOffset(0)]
-                               internal int Argb;
-                               [FieldOffset(0)]
-                               internal byte Blue;
-                               [FieldOffset(1)]
-                               internal byte Green;
-                               [FieldOffset(2)]
-                               internal byte Red;
-                               [FieldOffset(3)]
-                               internal byte Alpha;
-                       }
-
-                       private
-#if NET_2_0
-                       static
-#else
-                       sealed
-#endif
-                       class IndexedColorDepths
-                       {
-#if !NET_2_0
-                               private IndexedColorDepths()
-                               {
-                               }
-#endif
-                               internal static readonly ArgbColor[] Palette4Bit;
-                               internal static readonly ArgbColor[] Palette8Bit;
+                       private static class IndexedColorDepths
+                       {
+                               internal static readonly ColorPalette Palette4Bit;
+                               internal static readonly ColorPalette Palette8Bit;
                                private static readonly int[] squares;
 
                                static IndexedColorDepths()
                                {
-                                       Color[] palette;
                                        Bitmap bitmap;
                                        int index;
-                                       int count;
 
                                        bitmap = new Bitmap(1, 1, PixelFormat.Format4bppIndexed);
-                                       palette = bitmap.Palette.Entries;
+                                       Palette4Bit = bitmap.Palette;
                                        bitmap.Dispose();
 
-                                       Palette4Bit = new ArgbColor[count = palette.Length];
-                                       for (index = 0; index < count; index++)
-                                               Palette4Bit[index].Argb = palette[index].ToArgb();
-
                                        bitmap = new Bitmap(1, 1, PixelFormat.Format8bppIndexed);
-                                       palette = bitmap.Palette.Entries;
+                                       Palette8Bit = bitmap.Palette;
                                        bitmap.Dispose();
 
-                                       Palette8Bit = new ArgbColor[count = palette.Length];
-                                       for (index = 0; index < count; index++)
-                                               Palette8Bit[index].Argb = palette[index].ToArgb();
-
                                        squares = new int[511];
                                        for (index = 0; index < 256; index++)
                                                squares[255 + index] = squares[255 - index] = index * index;
                                }
 
-                               internal static int GetNearestColor(ArgbColor[] palette, int color)
+                               internal static int GetNearestColor(Color[] palette, int color)
                                {
                                        int index;
                                        int count;
@@ -155,7 +130,7 @@ namespace System.Windows.Forms
 
                                        count = palette.Length;
                                        for (index = 0; index < count; index++)
-                                               if (palette[index].Argb == color)
+                                               if (palette[index].ToArgb() == color)
                                                        return color;
 
                                        red = unchecked((int)(unchecked((uint)color) >> 16) & 0xFF);
@@ -165,22 +140,73 @@ namespace System.Windows.Forms
                                        minDistance = int.MaxValue;
 
                                        for (index = 0; index < count; index++)
-                                               if ((distance = squares[255 + palette[index].Red - red] + squares[255 + palette[index].Green - green] + squares[255 + palette[index].Blue - blue]) < minDistance) {
-                                                       nearestColor = palette[index].Argb;
+                                               if ((distance = squares[255 + palette[index].R - red] + squares[255 + palette[index].G - green] + squares[255 + palette[index].B - blue]) < minDistance) {
+                                                       nearestColor = palette[index].ToArgb();
                                                        minDistance = distance;
                                                }
 
                                        return nearestColor;
                                }
+                       }
 
+                       [Flags()]
+                       private enum ItemFlags
+                       {
+                               None = 0,
+                               UseTransparentColor = 1,
+                               ImageStrip = 2
+                       }
+
+                       private sealed class ImageListItem
+                       {
+                               internal readonly object Image;
+                               internal readonly ItemFlags Flags;
+                               internal readonly Color TransparentColor;
+                               internal readonly int ImageCount = 1;
+
+                               internal ImageListItem(Icon value)
+                               {
+                                       if (value == null)
+                                               throw new ArgumentNullException("value");
+
+                                       // Icons are cloned.
+                                       this.Image = (Icon)value.Clone();
+                               }
+
+                               internal ImageListItem(Image value)
+                               {
+                                       if (value == null)
+                                               throw new ArgumentNullException("value");
+
+                                       if (!(value is Bitmap))
+                                               throw new ArgumentException("Image must be a Bitmap.");
+
+                                       // Images are not cloned.
+                                       this.Image = value;
+                               }
+
+                               internal ImageListItem(Image value, Color transparentColor) : this(value)
+                               {
+                                       this.Flags = ItemFlags.UseTransparentColor;
+                                       this.TransparentColor = transparentColor;
+                               }
+
+                               internal ImageListItem(Image value, int imageCount) : this(value)
+                               {
+                                       this.Flags = ItemFlags.ImageStrip;
+                                       this.ImageCount = imageCount;
+                               }
                        }
 
                        #region ImageCollection Private Fields
-                       private ColorDepth colorDepth = ColorDepth.Depth8Bit;
-                       private Color transparentColor = Color.Transparent;
-                       private Size imageSize = new Size(16, 16);
+                       private ColorDepth colorDepth = DefaultColorDepth;
+                       private Size imageSize = DefaultImageSize;
+                       private Color transparentColor = DefaultTransparentColor;
+                       private ArrayList list = new ArrayList();
+                       private ArrayList keys = new ArrayList();
+                       private int count;
                        private bool handleCreated;
-                       private readonly ArrayList list = new ArrayList();
+                       private int lastKeyIndex = -1;
                        private readonly ImageList owner;
                        #endregion // ImageCollection Private Fields
 
@@ -205,12 +231,7 @@ namespace System.Windows.Forms
 
                                        if (this.colorDepth != value) {
                                                this.colorDepth = value;
-                                               if (handleCreated) {
-                                                       for (int i = 0; i < list.Count; i++) {
-                                                               list [i] = ReduceColorDepth ((Bitmap) list [i]);
-                                                       }
-                                                       owner.OnRecreateHandle();
-                                               }
+                                               RecreateHandle();
                                        }
                                }
                        }
@@ -218,7 +239,7 @@ namespace System.Windows.Forms
                        // For use in ImageList
                        internal IntPtr Handle {
                                get {
-                                       this.handleCreated = true;
+                                       CreateHandle();
                                        return (IntPtr)(-1);
                                }
                        }
@@ -242,10 +263,7 @@ namespace System.Windows.Forms
 
                                        if (this.imageSize != value) {
                                                this.imageSize = value;
-                                               if (handleCreated) {
-                                                       list.Clear();
-                                                       owner.OnRecreateHandle();
-                                               }
+                                               RecreateHandle();
                                        }
                                }
                        }
@@ -253,7 +271,7 @@ namespace System.Windows.Forms
                        // For use in ImageList
                        internal ImageListStreamer ImageStream {
                                get {
-                                       return list.Count == 0 ? null : new ImageListStreamer(this);
+                                       return this.Empty ? null : new ImageListStreamer(this);
                                }
 
                                set {
@@ -261,24 +279,30 @@ namespace System.Windows.Forms
                                        Image[] streamImages;
 
                                        if (value == null) {
-#if NET_2_0
-                                               this.handleCreated = false;
-                                               list.Clear();
-#endif
+                                               if (this.handleCreated)
+                                                       DestroyHandle();
+                                               else
+                                                       this.Clear();
                                        }
+                                       // Only deserialized ImageListStreamers are used.
                                        else if ((streamImages = value.Images) != null) {
+                                               this.list = new ArrayList(streamImages.Length);
+                                               this.count = 0;
                                                this.handleCreated = true;
-                                               list.Clear();
+                                               this.keys = new ArrayList(streamImages.Length);
 
-                                               for (index = 0; index < streamImages.Length; index++)
+                                               for (index = 0; index < streamImages.Length; index++) {
                                                        list.Add((Image)streamImages[index].Clone());
+                                                       keys.Add(null);
+                                               }
 
+                                               // Invalid ColorDepth values are ignored.
+                                               if (Enum.IsDefined(typeof(ColorDepth), value.ColorDepth))
+                                                       this.colorDepth = (ColorDepth)value.ColorDepth;
                                                this.imageSize = value.ImageSize;
-                                               this.colorDepth = value.ColorDepth;
-#if NET_2_0
+
                                                // Event is raised even when handle was not created yet.
                                                owner.OnRecreateHandle();
-#endif
                                        }
                                }
                        }
@@ -299,13 +323,13 @@ namespace System.Windows.Forms
                        [Browsable(false)]
                        public int Count {
                                get {
-                                       return list.Count;
+                                       return this.handleCreated ? list.Count : this.count;
                                }
                        }
 
                        public bool Empty {
                                get {
-                                       return list.Count == 0;
+                                       return this.Count == 0;
                                }
                        }
 
@@ -323,45 +347,216 @@ namespace System.Windows.Forms
                                }
 
                                set {
-                                       if (index < 0 || index >= list.Count)
+                                       Image image;
+
+                                       if (index < 0 || index >= this.Count)
                                                throw new ArgumentOutOfRangeException("index");
 
-                                       list[index] = CreateImage(value, this.transparentColor);
+                                       if (value == null)
+                                               throw new ArgumentNullException("value");
+
+                                       if (!(value is Bitmap))
+                                               throw new ArgumentException("Image must be a Bitmap.");
+
+                                       image = CreateImage(value, this.transparentColor);
+                                       CreateHandle();
+                                       list[index] = image;
+                               }
+                       }
+
+                       public Image this[string key] {
+                               get {
+                                       int index;
+
+                                       return (index = IndexOfKey(key)) == -1 ? null : this[index];
+                               }
+                       }
+
+                       public StringCollection Keys {
+                               get {
+                                       int index;
+                                       string key;
+                                       StringCollection keyCollection;
+
+                                       // Returns all keys even when there are more keys than
+                                       // images. Null keys are returned as empty strings.
+
+                                       keyCollection = new StringCollection();
+                                       for (index = 0; index < keys.Count; index++)
+                                               keyCollection.Add(((key = (string)keys[index]) == null || key.Length == 0) ? string.Empty : key);
+
+                                       return keyCollection;
                                }
                        }
                        #endregion // ImageCollection Public Instance Properties
 
+                       #region ImageCollection Private Static Methods
+                       private static bool CompareKeys(string key1, string key2)
+                       {
+                               // Keys are case-insensitive and keys with different length
+                               // are not equal even when string.Compare treats them equal.
+
+                               if (key1 == null || key2 == null || key1.Length != key2.Length)
+                                       return false;
+
+                               return string.Compare(key1, key2, true, CultureInfo.InvariantCulture) == 0;
+                       }
+                       #endregion // ImageCollection Private Static Methods
+
                        #region ImageCollection Private Instance Methods
-                       private Image CreateImage(Image value, Color transparentColor)
+                       private int AddItem(string key, ImageListItem item)
                        {
-                               int width;
-                               int height;
-                               Bitmap bitmap;
-                               Graphics graphics;
-                               ImageAttributes imageAttributes;
+                               int itemIndex;
+                               int index;
 
-                               if (value == null)
-                                       throw new ArgumentNullException("value");
+                               if (this.handleCreated)
+                                       itemIndex = AddItemInternal(item);
+                               else {
+                                       // Image strips are counted as a single item in the return
+                                       // value of Add and AddStrip until handle is created.
+
+                                       itemIndex = list.Add(item);
+                                       this.count += item.ImageCount;
+                               }
+
+                               if ((item.Flags & ItemFlags.ImageStrip) == 0)
+                                       keys.Add(key);
+                               else
+                                       for (index = 0; index < item.ImageCount; index++)
+                                               keys.Add(null);
+
+                               return itemIndex;
+                       }
+
+                       internal event EventHandler Changed;
+
+                       private int AddItemInternal(ImageListItem item)
+                       {
+                               if (Changed != null)
+                                       Changed (this, EventArgs.Empty);
+
+                               if (item.Image is Icon) {
+                                       int imageWidth;
+                                       int imageHeight;
+                                       Bitmap bitmap;
+                                       Graphics graphics;
+
+                                       bitmap = new Bitmap(imageWidth = this.imageSize.Width, imageHeight = this.imageSize.Height, PixelFormat.Format32bppArgb);
+                                       graphics = Graphics.FromImage(bitmap);
+                                       graphics.DrawIcon((Icon)item.Image, new Rectangle(0, 0, imageWidth, imageHeight));
+                                       graphics.Dispose();
 
-                               if (!(value is Bitmap))
-                                       throw new ArgumentException("Image must be a Bitmap.");
+                                       ReduceColorDepth(bitmap);
+                                       return list.Add(bitmap);
+                               }
+                               else if ((item.Flags & ItemFlags.ImageStrip) == 0)
+                                       return list.Add(CreateImage((Image)item.Image, (item.Flags & ItemFlags.UseTransparentColor) == 0 ? this.transparentColor : item.TransparentColor));
+                               else {
+                                       int imageX;
+                                       int width;
+                                       int imageWidth;
+                                       int imageHeight;
+                                       int index;
+                                       Image image;
+                                       Bitmap bitmap;
+                                       Graphics graphics;
+                                       Rectangle imageRect;
+                                       ImageAttributes imageAttributes;
+
+                                       // When ImageSize was changed after adding image strips
+                                       // Count will return invalid values based on old ImageSize
+                                       // but when creating handle either ArgumentException will
+                                       // be thrown or image strip will be added according to the
+                                       // new ImageSize. This can result in image count
+                                       // difference that can result in exceptions in methods
+                                       // that use Count before creating handle. In addition this
+                                       // can result in the loss of sync with keys. When doing
+                                       // the same after handle was created there are no problems
+                                       // as handle will be recreated after changing ImageSize
+                                       // that results in the loss of images added previously.
+
+                                       if ((width = (image = (Image)item.Image).Width) == 0 || (width % (imageWidth = this.imageSize.Width)) != 0)
+                                               throw new ArgumentException("Width of image strip must be a positive multiple of ImageSize.Width.", "value");
+
+                                       if (image.Height != (imageHeight = this.imageSize.Height))
+                                               throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
+
+                                       imageRect = new Rectangle(0, 0, imageWidth, imageHeight);
+                                       if (this.transparentColor.A == 0)
+                                               imageAttributes = null;
+                                       else {
+                                               imageAttributes = new ImageAttributes();
+                                               imageAttributes.SetColorKey(this.transparentColor, this.transparentColor);
+                                       }
+
+                                       index = list.Count;
+                                       for (imageX = 0; imageX < width; imageX += imageWidth) {
+                                               bitmap = new Bitmap(imageWidth, imageHeight, PixelFormat.Format32bppArgb);
+                                               graphics = Graphics.FromImage(bitmap);
+                                               graphics.DrawImage(image, imageRect, imageX, 0, imageWidth, imageHeight, GraphicsUnit.Pixel, imageAttributes);
+                                               graphics.Dispose();
+
+                                               ReduceColorDepth(bitmap);
+                                               list.Add(bitmap);
+                                       }
+
+                                       if (imageAttributes != null)
+                                               imageAttributes.Dispose();
+
+                                       return index;
+                               }
+                       }
+
+                       private void CreateHandle()
+                       {
+                               int index;
+                               ArrayList items;
+
+                               if (!this.handleCreated) {
+                                       items = this.list;
+                                       this.list = new ArrayList(this.count);
+                                       this.count = 0;
+                                       this.handleCreated = true;
+
+                                       for (index = 0; index < items.Count; index++)
+                                               AddItemInternal((ImageListItem)items[index]);
+                               }
+                       }
+
+                       private Image CreateImage(Image value, Color transparentColor)
+                       {
+                               int imageWidth;
+                               int imageHeight;
+                               ImageAttributes imageAttributes;
 
                                if (transparentColor.A == 0)
                                        imageAttributes = null;
                                else {
                                        imageAttributes = new ImageAttributes();
-                                       imageAttributes.SetColorKey(transparentColor, transparentColor);
+                                       imageAttributes.SetColorKey (transparentColor, transparentColor);
                                }
 
-                               bitmap = new Bitmap(width = this.imageSize.Width, height = this.imageSize.Height, PixelFormat.Format32bppArgb);
-                               graphics = Graphics.FromImage(bitmap);
-                               graphics.DrawImage(value, new Rectangle(0, 0, width, height), 0, 0, value.Width, value.Height, GraphicsUnit.Pixel, imageAttributes);
-                               graphics.Dispose();
+                               var bitmap = new Bitmap (imageWidth = this.imageSize.Width, imageHeight = this.imageSize.Height, PixelFormat.Format32bppArgb);
+                               using (var graphics = Graphics.FromImage (bitmap))
+                                       graphics.DrawImage (value, new Rectangle(0, 0, imageWidth, imageHeight), 0, 0, value.Width, value.Height, GraphicsUnit.Pixel, imageAttributes);
+
+                               if (imageAttributes != null)
+                                       imageAttributes.Dispose ();
 
-                               return ReduceColorDepth(bitmap);
+                               ReduceColorDepth (bitmap);
+                               return bitmap;
                        }
 
-                       private unsafe Image ReduceColorDepth(Bitmap bitmap)
+                       private void RecreateHandle()
+                       {
+                               if (this.handleCreated) {
+                                       DestroyHandle();
+                                       this.handleCreated = true;
+                                       owner.OnRecreateHandle();
+                               }
+                       }
+
+                       private unsafe void ReduceColorDepth(Bitmap bitmap)
                        {
                                byte* pixelPtr;
                                byte* lineEndPtr;
@@ -372,7 +567,7 @@ namespace System.Windows.Forms
                                int widthBytes;
                                int stride;
                                BitmapData bitmapData;
-                               ArgbColor[] palette;
+                               Color[] palette;
 
                                if (this.colorDepth < ColorDepth.Depth32Bit) {
                                        bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
@@ -383,7 +578,7 @@ namespace System.Windows.Forms
                                                stride = bitmapData.Stride;
 
                                                if (this.colorDepth < ColorDepth.Depth16Bit) {
-                                                       palette = this.colorDepth < ColorDepth.Depth8Bit ? IndexedColorDepths.Palette4Bit : IndexedColorDepths.Palette8Bit;
+                                                       palette = (this.colorDepth < ColorDepth.Depth8Bit ? IndexedColorDepths.Palette4Bit : IndexedColorDepths.Palette8Bit).Entries;
 
                                                        for (line = 0; line < height; line++) {
                                                                lineEndPtr = linePtr + widthBytes;
@@ -413,26 +608,39 @@ namespace System.Windows.Forms
                                                bitmap.UnlockBits(bitmapData);
                                        }
                                }
-
-                               return bitmap;
                        }
                        #endregion // ImageCollection Private Instance Methods
 
                        #region ImageCollection Internal Instance Methods
+                       // For use in ImageList
+                       internal void DestroyHandle()
+                       {
+                               if (this.handleCreated) {
+                                       this.list = new ArrayList();
+                                       this.count = 0;
+                                       this.handleCreated = false;
+                                       keys = new ArrayList();
+                               }
+                       }
+
                        // For use in ImageList
                        internal Image GetImage(int index)
                        {
-                               if (index < 0 || index >= list.Count)
+                               if (index < 0 || index >= this.Count)
                                        throw new ArgumentOutOfRangeException("index");
 
+                               CreateHandle();
                                return (Image)list[index];
                        }
 
                        // For use in ImageListStreamer
                        internal Image[] ToArray()
                        {
-                               Image[] images = new Image[list.Count];
+                               Image[] images;
 
+                               // Handle is created even when the list is empty.
+                               CreateHandle();
+                               images = new Image[list.Count];
                                list.CopyTo(images);
                                return images;
                        }
@@ -441,109 +649,123 @@ namespace System.Windows.Forms
                        #region ImageCollection Public Instance Methods
                        public void Add(Icon value)
                        {
-                               int width;
-                               int height;
-                               Bitmap bitmap;
-                               Graphics graphics;
-
-                               if (value == null)
-                                       throw new ArgumentNullException("value");
+                               Add(null, value);
+                       }
 
-                               this.handleCreated = true;
+                       public void Add(Image value)
+                       {
+                               Add(null, value);
+                       }
 
-                               bitmap = new Bitmap(width = this.imageSize.Width, height = this.imageSize.Height, PixelFormat.Format32bppArgb);
-                               graphics = Graphics.FromImage(bitmap);
-                               graphics.DrawIcon(value, new Rectangle(0, 0, width, height));
-                               graphics.Dispose();
+                       public int Add(Image value, Color transparentColor)
+                       {
+                               return AddItem(null, new ImageListItem(value, transparentColor));
+                       }
 
-                               list.Add(ReduceColorDepth(bitmap));
+                       public void Add(string key, Icon icon)
+                       {
+                               // Argument has name icon but exceptions use name value.
+                               AddItem(key, new ImageListItem(icon));
                        }
 
-                       public void Add(Image value)
+                       public void Add(string key, Image image)
                        {
-                               Add(value, this.transparentColor);
+                               // Argument has name image but exceptions use name value.
+                               AddItem(key, new ImageListItem(image));
                        }
 
-                       public int Add(Image value, Color transparentColor)
+                       public void AddRange(Image[] images)
                        {
-                               this.handleCreated = true;
-                               return list.Add(CreateImage(value, transparentColor));
+                               int index;
+
+                               if (images == null)
+                                       throw new ArgumentNullException("images");
+
+                               for (index = 0; index < images.Length; index++)
+                                       Add(images[index]);
                        }
 
                        public int AddStrip(Image value)
                        {
-                               int imageX;
-                               int imageWidth;
                                int width;
-                               int height;
-                               int index;
-                               Bitmap bitmap;
-                               Graphics graphics;
-                               Rectangle imageRect;
-                               ImageAttributes imageAttributes;
+                               int imageWidth;
 
                                if (value == null)
                                        throw new ArgumentNullException("value");
 
-                               if ((imageWidth = value.Width) == 0 || (imageWidth % (width = this.imageSize.Width)) != 0)
+                               if ((width = value.Width) == 0 || (width % (imageWidth = this.imageSize.Width)) != 0)
                                        throw new ArgumentException("Width of image strip must be a positive multiple of ImageSize.Width.", "value");
 
-                               if (value.Height != (height = this.imageSize.Height))
+                               if (value.Height != this.imageSize.Height)
                                        throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
 
-                               if (!(value is Bitmap))
-                                       throw new ArgumentException("Image must be a Bitmap.");
-
-                               this.handleCreated = true;
-
-                               imageRect = new Rectangle(0, 0, width, height);
-                               if (this.transparentColor.A == 0)
-                                       imageAttributes = null;
-                               else {
-                                       imageAttributes = new ImageAttributes();
-                                       imageAttributes.SetColorKey(this.transparentColor, this.transparentColor);
-                               }
-
-                               index = list.Count;
-                               for (imageX = 0; imageX < imageWidth; imageX += width) {
-                                       bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
-                                       graphics = Graphics.FromImage(bitmap);
-                                       graphics.DrawImage(value, imageRect, imageX, 0, width, height, GraphicsUnit.Pixel, imageAttributes);
-                                       graphics.Dispose();
-
-                                       list.Add(ReduceColorDepth(bitmap));
-                               }
-
-                               return index;
+                               return AddItem(null, new ImageListItem(value, width / imageWidth));
                        }
 
                        public void Clear()
                        {
                                list.Clear();
+                               if (!this.handleCreated)
+                                       this.count = 0;
+                               keys.Clear();
                        }
 
+                       [EditorBrowsable(EditorBrowsableState.Never)]
                        public bool Contains(Image image)
                        {
                                throw new NotSupportedException();
                        }
 
+                       public bool ContainsKey(string key)
+                       {
+                               return IndexOfKey(key) != -1;
+                       }
 
                        public IEnumerator GetEnumerator()
                        {
-                               Image[] images = new Image[list.Count];
+                               Image[] images = new Image[this.Count];
                                int index;
 
-                               for (index = 0; index < images.Length; index++)
-                                       images[index] = (Image)((Image)list[index]).Clone();
+                               if (images.Length != 0) {
+                                       // Handle is created only when there are images.
+                                       CreateHandle();
+
+                                       for (index = 0; index < images.Length; index++)
+                                               images[index] = (Image)((Image)list[index]).Clone();
+                               }
 
                                return images.GetEnumerator();
                        }
 
+                       [EditorBrowsable(EditorBrowsableState.Never)]
                        public int IndexOf(Image image)
                        {
                                throw new NotSupportedException();
                        }
 
+                       public int IndexOfKey(string key)
+                       {
+                               int index;
+
+                               if (key != null && key.Length != 0) {
+                                       // When last IndexOfKey was successful and the same key was
+                                       // assigned to an image with a lower index than the last
+                                       // result and the key of the last result equals to key
+                                       // argument the last result is returned.
+
+                                       if (this.lastKeyIndex >= 0 && this.lastKeyIndex < this.Count && CompareKeys((string)keys[this.lastKeyIndex], key))
+                                               return this.lastKeyIndex;
+
+                                       // Duplicate keys are allowed and first match is returned.
+                                       for (index = 0; index < this.Count; index++)
+                                               if (CompareKeys((string)keys[index], key))
+                                                       return this.lastKeyIndex = index;
+                               }
+
+                               return this.lastKeyIndex = -1;
+                       }
+
+                       [EditorBrowsable(EditorBrowsableState.Never)]
                        public void Remove(Image image)
                        {
                                throw new NotSupportedException();
@@ -551,10 +773,31 @@ namespace System.Windows.Forms
 
                        public void RemoveAt(int index)
                        {
-                               if (index < 0 || index >= list.Count)
+                               if (index < 0 || index >= this.Count)
                                        throw new ArgumentOutOfRangeException("index");
 
+                               CreateHandle();
                                list.RemoveAt(index);
+                               keys.RemoveAt(index);
+                               if (Changed != null)
+                                       Changed (this, EventArgs.Empty);
+                       }
+
+                       public void RemoveByKey(string key)
+                       {
+                               int index;
+
+                               if ((index = IndexOfKey(key)) != -1)
+                                       RemoveAt(index);
+                       }
+
+                       public void SetKeyName(int index, string name)
+                       {
+                               // Only SetKeyName throws IndexOutOfRangeException.
+                               if (index < 0 || index >= this.Count)
+                                       throw new IndexOutOfRangeException();
+
+                               keys[index] = name;
                        }
                        #endregion // ImageCollection Public Instance Methods
 
@@ -604,14 +847,14 @@ namespace System.Windows.Forms
                                return index;
                        }
 
-                       bool IList.Contains(object value)
+                       bool IList.Contains(object image)
                        {
-                               return value is Image ? this.Contains((Image)value) : false;
+                               return image is Image ? this.Contains ((Image) image) : false;
                        }
 
-                       int IList.IndexOf(object value)
+                       int IList.IndexOf (object image)
                        {
-                               return value is Image ? this.IndexOf((Image)value) : -1;
+                               return image is Image ? this.IndexOf ((Image) image) : -1;
                        }
 
                        void IList.Insert(int index, object value)
@@ -619,18 +862,16 @@ namespace System.Windows.Forms
                                throw new NotSupportedException();
                        }
 
-                       void IList.Remove(object value)
+                       void IList.Remove (object image)
                        {
-                               if (value is Image)
-                                       this.Remove((Image)value);
+                               if (image is Image)
+                                       this.Remove ((Image) image);
                        }
 
-                       void ICollection.CopyTo(Array array, int index)
+                       void ICollection.CopyTo(Array dest, int index)
                        {
-                               int imageIndex;
-
-                               for (imageIndex = 0; imageIndex < this.Count; imageIndex++)
-                                       array.SetValue(this[index], index++);
+                               for (int imageIndex = 0; imageIndex < this.Count; imageIndex++)
+                                       dest.SetValue (this[imageIndex], index++);
                        }
                        #endregion // ImageCollection Interface Methods
                }
@@ -648,14 +889,57 @@ namespace System.Windows.Forms
                }
                #endregion // Public Constructors
 
+               #region Private Instance Methods
                private void OnRecreateHandle()
                {
-                       if (recreateHandle != null)
-                               recreateHandle(this, EventArgs.Empty);
+                       EventHandler eh = (EventHandler)(Events [RecreateHandleEvent]);
+                       if (eh != null)
+                               eh (this, EventArgs.Empty);
+               }
+
+               // MS's TypeDescriptor stuff apparently uses
+               // non-public ShouldSerialize* methods, because it
+               // picks up this behavior even though the methods
+               // aren't public.  we can't make them private, though,
+               // without adding compiler warnings.  so, make then
+               // internal instead.
+
+               internal bool ShouldSerializeTransparentColor ()
+               {
+                       return this.TransparentColor != Color.LightGray;
+               }
+
+               internal bool ShouldSerializeColorDepth()
+               {
+                       // ColorDepth is serialized in ImageStream when non-empty.
+                       // It is serialized even if it has its default value when empty.
+                       return images.Empty;
+               }               
+
+               internal bool ShouldSerializeImageSize()
+               {
+                       // ImageSize is serialized in ImageStream when non-empty.
+                       // It is serialized even if it has its default value when empty.
+                       return images.Empty;
+               }
+
+               internal void ResetColorDepth ()
+               {
+                       this.ColorDepth = DefaultColorDepth;
+               }
+
+               internal void ResetImageSize ()
+               {
+                       this.ImageSize = DefaultImageSize;
+               }
+
+               internal void ResetTransparentColor ()
+               {
+                       this.TransparentColor = Color.LightGray;
                }
+               #endregion // Private Instance Methods
 
                #region Public Instance Properties
-               [DefaultValue(ColorDepth.Depth8Bit)]
                public ColorDepth ColorDepth {
                        get {
                                return images.ColorDepth;
@@ -717,6 +1001,20 @@ namespace System.Windows.Forms
                        }
                }
 
+               [Bindable(true)]
+               [DefaultValue(null)]
+               [Localizable(false)]
+               [TypeConverter(typeof(StringConverter))]
+               public object Tag {
+                       get {
+                               return this.tag;
+                       }
+
+                       set {
+                               this.tag = value;
+                       }
+               }
+
                public Color TransparentColor {
                        get {
                                return images.TransparentColor;
@@ -746,28 +1044,28 @@ namespace System.Windows.Forms
 
                public override string ToString()
                {
-                       return base.ToString() + " Images.Count: " + images.Count.ToString() + ", ImageSize: " + images.ImageSize.ToString();
+                       return base.ToString() + " Images.Count: " + images.Count.ToString() + ", ImageSize: " + this.ImageSize.ToString();
                }
                #endregion // Public Instance Methods
 
                #region Protected Instance Methods
                protected override void Dispose(bool disposing)
                {
+                       if (disposing)
+                               images.DestroyHandle();
+
                        base.Dispose(disposing);
                }
                #endregion // Protected Instance Methods
 
                #region Events
+               static object RecreateHandleEvent = new object ();
+
                [Browsable(false)]
                [EditorBrowsable(EditorBrowsableState.Advanced)]
                public event EventHandler RecreateHandle {
-                       add {
-                               recreateHandle += value;
-                       }
-
-                       remove {
-                               recreateHandle -= value;
-                       }
+                       add { Events.AddHandler (RecreateHandleEvent, value); }
+                       remove { Events.RemoveHandler (RecreateHandleEvent, value); }
                }
                #endregion // Events
        }