2006-12-31 Chris Toshok <toshok@ximian.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / ImageList.cs
index fa7a4f0beb3e8ae3a424944805e8cb8119ee8c9e..5a6f4de4bba0174833b8d6bd7bbeb5b70a01cb9d 100644 (file)
@@ -1,3 +1,15 @@
+//
+// System.Windows.Forms.ImageList.cs
+//
+// Authors:
+//   Peter Bartok <pbartok@novell.com>
+//   Kornél Pál <http://www.kornelpal.hu/>
+//
+// Copyright (C) 2004-2005 Novell, Inc.
+// Copyright (C) 2005 Kornél Pál
+//
+
+//
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // "Software"), to deal in the Software without restriction, including
 // distribute, sublicense, and/or sell copies of the Software, and to
 // permit persons to whom the Software is furnished to do so, subject to
 // the following conditions:
-// 
+//
 // The above copyright notice and this permission notice shall be
 // included in all copies or substantial portions of the Software.
-// 
+//
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-// Copyright (c) 2004-2005 Novell, Inc.
-//
-// Authors:
-//     Peter Bartok    pbartok@novell.com
-//
-
 
 // COMPLETE
 
+//
+// Differences between MS.NET ImageList and this implementation:
+//
+// This is a fully managed image list implementation.
+//
+// Images are stored as Format32bppArgb internally but ColorDepth is applied
+// to the colors of images. Images[index] returns a Format32bppArgb copy of
+// the image so this difference is only internal.
+//
+// MS.NET has no alpha channel support (except for icons in 32-bit mode with
+// comctl32.dll version 6.0) but this implementation has full alpha channel
+// support in 32-bit mode.
+//
+// Handle should be an HIMAGELIST returned by ImageList_Create. This
+// implementation uses (IntPtr)(-1) that is a non-zero but invalid handle.
+//
+// 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 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 {
+namespace System.Windows.Forms
+{
        [DefaultProperty("Images")]
        [Designer("System.Windows.Forms.Design.ImageListDesigner, " + Consts.AssemblySystem_Design)]
-       [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
-       [TypeConverter("System.Windows.Forms.ImageListConverter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
-       public sealed class ImageList : System.ComponentModel.Component {
-               #region Local Variables
-               private ColorDepth              color_depth;
-               private ImageCollection         image_collection;
-               private Size                    size;
-               private Color                   transparency_color;
-               private Delegate                handler;
-               private ImageListStreamer       image_stream;
-               #endregion      // Local Variables
+#if NET_2_0
+       [DesignerSerializer("System.Windows.Forms.Design.ImageListCodeDomSerializer, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.Serialization.CodeDomSerializer, " + Consts.AssemblySystem_Design)]
+#endif
+       [ToolboxItemFilter("System.Windows.Forms")]
+       [TypeConverter(typeof(ImageListConverter))]
+       public sealed class ImageList : System.ComponentModel.Component
+       {
+               #region Private Fields
+               private const ColorDepth DefaultColorDepth = ColorDepth.Depth8Bit;
+               private static readonly Size DefaultImageSize = new Size(16, 16);
+               private static readonly Color DefaultTransparentColor = Color.Transparent;
+
+#if NET_2_0
+               private object tag;
+#endif
+               private readonly ImageCollection images;
+               #endregion // Private Fields
 
                #region Sub-classes
-               [Editor("System.Windows.Forms.Design.ImageCollectionEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))]
-               public sealed class ImageCollection : IList, ICollection, IEnumerable {
-                       #region ImageCollection Local Variables
-                       private ImageList       owner;
-                       private ArrayList       list;
-                       #endregion      // ImageCollection Local Variables
+               [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);
+
+                       private
+#if NET_2_0
+                       static
+#else
+                       sealed
+#endif
+                       class IndexedColorDepths
+                       {
+#if !NET_2_0
+                               private IndexedColorDepths()
+                               {
+                               }
+#endif
+                               internal static readonly ColorPalette Palette4Bit;
+                               internal static readonly ColorPalette Palette8Bit;
+                               private static readonly int[] squares;
+
+                               static IndexedColorDepths()
+                               {
+                                       Bitmap bitmap;
+                                       int index;
+
+                                       bitmap = new Bitmap(1, 1, PixelFormat.Format4bppIndexed);
+                                       Palette4Bit = bitmap.Palette;
+                                       bitmap.Dispose();
+
+                                       bitmap = new Bitmap(1, 1, PixelFormat.Format8bppIndexed);
+                                       Palette8Bit = bitmap.Palette;
+                                       bitmap.Dispose();
+
+                                       squares = new int[511];
+                                       for (index = 0; index < 256; index++)
+                                               squares[255 + index] = squares[255 - index] = index * index;
+                               }
+
+                               internal static int GetNearestColor(Color[] palette, int color)
+                               {
+                                       int index;
+                                       int count;
+                                       int red;
+                                       int green;
+                                       int blue;
+                                       int nearestColor;
+                                       int minDistance;
+                                       int distance;
+
+                                       count = palette.Length;
+                                       for (index = 0; index < count; index++)
+                                               if (palette[index].ToArgb() == color)
+                                                       return color;
+
+                                       red = unchecked((int)(unchecked((uint)color) >> 16) & 0xFF);
+                                       green = unchecked((int)(unchecked((uint)color) >> 8) & 0xFF);
+                                       blue = color & 0xFF;
+                                       nearestColor = AlphaMask;
+                                       minDistance = int.MaxValue;
+
+                                       for (index = 0; index < count; index++)
+                                               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 = DefaultColorDepth;
+                       private Size imageSize = DefaultImageSize;
+                       private Color transparentColor = DefaultTransparentColor;
+                       private ArrayList list = new ArrayList();
+#if NET_2_0
+                       private ArrayList keys = new ArrayList();
+#endif
+                       private int count;
+                       private bool handleCreated;
+#if NET_2_0
+                       private int lastKeyIndex = -1;
+#endif
+                       private readonly ImageList owner;
+                       #endregion // ImageCollection Private Fields
+
+                       #region ImageCollection Internal Constructors
+                       // For use in ImageList
+                       internal ImageCollection(ImageList owner)
+                       {
+                               this.owner = owner;
+                       }
+                       #endregion // ImageCollection Internal Constructor
+
+                       #region ImageCollection Internal Instance Properties
+                       // For use in ImageList
+                       internal ColorDepth ColorDepth {
+                               get {
+                                       return this.colorDepth;
+                               }
+
+                               set {
+                                       if (!Enum.IsDefined(typeof(ColorDepth), value))
+                                               throw new InvalidEnumArgumentException("value", (int)value, typeof(ColorDepth));
+
+                                       if (this.colorDepth != value) {
+                                               this.colorDepth = value;
+                                               RecreateHandle();
+                                       }
+                               }
+                       }
+
+                       // For use in ImageList
+                       internal IntPtr Handle {
+                               get {
+                                       CreateHandle();
+                                       return (IntPtr)(-1);
+                               }
+                       }
+
+                       // For use in ImageList
+                       internal bool HandleCreated {
+                               get {
+                                       return this.handleCreated;
+                               }
+                       }
+
+                       // For use in ImageList
+                       internal Size ImageSize {
+                               get {
+                                       return this.imageSize;
+                               }
+
+                               set {
+                                       if (value.Width < 1 || value.Width > 256 || value.Height < 1 || value.Height > 256)
+                                               throw new ArgumentException("ImageSize.Width and Height must be between 1 and 256", "value");
+
+                                       if (this.imageSize != value) {
+                                               this.imageSize = value;
+                                               RecreateHandle();
+                                       }
+                               }
+                       }
+
+                       // For use in ImageList
+                       internal ImageListStreamer ImageStream {
+                               get {
+                                       return this.Empty ? null : new ImageListStreamer(this);
+                               }
+
+                               set {
+                                       int index;
+                                       Image[] streamImages;
+
+                                       if (value == null) {
+#if NET_2_0
+                                               if (this.handleCreated)
+                                                       DestroyHandle();
+                                               else
+                                                       this.Clear();
+#endif
+                                       }
+                                       // Only deserialized ImageListStreamers are used.
+                                       else if ((streamImages = value.Images) != null) {
+                                               this.list = new ArrayList(streamImages.Length);
+                                               this.count = 0;
+                                               this.handleCreated = true;
+#if NET_2_0
+                                               this.keys = new ArrayList(streamImages.Length);
+#endif
+
+                                               for (index = 0; index < streamImages.Length; index++) {
+                                                       list.Add((Image)streamImages[index].Clone());
+#if NET_2_0
+                                                       keys.Add(null);
+#endif
+                                               }
+
+                                               // Invalid ColorDepth values are ignored.
+                                               if (Enum.IsDefined(typeof(ColorDepth), value.ColorDepth))
+                                                       this.colorDepth = (ColorDepth)value.ColorDepth;
+                                               this.imageSize = value.ImageSize;
+
+#if NET_2_0
+                                               // Event is raised even when handle was not created yet.
+                                               owner.OnRecreateHandle();
+#endif
+                                       }
+                               }
+                       }
+
+                       // For use in ImageList
+                       internal Color TransparentColor {
+                               get {
+                                       return this.transparentColor;
+                               }
 
-                       #region ImageCollection Private Constructors
-                       internal ImageCollection(ImageList owner) {
-                               this.owner=owner;
-                               this.list=new ArrayList();
+                               set {
+                                       this.transparentColor = value;
+                               }
                        }
-                       #endregion      // ImageCollection Private Constructor
+                       #endregion // ImageCollection Internal Instance Properties
 
-                       #region ImageCollection Public Instance Properties
+                       #region ImageCollection Public Instance Properties
                        [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;
                                }
                        }
 
                        public bool IsReadOnly {
                                get {
-                                       return list.IsReadOnly;
+                                       return false;
                                }
                        }
 
@@ -85,183 +371,519 @@ namespace System.Windows.Forms {
                        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
                        public Image this[int index] {
                                get {
-                                       if (index<0 || index>=list.Count) {
-                                               throw new ArgumentOutOfRangeException("index", index, "ImageCollection does not have that many images");
-                                       }
-                                       return (Image)list[index];
+                                       return (Image)GetImage(index).Clone();
                                }
 
                                set {
-                                       if (index<0 || index>=list.Count) {
-                                               throw new ArgumentOutOfRangeException("index", index, "ImageCollection does not have that many images");
-                                       }
+                                       Image image;
 
-                                       if (value==null) {
-                                               throw new ArgumentOutOfRangeException("value", value, "Image cannot be null");
-                                       }
+                                       if (index < 0 || index >= this.Count)
+                                               throw new ArgumentOutOfRangeException("index");
 
-                                       list[index]=value;
-                                       // What happens if the bitmap had a previous 'MakeTransparent' done to it?
-                                       ((Bitmap)list[index]).MakeTransparent(owner.transparency_color);
+                                       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;
                                }
                        }
-                       #endregion      // ImageCollection Public Instance Properties
 
-                       #region ImageCollection Private Instance Methods
-                       private int AddInternal(Image image) {
-                               int             width;
-                               int             height;
-                               PixelFormat     format;
+#if NET_2_0
+                       public Image this[string key] {
+                               get {
+                                       int index;
 
-                               width=owner.ImageSize.Width;
-                               height=owner.ImageSize.Height;
-                               switch(owner.color_depth) {
-                                       case ColorDepth.Depth4Bit:      format=PixelFormat.Format4bppIndexed; break;
-                                       case ColorDepth.Depth8Bit:      format=PixelFormat.Format8bppIndexed; break;
-                                       case ColorDepth.Depth16Bit:     format=PixelFormat.Format16bppRgb555; break;
-                                       case ColorDepth.Depth24Bit:     format=PixelFormat.Format24bppRgb; break;
-                                       case ColorDepth.Depth32Bit:     format=PixelFormat.Format32bppRgb; break;
-                                       default:                        format=PixelFormat.Format32bppRgb; break;
+                                       return (index = IndexOfKey(key)) == -1 ? null : this[index];
                                }
+                       }
 
-                               // Check if we can add straight or if we have to resize
-                               if (image.Width!=width || image.Height!=height || image.PixelFormat!=format) {
-                                       Graphics        g;
-                                       Bitmap          reformatted_image;
+                       public StringCollection Keys {
+                               get {
+                                       int index;
+                                       string key;
+                                       StringCollection keyCollection;
 
-                                       reformatted_image = new Bitmap(width, height, format);
-                                       g=Graphics.FromImage(reformatted_image);
+                                       // Returns all keys even when there are more keys than
+                                       // images. Null keys are returned as empty strings.
 
-                                       g.DrawImage(image, new Rectangle(0, 0, width, height), 0, 0, width, height, GraphicsUnit.Pixel);
-                                       g.Dispose();
+                                       keyCollection = new StringCollection();
+                                       for (index = 0; index < keys.Count; index++)
+                                               keyCollection.Add(((key = (string)keys[index]) == null || key.Length == 0) ? string.Empty : key);
 
-                                       return list.Add(reformatted_image);
-                               } else {
-                                       return list.Add(image);
+                                       return keyCollection;
                                }
                        }
+#endif
+                       #endregion // ImageCollection Public Instance Properties
 
-                       internal void Dispose() {
-#if dontwantthis
-                               if (list!=null) {
-                                       for (int i=0; i<list.Count; i++) {
-                                               ((Image)list[i]).Dispose();
-                                       }
+                       #region ImageCollection Private Static Methods
+#if NET_2_0
+                       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;
+                       }
+#endif
+                       #endregion // ImageCollection Private Static Methods
+
+                       #region ImageCollection Private Instance Methods
+#if NET_2_0
+                       private int AddItem(string key, ImageListItem item)
+#else
+                       private int AddItem(ImageListItem item)
+#endif
+                       {
+                               int itemIndex;
+#if NET_2_0
+                               int index;
+#endif
+
+                               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 NET_2_0
+                               if ((item.Flags & ItemFlags.ImageStrip) == 0)
+                                       keys.Add(key);
+                               else
+                                       for (index = 0; index < item.ImageCount; index++)
+                                               keys.Add(null);
 #endif
+
+                               return itemIndex;
                        }
-                       #endregion      // ImageCollection Private Instance Methods
 
-                       #region ImageCollection Public Instance Methods
-                       public int Add(Image value, Color transparentColor) {
-                               if (value==null) {
-                                       throw new ArgumentNullException("value", "Cannot add null image");
+                       private int AddItemInternal(ImageListItem item)
+                       {
+                               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();
+
+                                       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);
+                                       }
 
-                               ((Bitmap)value).MakeTransparent(owner.transparency_color);
-                               return AddInternal(value);
-                               
-                       }
+                                       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();
 
-                       public void Add(Icon value) {
-                               Image image;
+                                               ReduceColorDepth(bitmap);
+                                               list.Add(bitmap);
+                                       }
 
-                               image = value.ToBitmap();
+                                       if (imageAttributes != null)
+                                               imageAttributes.Dispose();
 
-                               if (value==null || image==null) {
-                                       throw new ArgumentNullException("value", "Cannot add null icon");
+                                       return index;
                                }
-
-                               ((Bitmap)image).MakeTransparent(owner.transparency_color);
-                               AddInternal(image);
                        }
 
-                       public void Add(Image value) {
-                               if (value==null) {
-                                       throw new ArgumentNullException("value", "Cannot add null image");
+                       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]);
                                }
-                               ((Bitmap)value).MakeTransparent(owner.transparency_color);
-                               AddInternal(value);
                        }
 
-                       public int AddStrip(Image value) {
-                               int             image_count;
-                               int             width;
-                               int             height;
-                               Bitmap          image;
-                               Graphics        g;
+                       private Image CreateImage(Image value, Color transparentColor)
+                       {
+                               int imageWidth;
+                               int imageHeight;
+                               Bitmap bitmap;
+                               Graphics graphics;
+                               ImageAttributes imageAttributes;
+
+                               if (transparentColor.A == 0)
+                                       imageAttributes = null;
+                               else {
+                                       imageAttributes = new ImageAttributes();
+                                       imageAttributes.SetColorKey(transparentColor, transparentColor);
+                               }
+
+                               bitmap = new Bitmap(imageWidth = this.imageSize.Width, imageHeight = this.imageSize.Height, PixelFormat.Format32bppArgb);
+                               graphics = Graphics.FromImage(bitmap);
+                               graphics.DrawImage(value, new Rectangle(0, 0, imageWidth, imageHeight), 0, 0, value.Width, value.Height, GraphicsUnit.Pixel, imageAttributes);
+                               graphics.Dispose();
+
+                               if (imageAttributes != null)
+                                       imageAttributes.Dispose();
 
-                               if (value==null) {
-                                       throw new ArgumentNullException("value", "Cannot add null images");
+                               ReduceColorDepth(bitmap);
+                               return bitmap;
+                       }
+
+                       private void RecreateHandle()
+                       {
+                               if (this.handleCreated) {
+                                       DestroyHandle();
+                                       this.handleCreated = true;
+                                       owner.OnRecreateHandle();
                                }
+                       }
 
-                               if ((value.Width % owner.ImageSize.Width) != 0) {
-                                       throw new ArgumentException("Strip is not a multiple of the ImageList with", "value");
+                       private unsafe void ReduceColorDepth(Bitmap bitmap)
+                       {
+                               byte* pixelPtr;
+                               byte* lineEndPtr;
+                               byte* linePtr;
+                               int line;
+                               int pixel;
+                               int height;
+                               int widthBytes;
+                               int stride;
+                               BitmapData bitmapData;
+                               Color[] palette;
+
+                               if (this.colorDepth < ColorDepth.Depth32Bit) {
+                                       bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
+                                       try {
+                                               linePtr = (byte*)bitmapData.Scan0;
+                                               height = bitmapData.Height;
+                                               widthBytes = bitmapData.Width << 2;
+                                               stride = bitmapData.Stride;
+
+                                               if (this.colorDepth < ColorDepth.Depth16Bit) {
+                                                       palette = (this.colorDepth < ColorDepth.Depth8Bit ? IndexedColorDepths.Palette4Bit : IndexedColorDepths.Palette8Bit).Entries;
+
+                                                       for (line = 0; line < height; line++) {
+                                                               lineEndPtr = linePtr + widthBytes;
+                                                               for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
+                                                                       *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : IndexedColorDepths.GetNearestColor(palette, pixel | AlphaMask);
+                                                               linePtr += stride;
+                                                       }
+                                               }
+                                               else if (this.colorDepth < ColorDepth.Depth24Bit) {
+                                                       for (line = 0; line < height; line++) {
+                                                               lineEndPtr = linePtr + widthBytes;
+                                                               for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
+                                                                       *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : (pixel & 0x00F8F8F8) | AlphaMask;
+                                                               linePtr += stride;
+                                                       }
+                                               }
+                                               else {
+                                                       for (line = 0; line < height; line++) {
+                                                               lineEndPtr = linePtr + widthBytes;
+                                                               for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
+                                                                       *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : pixel | AlphaMask;
+                                                               linePtr += stride;
+                                                       }
+                                               }
+                                       }
+                                       finally {
+                                               bitmap.UnlockBits(bitmapData);
+                                       }
+                               }
+                       }
+                       #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;
+#if NET_2_0
+                                       keys = new ArrayList();
+#endif
                                }
+                       }
+
+                       // For use in ImageList
+                       internal Image GetImage(int index)
+                       {
+                               if (index < 0 || index >= this.Count)
+                                       throw new ArgumentOutOfRangeException("index");
 
-                               // MSDN: The number of images is inferred from the width. A strip is multiple images side-by-side
-                               width=owner.ImageSize.Width;
-                               height=owner.ImageSize.Height;
-                               image_count=value.Width/width;
-                               for (int i=0; i<image_count; i++) {
-                                       image = new Bitmap(value, width, height);
-                                       g = Graphics.FromImage(image);
+                               CreateHandle();
+                               return (Image)list[index];
+                       }
 
-                                       g.DrawImage(value, new Rectangle(0, 0, width, height), i*width, 0, width, height, GraphicsUnit.Pixel);
-                                       AddInternal(image);
+                       // For use in ImageListStreamer
+                       internal Image[] ToArray()
+                       {
+                               Image[] images;
 
-                                       g.Dispose();
-                                       image.Dispose();
-                               }
+                               // Handle is created even when the list is empty.
+                               CreateHandle();
+                               images = new Image[list.Count];
+                               list.CopyTo(images);
+                               return images;
+                       }
+                       #endregion // ImageCollection Internal Instance Methods
+
+                       #region ImageCollection Public Instance Methods
+                       public void Add(Icon value)
+                       {
+#if NET_2_0
+                               Add(null, value);
+#else
+                               AddItem(new ImageListItem(value));
+#endif
+                       }
+
+                       public void Add(Image value)
+                       {
+#if NET_2_0
+                               Add(null, value);
+#else
+                               AddItem(new ImageListItem(value));
+#endif
+                       }
+
+                       public int Add(Image value, Color transparentColor)
+                       {
+#if NET_2_0
+                               return AddItem(null, new ImageListItem(value, transparentColor));
+#else
+                               return AddItem(new ImageListItem(value, transparentColor));
+#endif
+                       }
+
+#if NET_2_0
+                       public void Add(string key, Icon icon)
+                       {
+                               // Argument has name icon but exceptions use name value.
+                               AddItem(key, new ImageListItem(icon));
+                       }
+
+                       public void Add(string key, Image image)
+                       {
+                               // Argument has name image but exceptions use name value.
+                               AddItem(key, new ImageListItem(image));
+                       }
+
+                       public void AddRange(Image[] images)
+                       {
+                               int index;
+
+                               if (images == null)
+                                       throw new ArgumentNullException("images");
+
+                               for (index = 0; index < images.Length; index++)
+                                       Add(images[index]);
+                       }
+#endif
+
+                       public int AddStrip(Image value)
+                       {
+                               int width;
+                               int imageWidth;
+
+                               if (value == null)
+                                       throw new ArgumentNullException("value");
 
-                               // FIXME - is this right? MSDN says to return the index, but we might have multiple...
-                               return image_count;
+                               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 != this.imageSize.Height)
+                                       throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
+
+#if NET_2_0
+                               return AddItem(null, new ImageListItem(value, width / imageWidth));
+#else
+                               return AddItem(new ImageListItem(value, width / imageWidth));
+#endif
                        }
 
-                       public void Clear() {
+                       public void Clear()
+                       {
                                list.Clear();
+                               if (this.handleCreated)
+                                       this.count = 0;
+#if NET_2_0
+                               keys.Clear();
+#endif
                        }
 
-                       public bool Contains(Image image) {
-                               return list.Contains(image);
+#if NET_2_0
+                       [EditorBrowsable(EditorBrowsableState.Never)]
+#endif
+                       public bool Contains(Image image)
+                       {
+                               throw new NotSupportedException();
                        }
 
-                       public IEnumerator GetEnumerator() {
-                               return list.GetEnumerator();
+#if NET_2_0
+                       public bool ContainsKey(string key)
+                       {
+                               return IndexOfKey(key) != -1;
                        }
+#endif
+
+                       public IEnumerator GetEnumerator()
+                       {
+                               Image[] images = new Image[this.Count];
+                               int index;
+
+                               if (images.Length != 0) {
+                                       // Handle is created only when there are images.
+                                       CreateHandle();
 
-                       public int IndexOf(Image image) {
-                               return list.IndexOf(image);
+                                       for (index = 0; index < images.Length; index++)
+                                               images[index] = (Image)((Image)list[index]).Clone();
+                               }
+
+                               return images.GetEnumerator();
                        }
 
-                       public void Remove(Image image) {
-                               list.Remove(image);
+#if NET_2_0
+                       [EditorBrowsable(EditorBrowsableState.Never)]
+#endif
+                       public int IndexOf(Image image)
+                       {
+                               throw new NotSupportedException();
                        }
 
-                       public void RemoveAt(int index) {
-                               if (index<0 || index>=list.Count) {
-                                       throw new ArgumentOutOfRangeException("index", index, "ImageCollection does not have that many images");
+#if NET_2_0
+                       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;
+                       }
+#endif
+
+#if NET_2_0
+                       [EditorBrowsable(EditorBrowsableState.Never)]
+#endif
+                       public void Remove(Image image)
+                       {
+                               throw new NotSupportedException();
+                       }
+
+                       public void RemoveAt(int index)
+                       {
+                               if (index < 0 || index >= this.Count)
+                                       throw new ArgumentOutOfRangeException("index");
+
+                               CreateHandle();
                                list.RemoveAt(index);
+#if NET_2_0
+                               keys.RemoveAt(index);
+#endif
+                       }
+
+#if NET_2_0
+                       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
+#endif
+                       #endregion // ImageCollection Public Instance Methods
 
-                       #region ImageCollection Interface Properties
+                       #region ImageCollection Interface Properties
                        object IList.this[int index] {
                                get {
-                                       if (index<0 || index>=list.Count) {
-                                               throw new ArgumentOutOfRangeException("index", index, "ImageCollection does not have that many images");
-                                       }
                                        return this[index];
                                }
 
                                set {
-                                       if (!(value is Bitmap)) {
-                                               throw new ArgumentException("Object of type Image required", "value");
-                                       }
+                                       if (!(value is Image))
+                                               throw new ArgumentException("value");
 
-                                       this[index]=(Image)value;
+                                       this[index] = (Image)value;
                                }
                        }
 
@@ -271,121 +893,162 @@ namespace System.Windows.Forms {
                                }
                        }
 
-                       bool IList.IsReadOnly {
-                               get {
-                                       return list.IsReadOnly;
-                               }
-                       }
-
                        bool ICollection.IsSynchronized {
                                get {
-                                       return list.IsSynchronized;
+                                       return false;
                                }
                        }
 
                        object ICollection.SyncRoot {
                                get {
-                                       return list.SyncRoot;
+                                       return this;
                                }
                        }
-                       #endregion      // ImageCollection Interface Properties
+                       #endregion // ImageCollection Interface Properties
 
-                       #region ImageCollection Interface Methods
-                       int IList.Add(object value) {
-                               if (value == null) {
-                                       throw new ArgumentNullException("value", "Cannot add null images");
-                               }
+                       #region ImageCollection Interface Methods
+                       int IList.Add(object value)
+                       {
+                               int index;
 
-                               if (!(value is Bitmap)) {
-                                       throw new ArgumentException("Object of type Image required", "value");
-                               }
+                               if (!(value is Image))
+                                       throw new ArgumentException("value");
 
-                               return list.Add(value);
+                               index = this.Count;
+                               this.Add((Image)value);
+                               return index;
                        }
 
-                       bool IList.Contains(object value) {
-                               if (!(value is Bitmap)) {
-                                       throw new ArgumentException("Object of type Image required", "value");
-                               }
-
-                               return this.Contains((Image) value);
+                       bool IList.Contains(object value)
+                       {
+                               return value is Image ? this.Contains((Image)value) : false;
                        }
 
-                       int IList.IndexOf(object value) {
-                               if (!(value is Bitmap)) {
-                                       throw new ArgumentException("Object of type Image required", "value");
-                               }
-
-                               return this.IndexOf((Image) value);
+                       int IList.IndexOf(object value)
+                       {
+                               return value is Image ? this.IndexOf((Image)value) : -1;
                        }
 
-                       void IList.Insert(int index, object value) {
-                               if (!(value is Bitmap)) {
-                                       throw new ArgumentException("Object of type Image required", "value");
-                               }
-                               list.Insert(index, value);
+                       void IList.Insert(int index, object value)
+                       {
+                               throw new NotSupportedException();
                        }
 
-                       void IList.Remove(object value) {
-                               if (!(value is Bitmap)) {
-                                       throw new ArgumentException("Object of type Image required", "value");
-                               }
-                               list.Remove(value);
+                       void IList.Remove(object value)
+                       {
+                               if (value is Image)
+                                       this.Remove((Image)value);
                        }
 
-                       void ICollection.CopyTo(Array array, int index) {
-                               if (list.Count>0) {
-                                       list.CopyTo(array, index);
-                               }
+                       void ICollection.CopyTo(Array array, int index)
+                       {
+                               int imageIndex;
+
+                               for (imageIndex = 0; imageIndex < this.Count; imageIndex++)
+                                       array.SetValue(this[index], index++);
                        }
-                       #endregion      // ImageCollection Interface Methods
+                       #endregion // ImageCollection Interface Methods
                }
-               #endregion      // Sub-classes
+               #endregion // Sub-classes
 
                #region Public Constructors
-               public ImageList() {
-                       color_depth = ColorDepth.Depth8Bit;
-                       transparency_color = Color.Transparent;
-                       size = new Size(16, 16);
-                       image_collection = new ImageCollection(this);
+               public ImageList()
+               {
+                       images = new ImageCollection(this);
+               }
+
+               public ImageList(System.ComponentModel.IContainer container) : this()
+               {
+                       container.Add(this);
+               }
+               #endregion // Public Constructors
+
+               #region Private Instance Methods
+               private void OnRecreateHandle()
+               {
+                       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.
+#if NET_2_0
+                       return images.Empty;
+#else
+                       return this.ImageSize != DefaultImageSize;
+#endif
+               }
+
+
+               internal void ResetColorDepth ()
+               {
+                       this.ColorDepth = DefaultColorDepth;
+               }
+
+#if NET_2_0
+               internal void ResetImageSize ()
+               {
+                       this.ImageSize = DefaultImageSize;
                }
 
-               public ImageList(System.ComponentModel.IContainer container) : this ()
+               internal void ResetTransparentColor ()
                {
-                       container.Add (this);
+                       this.TransparentColor = Color.LightGray;
                }
-               #endregion      // Public Constructors
+#endif
+               #endregion // Private Instance Methods
 
                #region Public Instance Properties
-               [DefaultValue(ColorDepth.Depth8Bit)]
+#if !NET_2_0
+               [DefaultValue(DefaultColorDepth)]
+#endif
                public ColorDepth ColorDepth {
                        get {
-                               return this.color_depth;
+                               return images.ColorDepth;
                        }
 
                        set {
-                               this.color_depth=value;
+                               images.ColorDepth = value;
                        }
                }
 
-               [MonoTODO("Determine if we support HBITMAP handles, this would involve XplatUI")]
                [Browsable(false)]
                [EditorBrowsable(EditorBrowsableState.Advanced)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
                public IntPtr Handle {
                        get {
-                               if (RecreateHandle!=null) RecreateHandle(this, EventArgs.Empty);
-                               return IntPtr.Zero;
+                               return images.Handle;
                        }
                }
 
-               [MonoTODO("Determine if we support HBITMAP handles, this would involve XplatUI")]
                [Browsable(false)]
                [EditorBrowsable(EditorBrowsableState.Advanced)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
                public bool HandleCreated {
                        get {
-                               return false;
+                               return images.HandleCreated;
                        }
                }
 
@@ -394,21 +1057,18 @@ namespace System.Windows.Forms {
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
                public ImageCollection Images {
                        get {
-                               return this.image_collection;
+                               return this.images;
                        }
                }
 
                [Localizable(true)]
                public Size ImageSize {
                        get {
-                               return this.size;
+                               return images.ImageSize;
                        }
 
                        set {
-                               if (value.Width<1 || value.Width>256 || value.Height<1 || value.Height>256) {
-                                       throw new ArgumentException("ImageSize width and height must be between 1 and 255", "value");
-                               }
-                               this.size=value;
+                               images.ImageSize = value;
                        }
                }
 
@@ -417,62 +1077,82 @@ namespace System.Windows.Forms {
                [EditorBrowsable(EditorBrowsableState.Advanced)]
                public ImageListStreamer ImageStream {
                        get {
-                               return image_stream;
+                               return images.ImageStream;
+                       }
+
+                       set {
+                               images.ImageStream = value;
+                       }
+               }
+
+#if NET_2_0
+               [Bindable(true)]
+               [DefaultValue(null)]
+               [Localizable(false)]
+               [TypeConverter(typeof(StringConverter))]
+               public object Tag {
+                       get {
+                               return this.tag;
                        }
 
                        set {
-                               image_stream = value;
+                               this.tag = value;
                        }
                }
+#endif
 
                public Color TransparentColor {
                        get {
-                               return this.transparency_color;
+                               return images.TransparentColor;
                        }
 
                        set {
-                               this.transparency_color=value;
+                               images.TransparentColor = value;
                        }
                }
-               #endregion      // Public Instance Properties
+               #endregion // Public Instance Properties
 
                #region Public Instance Methods
-               public void Draw(Graphics g, Point pt, int index) {
+               public void Draw(Graphics g, Point pt, int index)
+               {
                        this.Draw(g, pt.X, pt.Y, index);
                }
 
-               public void Draw(Graphics g, int x, int y, int index) {
-                       this.Draw(g, x, y, this.size.Width, this.size.Height, index);
+               public void Draw(Graphics g, int x, int y, int index)
+               {
+                       g.DrawImage(images.GetImage(index), x, y);
                }
 
-               public void Draw(Graphics g, int x, int y, int width, int height, int index) {
-                       Image   i;
-
-                       if ((index < 0) || (index >= this.Images.Count)) {
-                               throw new ArgumentOutOfRangeException("index", index, "ImageList does not contain that many images");
-                       }
-                       i = this.Images[index];
-                       g.DrawImage(i, x, y, width, height);
+               public void Draw(Graphics g, int x, int y, int width, int height, int index)
+               {
+                       g.DrawImage(images.GetImage(index), x, y, width, height);
                }
 
-               public override string ToString() {\r
-                       return "ImageList Size "+this.size.Width.ToString()+"x"+this.size.Height.ToString()+", Depth "+this.color_depth.ToString()+", Transparency color "+this.transparency_color.ToString();\r
+               public override string ToString()
+               {
+                       return base.ToString() + " Images.Count: " + images.Count.ToString() + ", ImageSize: " + this.ImageSize.ToString();
                }
-               #endregion      // Public Instance Methods
+               #endregion // Public Instance Methods
 
-               #region Protected Instance Methods
-               protected override void Dispose(bool disposing) {\r
-                       if (image_collection!=null) {\r
-                               image_collection.Dispose();\r
-                       }\r
-               }\r
+               #region Protected Instance Methods
+               protected override void Dispose(bool disposing)
+               {
+                       if (disposing)
+                               images.DestroyHandle();
 
-               #endregion      // Protected Instance Methods
+                       base.Dispose(disposing);
+               }
+               #endregion // Protected Instance Methods
 
                #region Events
+               static object RecreateHandleEvent = new object ();
+
                [Browsable(false)]
                [EditorBrowsable(EditorBrowsableState.Advanced)]
-               public event EventHandler RecreateHandle;
-               #endregion      // Events
+               public event EventHandler RecreateHandle {
+                       add { Events.AddHandler (RecreateHandleEvent, value); }
+                       remove { Events.RemoveHandler (RecreateHandleEvent, value); }
+               }
+               #endregion // Events
        }
 }