2006-12-04 Chris Toshok <toshok@ximian.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / ImageList.cs
1 //
2 // System.Windows.Forms.ImageList.cs
3 //
4 // Authors:
5 //   Peter Bartok <pbartok@novell.com>
6 //   Kornél Pál <http://www.kornelpal.hu/>
7 //
8 // Copyright (C) 2004-2005 Novell, Inc.
9 // Copyright (C) 2005 Kornél Pál
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 //
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 // COMPLETE
34
35 //
36 // Differences between MS.NET ImageList and this implementation:
37 //
38 // This is a fully managed image list implementation.
39 //
40 // Images are stored as Format32bppArgb internally but ColorDepth is applied
41 // to the colors of images. Images[index] returns a Format32bppArgb copy of
42 // the image so this difference is only internal.
43 //
44 // MS.NET has no alpha channel support (except for icons in 32-bit mode with
45 // comctl32.dll version 6.0) but this implementation has full alpha channel
46 // support in 32-bit mode.
47 //
48 // Handle should be an HIMAGELIST returned by ImageList_Create. This
49 // implementation uses (IntPtr)(-1) that is a non-zero but invalid handle.
50 //
51 // MS.NET destroys handles using the garbage collector this implementation
52 // does the same with Image objects stored in an ArrayList.
53 //
54 // MS.NET 1.x shares the same HIMAGELIST between ImageLists that were
55 // initialized from the same ImageListStreamer and doesn't update ImageSize
56 // and ColorDepth that are treated as bugs and MS.NET 2.0 behavior is
57 // implemented.
58 //
59 // MS.NET 2.0 initializes TransparentColor to Color.Transparent in
60 // constructors but ResetTransparentColor and ShouldSerializeTransparentColor
61 // default to Color.LightGray that is treated as a bug.
62 //
63 // MS.NET 2.0 does not clear keys when handle is destroyed that is treated as
64 // a bug.
65 //
66
67 using System.Collections;
68 using System.Collections.Specialized;
69 using System.ComponentModel;
70 using System.ComponentModel.Design.Serialization;
71 using System.Drawing;
72 using System.Drawing.Design;
73 using System.Drawing.Imaging;
74 using System.Globalization;
75 using System.Runtime.InteropServices;
76
77 namespace System.Windows.Forms
78 {
79         [DefaultProperty("Images")]
80         [Designer("System.Windows.Forms.Design.ImageListDesigner, " + Consts.AssemblySystem_Design)]
81 #if NET_2_0
82         [DesignerSerializer("System.Windows.Forms.Design.ImageListCodeDomSerializer, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.Serialization.CodeDomSerializer, " + Consts.AssemblySystem_Design)]
83 #endif
84         [ToolboxItemFilter("System.Windows.Forms")]
85         [TypeConverter(typeof(ImageListConverter))]
86         public sealed class ImageList : System.ComponentModel.Component
87         {
88                 #region Private Fields
89                 private const ColorDepth DefaultColorDepth = ColorDepth.Depth8Bit;
90                 private static readonly Size DefaultImageSize = new Size(16, 16);
91                 private static readonly Color DefaultTransparentColor = Color.Transparent;
92
93 #if NET_2_0
94                 private object tag;
95 #endif
96                 private readonly ImageCollection images;
97                 #endregion // Private Fields
98
99                 #region Sub-classes
100                 [Editor("System.Windows.Forms.Design.ImageCollectionEditor, " + Consts.AssemblySystem_Design, typeof(UITypeEditor))]
101                 public sealed class ImageCollection : IList, ICollection, IEnumerable
102                 {
103                         private const int AlphaMask = unchecked((int)0xFF000000);
104
105                         private
106 #if NET_2_0
107                         static
108 #else
109                         sealed
110 #endif
111                         class IndexedColorDepths
112                         {
113 #if !NET_2_0
114                                 private IndexedColorDepths()
115                                 {
116                                 }
117 #endif
118                                 internal static readonly ColorPalette Palette4Bit;
119                                 internal static readonly ColorPalette Palette8Bit;
120                                 private static readonly int[] squares;
121
122                                 static IndexedColorDepths()
123                                 {
124                                         Bitmap bitmap;
125                                         int index;
126
127                                         bitmap = new Bitmap(1, 1, PixelFormat.Format4bppIndexed);
128                                         Palette4Bit = bitmap.Palette;
129                                         bitmap.Dispose();
130
131                                         bitmap = new Bitmap(1, 1, PixelFormat.Format8bppIndexed);
132                                         Palette8Bit = bitmap.Palette;
133                                         bitmap.Dispose();
134
135                                         squares = new int[511];
136                                         for (index = 0; index < 256; index++)
137                                                 squares[255 + index] = squares[255 - index] = index * index;
138                                 }
139
140                                 internal static int GetNearestColor(Color[] palette, int color)
141                                 {
142                                         int index;
143                                         int count;
144                                         int red;
145                                         int green;
146                                         int blue;
147                                         int nearestColor;
148                                         int minDistance;
149                                         int distance;
150
151                                         count = palette.Length;
152                                         for (index = 0; index < count; index++)
153                                                 if (palette[index].ToArgb() == color)
154                                                         return color;
155
156                                         red = unchecked((int)(unchecked((uint)color) >> 16) & 0xFF);
157                                         green = unchecked((int)(unchecked((uint)color) >> 8) & 0xFF);
158                                         blue = color & 0xFF;
159                                         nearestColor = AlphaMask;
160                                         minDistance = int.MaxValue;
161
162                                         for (index = 0; index < count; index++)
163                                                 if ((distance = squares[255 + palette[index].R - red] + squares[255 + palette[index].G - green] + squares[255 + palette[index].B - blue]) < minDistance) {
164                                                         nearestColor = palette[index].ToArgb();
165                                                         minDistance = distance;
166                                                 }
167
168                                         return nearestColor;
169                                 }
170                         }
171
172                         [Flags()]
173                         private enum ItemFlags
174                         {
175                                 None = 0,
176                                 UseTransparentColor = 1,
177                                 ImageStrip = 2
178                         }
179
180                         private sealed class ImageListItem
181                         {
182                                 internal readonly object Image;
183                                 internal readonly ItemFlags Flags;
184                                 internal readonly Color TransparentColor;
185                                 internal readonly int ImageCount = 1;
186
187                                 internal ImageListItem(Icon value)
188                                 {
189                                         if (value == null)
190                                                 throw new ArgumentNullException("value");
191
192                                         // Icons are cloned.
193                                         this.Image = (Icon)value.Clone();
194                                 }
195
196                                 internal ImageListItem(Image value)
197                                 {
198                                         if (value == null)
199                                                 throw new ArgumentNullException("value");
200
201                                         if (!(value is Bitmap))
202                                                 throw new ArgumentException("Image must be a Bitmap.");
203
204                                         // Images are not cloned.
205                                         this.Image = value;
206                                 }
207
208                                 internal ImageListItem(Image value, Color transparentColor) : this(value)
209                                 {
210                                         this.Flags = ItemFlags.UseTransparentColor;
211                                         this.TransparentColor = transparentColor;
212                                 }
213
214                                 internal ImageListItem(Image value, int imageCount) : this(value)
215                                 {
216                                         this.Flags = ItemFlags.ImageStrip;
217                                         this.ImageCount = imageCount;
218                                 }
219                         }
220
221                         #region ImageCollection Private Fields
222                         private ColorDepth colorDepth = DefaultColorDepth;
223                         private Size imageSize = DefaultImageSize;
224                         private Color transparentColor = DefaultTransparentColor;
225                         private ArrayList list = new ArrayList();
226 #if NET_2_0
227                         private ArrayList keys = new ArrayList();
228 #endif
229                         private int count;
230                         private bool handleCreated;
231 #if NET_2_0
232                         private int lastKeyIndex = -1;
233 #endif
234                         private readonly ImageList owner;
235                         #endregion // ImageCollection Private Fields
236
237                         #region ImageCollection Internal Constructors
238                         // For use in ImageList
239                         internal ImageCollection(ImageList owner)
240                         {
241                                 this.owner = owner;
242                         }
243                         #endregion // ImageCollection Internal Constructor
244
245                         #region ImageCollection Internal Instance Properties
246                         // For use in ImageList
247                         internal ColorDepth ColorDepth {
248                                 get {
249                                         return this.colorDepth;
250                                 }
251
252                                 set {
253                                         if (!Enum.IsDefined(typeof(ColorDepth), value))
254                                                 throw new InvalidEnumArgumentException("value", (int)value, typeof(ColorDepth));
255
256                                         if (this.colorDepth != value) {
257                                                 this.colorDepth = value;
258                                                 RecreateHandle();
259                                         }
260                                 }
261                         }
262
263                         // For use in ImageList
264                         internal IntPtr Handle {
265                                 get {
266                                         CreateHandle();
267                                         return (IntPtr)(-1);
268                                 }
269                         }
270
271                         // For use in ImageList
272                         internal bool HandleCreated {
273                                 get {
274                                         return this.handleCreated;
275                                 }
276                         }
277
278                         // For use in ImageList
279                         internal Size ImageSize {
280                                 get {
281                                         return this.imageSize;
282                                 }
283
284                                 set {
285                                         if (value.Width < 1 || value.Width > 256 || value.Height < 1 || value.Height > 256)
286                                                 throw new ArgumentException("ImageSize.Width and Height must be between 1 and 256", "value");
287
288                                         if (this.imageSize != value) {
289                                                 this.imageSize = value;
290                                                 RecreateHandle();
291                                         }
292                                 }
293                         }
294
295                         // For use in ImageList
296                         internal ImageListStreamer ImageStream {
297                                 get {
298                                         return this.Empty ? null : new ImageListStreamer(this);
299                                 }
300
301                                 set {
302                                         int index;
303                                         Image[] streamImages;
304
305                                         if (value == null) {
306 #if NET_2_0
307                                                 if (this.handleCreated)
308                                                         DestroyHandle();
309                                                 else
310                                                         this.Clear();
311 #endif
312                                         }
313                                         // Only deserialized ImageListStreamers are used.
314                                         else if ((streamImages = value.Images) != null) {
315                                                 this.list = new ArrayList(streamImages.Length);
316                                                 this.count = 0;
317                                                 this.handleCreated = true;
318 #if NET_2_0
319                                                 this.keys = new ArrayList(streamImages.Length);
320 #endif
321
322                                                 for (index = 0; index < streamImages.Length; index++) {
323                                                         list.Add((Image)streamImages[index].Clone());
324 #if NET_2_0
325                                                         keys.Add(null);
326 #endif
327                                                 }
328
329                                                 // Invalid ColorDepth values are ignored.
330                                                 if (Enum.IsDefined(typeof(ColorDepth), value.ColorDepth))
331                                                         this.colorDepth = (ColorDepth)value.ColorDepth;
332                                                 this.imageSize = value.ImageSize;
333
334 #if NET_2_0
335                                                 // Event is raised even when handle was not created yet.
336                                                 owner.OnRecreateHandle();
337 #endif
338                                         }
339                                 }
340                         }
341
342                         // For use in ImageList
343                         internal Color TransparentColor {
344                                 get {
345                                         return this.transparentColor;
346                                 }
347
348                                 set {
349                                         this.transparentColor = value;
350                                 }
351                         }
352                         #endregion // ImageCollection Internal Instance Properties
353
354                         #region ImageCollection Public Instance Properties
355                         [Browsable(false)]
356                         public int Count {
357                                 get {
358                                         return this.handleCreated ? list.Count : this.count;
359                                 }
360                         }
361
362                         public bool Empty {
363                                 get {
364                                         return this.Count == 0;
365                                 }
366                         }
367
368                         public bool IsReadOnly {
369                                 get {
370                                         return false;
371                                 }
372                         }
373
374                         [Browsable(false)]
375                         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
376                         public Image this[int index] {
377                                 get {
378                                         return (Image)GetImage(index).Clone();
379                                 }
380
381                                 set {
382                                         Image image;
383
384                                         if (index < 0 || index >= this.Count)
385                                                 throw new ArgumentOutOfRangeException("index");
386
387                                         if (value == null)
388                                                 throw new ArgumentNullException("value");
389
390                                         if (!(value is Bitmap))
391                                                 throw new ArgumentException("Image must be a Bitmap.");
392
393                                         image = CreateImage(value, this.transparentColor);
394                                         CreateHandle();
395                                         list[index] = image;
396                                 }
397                         }
398
399 #if NET_2_0
400                         public Image this[string key] {
401                                 get {
402                                         int index;
403
404                                         return (index = IndexOfKey(key)) == -1 ? null : this[index];
405                                 }
406                         }
407
408                         public StringCollection Keys {
409                                 get {
410                                         int index;
411                                         string key;
412                                         StringCollection keyCollection;
413
414                                         // Returns all keys even when there are more keys than
415                                         // images. Null keys are returned as empty strings.
416
417                                         keyCollection = new StringCollection();
418                                         for (index = 0; index < keys.Count; index++)
419                                                 keyCollection.Add(((key = (string)keys[index]) == null || key.Length == 0) ? string.Empty : key);
420
421                                         return keyCollection;
422                                 }
423                         }
424 #endif
425                         #endregion // ImageCollection Public Instance Properties
426
427                         #region ImageCollection Private Static Methods
428 #if NET_2_0
429                         private static bool CompareKeys(string key1, string key2)
430                         {
431                                 // Keys are case-insensitive and keys with different length
432                                 // are not equal even when string.Compare treats them equal.
433
434                                 if (key1 == null || key2 == null || key1.Length != key2.Length)
435                                         return false;
436
437                                 return string.Compare(key1, key2, true, CultureInfo.InvariantCulture) == 0;
438                         }
439 #endif
440                         #endregion // ImageCollection Private Static Methods
441
442                         #region ImageCollection Private Instance Methods
443 #if NET_2_0
444                         private int AddItem(string key, ImageListItem item)
445 #else
446                         private int AddItem(ImageListItem item)
447 #endif
448                         {
449                                 int itemIndex;
450 #if NET_2_0
451                                 int index;
452 #endif
453
454                                 if (this.handleCreated)
455                                         itemIndex = AddItemInternal(item);
456                                 else {
457                                         // Image strips are counted as a single item in the return
458                                         // value of Add and AddStrip until handle is created.
459
460                                         itemIndex = list.Add(item);
461                                         this.count += item.ImageCount;
462                                 }
463
464 #if NET_2_0
465                                 if ((item.Flags & ItemFlags.ImageStrip) == 0)
466                                         keys.Add(key);
467                                 else
468                                         for (index = 0; index < item.ImageCount; index++)
469                                                 keys.Add(null);
470 #endif
471
472                                 return itemIndex;
473                         }
474
475                         private int AddItemInternal(ImageListItem item)
476                         {
477                                 if (item.Image is Icon) {
478                                         int imageWidth;
479                                         int imageHeight;
480                                         Bitmap bitmap;
481                                         Graphics graphics;
482
483                                         bitmap = new Bitmap(imageWidth = this.imageSize.Width, imageHeight = this.imageSize.Height, PixelFormat.Format32bppArgb);
484                                         graphics = Graphics.FromImage(bitmap);
485                                         graphics.DrawIcon((Icon)item.Image, new Rectangle(0, 0, imageWidth, imageHeight));
486                                         graphics.Dispose();
487
488                                         ReduceColorDepth(bitmap);
489                                         return list.Add(bitmap);
490                                 }
491                                 else if ((item.Flags & ItemFlags.ImageStrip) == 0)
492                                         return list.Add(CreateImage((Image)item.Image, (item.Flags & ItemFlags.UseTransparentColor) == 0 ? this.transparentColor : item.TransparentColor));
493                                 else {
494                                         int imageX;
495                                         int width;
496                                         int imageWidth;
497                                         int imageHeight;
498                                         int index;
499                                         Image image;
500                                         Bitmap bitmap;
501                                         Graphics graphics;
502                                         Rectangle imageRect;
503                                         ImageAttributes imageAttributes;
504
505                                         // When ImageSize was changed after adding image strips
506                                         // Count will return invalid values based on old ImageSize
507                                         // but when creating handle either ArgumentException will
508                                         // be thrown or image strip will be added according to the
509                                         // new ImageSize. This can result in image count
510                                         // difference that can result in exceptions in methods
511                                         // that use Count before creating handle. In addition this
512                                         // can result in the loss of sync with keys. When doing
513                                         // the same after handle was created there are no problems
514                                         // as handle will be recreated after changing ImageSize
515                                         // that results in the loss of images added previously.
516
517                                         if ((width = (image = (Image)item.Image).Width) == 0 || (width % (imageWidth = this.imageSize.Width)) != 0)
518                                                 throw new ArgumentException("Width of image strip must be a positive multiple of ImageSize.Width.", "value");
519
520                                         if (image.Height != (imageHeight = this.imageSize.Height))
521                                                 throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
522
523                                         imageRect = new Rectangle(0, 0, imageWidth, imageHeight);
524                                         if (this.transparentColor.A == 0)
525                                                 imageAttributes = null;
526                                         else {
527                                                 imageAttributes = new ImageAttributes();
528                                                 imageAttributes.SetColorKey(this.transparentColor, this.transparentColor);
529                                         }
530
531                                         index = list.Count;
532                                         for (imageX = 0; imageX < width; imageX += imageWidth) {
533                                                 bitmap = new Bitmap(imageWidth, imageHeight, PixelFormat.Format32bppArgb);
534                                                 graphics = Graphics.FromImage(bitmap);
535                                                 graphics.DrawImage(image, imageRect, imageX, 0, imageWidth, imageHeight, GraphicsUnit.Pixel, imageAttributes);
536                                                 graphics.Dispose();
537
538                                                 ReduceColorDepth(bitmap);
539                                                 list.Add(bitmap);
540                                         }
541
542                                         if (imageAttributes != null)
543                                                 imageAttributes.Dispose();
544
545                                         return index;
546                                 }
547                         }
548
549                         private void CreateHandle()
550                         {
551                                 int index;
552                                 ArrayList items;
553
554                                 if (!this.handleCreated) {
555                                         items = this.list;
556                                         this.list = new ArrayList(this.count);
557                                         this.count = 0;
558                                         this.handleCreated = true;
559
560                                         for (index = 0; index < items.Count; index++)
561                                                 AddItemInternal((ImageListItem)items[index]);
562                                 }
563                         }
564
565                         private Image CreateImage(Image value, Color transparentColor)
566                         {
567                                 int imageWidth;
568                                 int imageHeight;
569                                 Bitmap bitmap;
570                                 Graphics graphics;
571                                 ImageAttributes imageAttributes;
572
573                                 if (transparentColor.A == 0)
574                                         imageAttributes = null;
575                                 else {
576                                         imageAttributes = new ImageAttributes();
577                                         imageAttributes.SetColorKey(transparentColor, transparentColor);
578                                 }
579
580                                 bitmap = new Bitmap(imageWidth = this.imageSize.Width, imageHeight = this.imageSize.Height, PixelFormat.Format32bppArgb);
581                                 graphics = Graphics.FromImage(bitmap);
582                                 graphics.DrawImage(value, new Rectangle(0, 0, imageWidth, imageHeight), 0, 0, value.Width, value.Height, GraphicsUnit.Pixel, imageAttributes);
583                                 graphics.Dispose();
584
585                                 if (imageAttributes != null)
586                                         imageAttributes.Dispose();
587
588                                 ReduceColorDepth(bitmap);
589                                 return bitmap;
590                         }
591
592                         private void RecreateHandle()
593                         {
594                                 if (this.handleCreated) {
595                                         DestroyHandle();
596                                         this.handleCreated = true;
597                                         owner.OnRecreateHandle();
598                                 }
599                         }
600
601                         private unsafe void ReduceColorDepth(Bitmap bitmap)
602                         {
603                                 byte* pixelPtr;
604                                 byte* lineEndPtr;
605                                 byte* linePtr;
606                                 int line;
607                                 int pixel;
608                                 int height;
609                                 int widthBytes;
610                                 int stride;
611                                 BitmapData bitmapData;
612                                 Color[] palette;
613
614                                 if (this.colorDepth < ColorDepth.Depth32Bit) {
615                                         bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
616                                         try {
617                                                 linePtr = (byte*)bitmapData.Scan0;
618                                                 height = bitmapData.Height;
619                                                 widthBytes = bitmapData.Width << 2;
620                                                 stride = bitmapData.Stride;
621
622                                                 if (this.colorDepth < ColorDepth.Depth16Bit) {
623                                                         palette = (this.colorDepth < ColorDepth.Depth8Bit ? IndexedColorDepths.Palette4Bit : IndexedColorDepths.Palette8Bit).Entries;
624
625                                                         for (line = 0; line < height; line++) {
626                                                                 lineEndPtr = linePtr + widthBytes;
627                                                                 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
628                                                                         *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : IndexedColorDepths.GetNearestColor(palette, pixel | AlphaMask);
629                                                                 linePtr += stride;
630                                                         }
631                                                 }
632                                                 else if (this.colorDepth < ColorDepth.Depth24Bit) {
633                                                         for (line = 0; line < height; line++) {
634                                                                 lineEndPtr = linePtr + widthBytes;
635                                                                 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
636                                                                         *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : (pixel & 0x00F8F8F8) | AlphaMask;
637                                                                 linePtr += stride;
638                                                         }
639                                                 }
640                                                 else {
641                                                         for (line = 0; line < height; line++) {
642                                                                 lineEndPtr = linePtr + widthBytes;
643                                                                 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
644                                                                         *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : pixel | AlphaMask;
645                                                                 linePtr += stride;
646                                                         }
647                                                 }
648                                         }
649                                         finally {
650                                                 bitmap.UnlockBits(bitmapData);
651                                         }
652                                 }
653                         }
654                         #endregion // ImageCollection Private Instance Methods
655
656                         #region ImageCollection Internal Instance Methods
657                         // For use in ImageList
658                         internal void DestroyHandle()
659                         {
660                                 if (this.handleCreated) {
661                                         this.list = new ArrayList();
662                                         this.count = 0;
663                                         this.handleCreated = false;
664 #if NET_2_0
665                                         keys = new ArrayList();
666 #endif
667                                 }
668                         }
669
670                         // For use in ImageList
671                         internal Image GetImage(int index)
672                         {
673                                 if (index < 0 || index >= this.Count)
674                                         throw new ArgumentOutOfRangeException("index");
675
676                                 CreateHandle();
677                                 return (Image)list[index];
678                         }
679
680                         // For use in ImageListStreamer
681                         internal Image[] ToArray()
682                         {
683                                 Image[] images;
684
685                                 // Handle is created even when the list is empty.
686                                 CreateHandle();
687                                 images = new Image[list.Count];
688                                 list.CopyTo(images);
689                                 return images;
690                         }
691                         #endregion // ImageCollection Internal Instance Methods
692
693                         #region ImageCollection Public Instance Methods
694                         public void Add(Icon value)
695                         {
696 #if NET_2_0
697                                 Add(null, value);
698 #else
699                                 AddItem(new ImageListItem(value));
700 #endif
701                         }
702
703                         public void Add(Image value)
704                         {
705 #if NET_2_0
706                                 Add(null, value);
707 #else
708                                 AddItem(new ImageListItem(value));
709 #endif
710                         }
711
712                         public int Add(Image value, Color transparentColor)
713                         {
714 #if NET_2_0
715                                 return AddItem(null, new ImageListItem(value, transparentColor));
716 #else
717                                 return AddItem(new ImageListItem(value, transparentColor));
718 #endif
719                         }
720
721 #if NET_2_0
722                         public void Add(string key, Icon icon)
723                         {
724                                 // Argument has name icon but exceptions use name value.
725                                 AddItem(key, new ImageListItem(icon));
726                         }
727
728                         public void Add(string key, Image image)
729                         {
730                                 // Argument has name image but exceptions use name value.
731                                 AddItem(key, new ImageListItem(image));
732                         }
733
734                         public void AddRange(Image[] images)
735                         {
736                                 int index;
737
738                                 if (images == null)
739                                         throw new ArgumentNullException("images");
740
741                                 for (index = 0; index < images.Length; index++)
742                                         Add(images[index]);
743                         }
744 #endif
745
746                         public int AddStrip(Image value)
747                         {
748                                 int width;
749                                 int imageWidth;
750
751                                 if (value == null)
752                                         throw new ArgumentNullException("value");
753
754                                 if ((width = value.Width) == 0 || (width % (imageWidth = this.imageSize.Width)) != 0)
755                                         throw new ArgumentException("Width of image strip must be a positive multiple of ImageSize.Width.", "value");
756
757                                 if (value.Height != this.imageSize.Height)
758                                         throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
759
760 #if NET_2_0
761                                 return AddItem(null, new ImageListItem(value, width / imageWidth));
762 #else
763                                 return AddItem(new ImageListItem(value, width / imageWidth));
764 #endif
765                         }
766
767                         public void Clear()
768                         {
769                                 list.Clear();
770                                 if (this.handleCreated)
771                                         this.count = 0;
772 #if NET_2_0
773                                 keys.Clear();
774 #endif
775                         }
776
777 #if NET_2_0
778                         [EditorBrowsable(EditorBrowsableState.Never)]
779 #endif
780                         public bool Contains(Image image)
781                         {
782                                 throw new NotSupportedException();
783                         }
784
785 #if NET_2_0
786                         public bool ContainsKey(string key)
787                         {
788                                 return IndexOfKey(key) != -1;
789                         }
790 #endif
791
792                         public IEnumerator GetEnumerator()
793                         {
794                                 Image[] images = new Image[this.Count];
795                                 int index;
796
797                                 if (images.Length != 0) {
798                                         // Handle is created only when there are images.
799                                         CreateHandle();
800
801                                         for (index = 0; index < images.Length; index++)
802                                                 images[index] = (Image)((Image)list[index]).Clone();
803                                 }
804
805                                 return images.GetEnumerator();
806                         }
807
808 #if NET_2_0
809                         [EditorBrowsable(EditorBrowsableState.Never)]
810 #endif
811                         public int IndexOf(Image image)
812                         {
813                                 throw new NotSupportedException();
814                         }
815
816 #if NET_2_0
817                         public int IndexOfKey(string key)
818                         {
819                                 int index;
820
821                                 if (key != null && key.Length != 0) {
822                                         // When last IndexOfKey was successful and the same key was
823                                         // assigned to an image with a lower index than the last
824                                         // result and the key of the last result equals to key
825                                         // argument the last result is returned.
826
827                                         if (this.lastKeyIndex >= 0 && this.lastKeyIndex < this.Count && CompareKeys((string)keys[this.lastKeyIndex], key))
828                                                 return this.lastKeyIndex;
829
830                                         // Duplicate keys are allowed and first match is returned.
831                                         for (index = 0; index < this.Count; index++)
832                                                 if (CompareKeys((string)keys[index], key))
833                                                         return this.lastKeyIndex = index;
834                                 }
835
836                                 return this.lastKeyIndex = -1;
837                         }
838 #endif
839
840 #if NET_2_0
841                         [EditorBrowsable(EditorBrowsableState.Never)]
842 #endif
843                         public void Remove(Image image)
844                         {
845                                 throw new NotSupportedException();
846                         }
847
848                         public void RemoveAt(int index)
849                         {
850                                 if (index < 0 || index >= this.Count)
851                                         throw new ArgumentOutOfRangeException("index");
852
853                                 CreateHandle();
854                                 list.RemoveAt(index);
855 #if NET_2_0
856                                 keys.RemoveAt(index);
857 #endif
858                         }
859
860 #if NET_2_0
861                         public void RemoveByKey(string key)
862                         {
863                                 int index;
864
865                                 if ((index = IndexOfKey(key)) != -1)
866                                         RemoveAt(index);
867                         }
868
869                         public void SetKeyName(int index, string name)
870                         {
871                                 // Only SetKeyName throws IndexOutOfRangeException.
872                                 if (index < 0 || index >= this.Count)
873                                         throw new IndexOutOfRangeException();
874
875                                 keys[index] = name;
876                         }
877 #endif
878                         #endregion // ImageCollection Public Instance Methods
879
880                         #region ImageCollection Interface Properties
881                         object IList.this[int index] {
882                                 get {
883                                         return this[index];
884                                 }
885
886                                 set {
887                                         if (!(value is Image))
888                                                 throw new ArgumentException("value");
889
890                                         this[index] = (Image)value;
891                                 }
892                         }
893
894                         bool IList.IsFixedSize {
895                                 get {
896                                         return false;
897                                 }
898                         }
899
900                         bool ICollection.IsSynchronized {
901                                 get {
902                                         return false;
903                                 }
904                         }
905
906                         object ICollection.SyncRoot {
907                                 get {
908                                         return this;
909                                 }
910                         }
911                         #endregion // ImageCollection Interface Properties
912
913                         #region ImageCollection Interface Methods
914                         int IList.Add(object value)
915                         {
916                                 int index;
917
918                                 if (!(value is Image))
919                                         throw new ArgumentException("value");
920
921                                 index = this.Count;
922                                 this.Add((Image)value);
923                                 return index;
924                         }
925
926                         bool IList.Contains(object value)
927                         {
928                                 return value is Image ? this.Contains((Image)value) : false;
929                         }
930
931                         int IList.IndexOf(object value)
932                         {
933                                 return value is Image ? this.IndexOf((Image)value) : -1;
934                         }
935
936                         void IList.Insert(int index, object value)
937                         {
938                                 throw new NotSupportedException();
939                         }
940
941                         void IList.Remove(object value)
942                         {
943                                 if (value is Image)
944                                         this.Remove((Image)value);
945                         }
946
947                         void ICollection.CopyTo(Array array, int index)
948                         {
949                                 int imageIndex;
950
951                                 for (imageIndex = 0; imageIndex < this.Count; imageIndex++)
952                                         array.SetValue(this[index], index++);
953                         }
954                         #endregion // ImageCollection Interface Methods
955                 }
956                 #endregion // Sub-classes
957
958                 #region Public Constructors
959                 public ImageList()
960                 {
961                         images = new ImageCollection(this);
962                 }
963
964                 public ImageList(System.ComponentModel.IContainer container) : this()
965                 {
966                         container.Add(this);
967                 }
968                 #endregion // Public Constructors
969
970                 #region Private Instance Methods
971                 private void OnRecreateHandle()
972                 {
973                         EventHandler eh = (EventHandler)(Events [RecreateHandleEvent]);
974                         if (eh != null)
975                                 eh (this, EventArgs.Empty);
976                 }
977                 #endregion // Private Instance Methods
978
979                 #region Public Instance Properties
980 #if !NET_2_0
981                 [DefaultValue(DefaultColorDepth)]
982 #endif
983                 public ColorDepth ColorDepth {
984                         get {
985                                 return images.ColorDepth;
986                         }
987
988                         set {
989                                 images.ColorDepth = value;
990                         }
991                 }
992
993                 [Browsable(false)]
994                 [EditorBrowsable(EditorBrowsableState.Advanced)]
995                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
996                 public IntPtr Handle {
997                         get {
998                                 return images.Handle;
999                         }
1000                 }
1001
1002                 [Browsable(false)]
1003                 [EditorBrowsable(EditorBrowsableState.Advanced)]
1004                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1005                 public bool HandleCreated {
1006                         get {
1007                                 return images.HandleCreated;
1008                         }
1009                 }
1010
1011                 [DefaultValue(null)]
1012                 [MergableProperty(false)]
1013                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1014                 public ImageCollection Images {
1015                         get {
1016                                 return this.images;
1017                         }
1018                 }
1019
1020                 [Localizable(true)]
1021                 public Size ImageSize {
1022                         get {
1023                                 return images.ImageSize;
1024                         }
1025
1026                         set {
1027                                 images.ImageSize = value;
1028                         }
1029                 }
1030
1031                 [Browsable(false)]
1032                 [DefaultValue(null)]
1033                 [EditorBrowsable(EditorBrowsableState.Advanced)]
1034                 public ImageListStreamer ImageStream {
1035                         get {
1036                                 return images.ImageStream;
1037                         }
1038
1039                         set {
1040                                 images.ImageStream = value;
1041                         }
1042                 }
1043
1044 #if NET_2_0
1045                 [Bindable(true)]
1046                 [DefaultValue(null)]
1047                 [Localizable(false)]
1048                 [TypeConverter(typeof(StringConverter))]
1049                 public object Tag {
1050                         get {
1051                                 return this.tag;
1052                         }
1053
1054                         set {
1055                                 this.tag = value;
1056                         }
1057                 }
1058 #endif
1059
1060                 public Color TransparentColor {
1061                         get {
1062                                 return images.TransparentColor;
1063                         }
1064
1065                         set {
1066                                 images.TransparentColor = value;
1067                         }
1068                 }
1069                 #endregion // Public Instance Properties
1070
1071                 #region Public Instance Methods
1072                 public void Draw(Graphics g, Point pt, int index)
1073                 {
1074                         this.Draw(g, pt.X, pt.Y, index);
1075                 }
1076
1077                 public void Draw(Graphics g, int x, int y, int index)
1078                 {
1079                         g.DrawImage(images.GetImage(index), x, y);
1080                 }
1081
1082                 public void Draw(Graphics g, int x, int y, int width, int height, int index)
1083                 {
1084                         g.DrawImage(images.GetImage(index), x, y, width, height);
1085                 }
1086
1087                 public override string ToString()
1088                 {
1089                         return base.ToString() + " Images.Count: " + images.Count.ToString() + ", ImageSize: " + this.ImageSize.ToString();
1090                 }
1091                 #endregion // Public Instance Methods
1092
1093                 #region Protected Instance Methods
1094                 protected override void Dispose(bool disposing)
1095                 {
1096                         if (disposing)
1097                                 images.DestroyHandle();
1098
1099                         base.Dispose(disposing);
1100                 }
1101                 #endregion // Protected Instance Methods
1102
1103                 #region Events
1104                 static object RecreateHandleEvent = new object ();
1105
1106                 [Browsable(false)]
1107                 [EditorBrowsable(EditorBrowsableState.Advanced)]
1108                 public event EventHandler RecreateHandle {
1109                         add { Events.AddHandler (RecreateHandleEvent, value); }
1110                         remove { Events.RemoveHandler (RecreateHandleEvent, value); }
1111                 }
1112                 #endregion // Events
1113         }
1114 }