New test.
[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 EventHandler recreateHandle;
97                 private readonly ImageCollection images;
98                 #endregion // Private Fields
99
100                 #region Sub-classes
101                 [Editor("System.Windows.Forms.Design.ImageCollectionEditor, " + Consts.AssemblySystem_Design, typeof(UITypeEditor))]
102                 public sealed class ImageCollection : IList, ICollection, IEnumerable
103                 {
104                         private const int AlphaMask = unchecked((int)0xFF000000);
105
106                         private
107 #if NET_2_0
108                         static
109 #else
110                         sealed
111 #endif
112                         class IndexedColorDepths
113                         {
114 #if !NET_2_0
115                                 private IndexedColorDepths()
116                                 {
117                                 }
118 #endif
119                                 internal static readonly ColorPalette Palette4Bit;
120                                 internal static readonly ColorPalette Palette8Bit;
121                                 private static readonly int[] squares;
122
123                                 static IndexedColorDepths()
124                                 {
125                                         Bitmap bitmap;
126                                         int index;
127
128                                         bitmap = new Bitmap(1, 1, PixelFormat.Format4bppIndexed);
129                                         Palette4Bit = bitmap.Palette;
130                                         bitmap.Dispose();
131
132                                         bitmap = new Bitmap(1, 1, PixelFormat.Format8bppIndexed);
133                                         Palette8Bit = bitmap.Palette;
134                                         bitmap.Dispose();
135
136                                         squares = new int[511];
137                                         for (index = 0; index < 256; index++)
138                                                 squares[255 + index] = squares[255 - index] = index * index;
139                                 }
140
141                                 internal static int GetNearestColor(Color[] palette, int color)
142                                 {
143                                         int index;
144                                         int count;
145                                         int red;
146                                         int green;
147                                         int blue;
148                                         int nearestColor;
149                                         int minDistance;
150                                         int distance;
151
152                                         count = palette.Length;
153                                         for (index = 0; index < count; index++)
154                                                 if (palette[index].ToArgb() == color)
155                                                         return color;
156
157                                         red = unchecked((int)(unchecked((uint)color) >> 16) & 0xFF);
158                                         green = unchecked((int)(unchecked((uint)color) >> 8) & 0xFF);
159                                         blue = color & 0xFF;
160                                         nearestColor = AlphaMask;
161                                         minDistance = int.MaxValue;
162
163                                         for (index = 0; index < count; index++)
164                                                 if ((distance = squares[255 + palette[index].R - red] + squares[255 + palette[index].G - green] + squares[255 + palette[index].B - blue]) < minDistance) {
165                                                         nearestColor = palette[index].ToArgb();
166                                                         minDistance = distance;
167                                                 }
168
169                                         return nearestColor;
170                                 }
171                         }
172
173                         [Flags()]
174                         private enum ItemFlags
175                         {
176                                 None = 0,
177                                 UseTransparentColor = 1,
178                                 ImageStrip = 2
179                         }
180
181                         private sealed class ImageListItem
182                         {
183                                 internal readonly object Image;
184                                 internal readonly ItemFlags Flags;
185                                 internal readonly Color TransparentColor;
186                                 internal readonly int ImageCount = 1;
187
188                                 internal ImageListItem(Icon value)
189                                 {
190                                         if (value == null)
191                                                 throw new ArgumentNullException("value");
192
193                                         // Icons are cloned.
194                                         this.Image = (Icon)value.Clone();
195                                 }
196
197                                 internal ImageListItem(Image value)
198                                 {
199                                         if (value == null)
200                                                 throw new ArgumentNullException("value");
201
202                                         if (!(value is Bitmap))
203                                                 throw new ArgumentException("Image must be a Bitmap.");
204
205                                         // Images are not cloned.
206                                         this.Image = value;
207                                 }
208
209                                 internal ImageListItem(Image value, Color transparentColor) : this(value)
210                                 {
211                                         this.Flags = ItemFlags.UseTransparentColor;
212                                         this.TransparentColor = transparentColor;
213                                 }
214
215                                 internal ImageListItem(Image value, int imageCount) : this(value)
216                                 {
217                                         this.Flags = ItemFlags.ImageStrip;
218                                         this.ImageCount = imageCount;
219                                 }
220                         }
221
222                         #region ImageCollection Private Fields
223                         private ColorDepth colorDepth = DefaultColorDepth;
224                         private Size imageSize = DefaultImageSize;
225                         private Color transparentColor = DefaultTransparentColor;
226                         private ArrayList list = new ArrayList();
227 #if NET_2_0
228                         private ArrayList keys = new ArrayList();
229 #endif
230                         private int count;
231                         private bool handleCreated;
232 #if NET_2_0
233                         private int lastKeyIndex = -1;
234 #endif
235                         private readonly ImageList owner;
236                         #endregion // ImageCollection Private Fields
237
238                         #region ImageCollection Internal Constructors
239                         // For use in ImageList
240                         internal ImageCollection(ImageList owner)
241                         {
242                                 this.owner = owner;
243                         }
244                         #endregion // ImageCollection Internal Constructor
245
246                         #region ImageCollection Internal Instance Properties
247                         // For use in ImageList
248                         internal ColorDepth ColorDepth {
249                                 get {
250                                         return this.colorDepth;
251                                 }
252
253                                 set {
254                                         if (!Enum.IsDefined(typeof(ColorDepth), value))
255                                                 throw new InvalidEnumArgumentException("value", (int)value, typeof(ColorDepth));
256
257                                         if (this.colorDepth != value) {
258                                                 this.colorDepth = value;
259                                                 RecreateHandle();
260                                         }
261                                 }
262                         }
263
264                         // For use in ImageList
265                         internal IntPtr Handle {
266                                 get {
267                                         CreateHandle();
268                                         return (IntPtr)(-1);
269                                 }
270                         }
271
272                         // For use in ImageList
273                         internal bool HandleCreated {
274                                 get {
275                                         return this.handleCreated;
276                                 }
277                         }
278
279                         // For use in ImageList
280                         internal Size ImageSize {
281                                 get {
282                                         return this.imageSize;
283                                 }
284
285                                 set {
286                                         if (value.Width < 1 || value.Width > 256 || value.Height < 1 || value.Height > 256)
287                                                 throw new ArgumentException("ImageSize.Width and Height must be between 1 and 256", "value");
288
289                                         if (this.imageSize != value) {
290                                                 this.imageSize = value;
291                                                 RecreateHandle();
292                                         }
293                                 }
294                         }
295
296                         // For use in ImageList
297                         internal ImageListStreamer ImageStream {
298                                 get {
299                                         return this.Empty ? null : new ImageListStreamer(this);
300                                 }
301
302                                 set {
303                                         int index;
304                                         Image[] streamImages;
305
306                                         if (value == null) {
307 #if NET_2_0
308                                                 if (this.handleCreated)
309                                                         DestroyHandle();
310                                                 else
311                                                         this.Clear();
312 #endif
313                                         }
314                                         // Only deserialized ImageListStreamers are used.
315                                         else if ((streamImages = value.Images) != null) {
316                                                 this.list = new ArrayList(streamImages.Length);
317                                                 this.count = 0;
318                                                 this.handleCreated = true;
319 #if NET_2_0
320                                                 this.keys = new ArrayList(streamImages.Length);
321 #endif
322
323                                                 for (index = 0; index < streamImages.Length; index++) {
324                                                         list.Add((Image)streamImages[index].Clone());
325 #if NET_2_0
326                                                         keys.Add(null);
327 #endif
328                                                 }
329
330                                                 // Invalid ColorDepth values are ignored.
331                                                 if (Enum.IsDefined(typeof(ColorDepth), value.ColorDepth))
332                                                         this.colorDepth = (ColorDepth)value.ColorDepth;
333                                                 this.imageSize = value.ImageSize;
334
335 #if NET_2_0
336                                                 // Event is raised even when handle was not created yet.
337                                                 owner.OnRecreateHandle();
338 #endif
339                                         }
340                                 }
341                         }
342
343                         // For use in ImageList
344                         internal Color TransparentColor {
345                                 get {
346                                         return this.transparentColor;
347                                 }
348
349                                 set {
350                                         this.transparentColor = value;
351                                 }
352                         }
353                         #endregion // ImageCollection Internal Instance Properties
354
355                         #region ImageCollection Public Instance Properties
356                         [Browsable(false)]
357                         public int Count {
358                                 get {
359                                         return this.handleCreated ? list.Count : this.count;
360                                 }
361                         }
362
363                         public bool Empty {
364                                 get {
365                                         return this.Count == 0;
366                                 }
367                         }
368
369                         public bool IsReadOnly {
370                                 get {
371                                         return false;
372                                 }
373                         }
374
375                         [Browsable(false)]
376                         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
377                         public Image this[int index] {
378                                 get {
379                                         return (Image)GetImage(index).Clone();
380                                 }
381
382                                 set {
383                                         Image image;
384
385                                         if (index < 0 || index >= this.Count)
386                                                 throw new ArgumentOutOfRangeException("index");
387
388                                         if (value == null)
389                                                 throw new ArgumentNullException("value");
390
391                                         if (!(value is Bitmap))
392                                                 throw new ArgumentException("Image must be a Bitmap.");
393
394                                         image = CreateImage(value, this.transparentColor);
395                                         CreateHandle();
396                                         list[index] = image;
397                                 }
398                         }
399
400 #if NET_2_0
401                         public Image this[string key] {
402                                 get {
403                                         int index;
404
405                                         return (index = IndexOfKey(key)) == -1 ? null : this[index];
406                                 }
407                         }
408
409                         public StringCollection Keys {
410                                 get {
411                                         int index;
412                                         string key;
413                                         StringCollection keyCollection;
414
415                                         // Returns all keys even when there are more keys than
416                                         // images. Null keys are returned as empty strings.
417
418                                         keyCollection = new StringCollection();
419                                         for (index = 0; index < keys.Count; index++)
420                                                 keyCollection.Add(((key = (string)keys[index]) == null || key.Length == 0) ? string.Empty : key);
421
422                                         return keyCollection;
423                                 }
424                         }
425 #endif
426                         #endregion // ImageCollection Public Instance Properties
427
428                         #region ImageCollection Private Static Methods
429 #if NET_2_0
430                         private static bool CompareKeys(string key1, string key2)
431                         {
432                                 // Keys are case-insensitive and keys with different length
433                                 // are not equal even when string.Compare treats them equal.
434
435                                 if (key1 == null || key2 == null || key1.Length != key2.Length)
436                                         return false;
437
438                                 return string.Compare(key1, key2, true, CultureInfo.InvariantCulture) == 0;
439                         }
440 #endif
441                         #endregion // ImageCollection Private Static Methods
442
443                         #region ImageCollection Private Instance Methods
444 #if NET_2_0
445                         private int AddItem(string key, ImageListItem item)
446 #else
447                         private int AddItem(ImageListItem item)
448 #endif
449                         {
450                                 int itemIndex;
451 #if NET_2_0
452                                 int index;
453 #endif
454
455                                 if (this.handleCreated)
456                                         itemIndex = AddItemInternal(item);
457                                 else {
458                                         // Image strips are counted as a single item in the return
459                                         // value of Add and AddStrip until handle is created.
460
461                                         itemIndex = list.Add(item);
462                                         this.count += item.ImageCount;
463                                 }
464
465 #if NET_2_0
466                                 if ((item.Flags & ItemFlags.ImageStrip) == 0)
467                                         keys.Add(key);
468                                 else
469                                         for (index = 0; index < item.ImageCount; index++)
470                                                 keys.Add(null);
471 #endif
472
473                                 return itemIndex;
474                         }
475
476                         private int AddItemInternal(ImageListItem item)
477                         {
478                                 if (item.Image is Icon) {
479                                         int imageWidth;
480                                         int imageHeight;
481                                         Bitmap bitmap;
482                                         Graphics graphics;
483
484                                         bitmap = new Bitmap(imageWidth = this.imageSize.Width, imageHeight = this.imageSize.Height, PixelFormat.Format32bppArgb);
485                                         graphics = Graphics.FromImage(bitmap);
486                                         graphics.DrawIcon((Icon)item.Image, new Rectangle(0, 0, imageWidth, imageHeight));
487                                         graphics.Dispose();
488
489                                         ReduceColorDepth(bitmap);
490                                         return list.Add(bitmap);
491                                 }
492                                 else if ((item.Flags & ItemFlags.ImageStrip) == 0)
493                                         return list.Add(CreateImage((Image)item.Image, (item.Flags & ItemFlags.UseTransparentColor) == 0 ? this.transparentColor : item.TransparentColor));
494                                 else {
495                                         int imageX;
496                                         int width;
497                                         int imageWidth;
498                                         int imageHeight;
499                                         int index;
500                                         Image image;
501                                         Bitmap bitmap;
502                                         Graphics graphics;
503                                         Rectangle imageRect;
504                                         ImageAttributes imageAttributes;
505
506                                         // When ImageSize was changed after adding image strips
507                                         // Count will return invalid values based on old ImageSize
508                                         // but when creating handle either ArgumentException will
509                                         // be thrown or image strip will be added according to the
510                                         // new ImageSize. This can result in image count
511                                         // difference that can result in exceptions in methods
512                                         // that use Count before creating handle. In addition this
513                                         // can result in the loss of sync with keys. When doing
514                                         // the same after handle was created there are no problems
515                                         // as handle will be recreated after changing ImageSize
516                                         // that results in the loss of images added previously.
517
518                                         if ((width = (image = (Image)item.Image).Width) == 0 || (width % (imageWidth = this.imageSize.Width)) != 0)
519                                                 throw new ArgumentException("Width of image strip must be a positive multiple of ImageSize.Width.", "value");
520
521                                         if (image.Height != (imageHeight = this.imageSize.Height))
522                                                 throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
523
524                                         imageRect = new Rectangle(0, 0, imageWidth, imageHeight);
525                                         if (this.transparentColor.A == 0)
526                                                 imageAttributes = null;
527                                         else {
528                                                 imageAttributes = new ImageAttributes();
529                                                 imageAttributes.SetColorKey(this.transparentColor, this.transparentColor);
530                                         }
531
532                                         index = list.Count;
533                                         for (imageX = 0; imageX < width; imageX += imageWidth) {
534                                                 bitmap = new Bitmap(imageWidth, imageHeight, PixelFormat.Format32bppArgb);
535                                                 graphics = Graphics.FromImage(bitmap);
536                                                 graphics.DrawImage(image, imageRect, imageX, 0, imageWidth, imageHeight, GraphicsUnit.Pixel, imageAttributes);
537                                                 graphics.Dispose();
538
539                                                 ReduceColorDepth(bitmap);
540                                                 list.Add(bitmap);
541                                         }
542
543                                         if (imageAttributes != null)
544                                                 imageAttributes.Dispose();
545
546                                         return index;
547                                 }
548                         }
549
550                         private void CreateHandle()
551                         {
552                                 int index;
553                                 ArrayList items;
554
555                                 if (!this.handleCreated) {
556                                         items = this.list;
557                                         this.list = new ArrayList(this.count);
558                                         this.count = 0;
559                                         this.handleCreated = true;
560
561                                         for (index = 0; index < items.Count; index++)
562                                                 AddItemInternal((ImageListItem)items[index]);
563                                 }
564                         }
565
566                         private Image CreateImage(Image value, Color transparentColor)
567                         {
568                                 int imageWidth;
569                                 int imageHeight;
570                                 Bitmap bitmap;
571                                 Graphics graphics;
572                                 ImageAttributes imageAttributes;
573
574                                 if (transparentColor.A == 0)
575                                         imageAttributes = null;
576                                 else {
577                                         imageAttributes = new ImageAttributes();
578                                         imageAttributes.SetColorKey(transparentColor, transparentColor);
579                                 }
580
581                                 bitmap = new Bitmap(imageWidth = this.imageSize.Width, imageHeight = this.imageSize.Height, PixelFormat.Format32bppArgb);
582                                 graphics = Graphics.FromImage(bitmap);
583                                 graphics.DrawImage(value, new Rectangle(0, 0, imageWidth, imageHeight), 0, 0, value.Width, value.Height, GraphicsUnit.Pixel, imageAttributes);
584                                 graphics.Dispose();
585
586                                 if (imageAttributes != null)
587                                         imageAttributes.Dispose();
588
589                                 ReduceColorDepth(bitmap);
590                                 return bitmap;
591                         }
592
593                         private void RecreateHandle()
594                         {
595                                 if (this.handleCreated) {
596                                         DestroyHandle();
597                                         this.handleCreated = true;
598                                         owner.OnRecreateHandle();
599                                 }
600                         }
601
602                         private unsafe void ReduceColorDepth(Bitmap bitmap)
603                         {
604                                 byte* pixelPtr;
605                                 byte* lineEndPtr;
606                                 byte* linePtr;
607                                 int line;
608                                 int pixel;
609                                 int height;
610                                 int widthBytes;
611                                 int stride;
612                                 BitmapData bitmapData;
613                                 Color[] palette;
614
615                                 if (this.colorDepth < ColorDepth.Depth32Bit) {
616                                         bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
617                                         try {
618                                                 linePtr = (byte*)bitmapData.Scan0;
619                                                 height = bitmapData.Height;
620                                                 widthBytes = bitmapData.Width << 2;
621                                                 stride = bitmapData.Stride;
622
623                                                 if (this.colorDepth < ColorDepth.Depth16Bit) {
624                                                         palette = (this.colorDepth < ColorDepth.Depth8Bit ? IndexedColorDepths.Palette4Bit : IndexedColorDepths.Palette8Bit).Entries;
625
626                                                         for (line = 0; line < height; line++) {
627                                                                 lineEndPtr = linePtr + widthBytes;
628                                                                 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
629                                                                         *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : IndexedColorDepths.GetNearestColor(palette, pixel | AlphaMask);
630                                                                 linePtr += stride;
631                                                         }
632                                                 }
633                                                 else if (this.colorDepth < ColorDepth.Depth24Bit) {
634                                                         for (line = 0; line < height; line++) {
635                                                                 lineEndPtr = linePtr + widthBytes;
636                                                                 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
637                                                                         *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : (pixel & 0x00F8F8F8) | AlphaMask;
638                                                                 linePtr += stride;
639                                                         }
640                                                 }
641                                                 else {
642                                                         for (line = 0; line < height; line++) {
643                                                                 lineEndPtr = linePtr + widthBytes;
644                                                                 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
645                                                                         *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : pixel | AlphaMask;
646                                                                 linePtr += stride;
647                                                         }
648                                                 }
649                                         }
650                                         finally {
651                                                 bitmap.UnlockBits(bitmapData);
652                                         }
653                                 }
654                         }
655                         #endregion // ImageCollection Private Instance Methods
656
657                         #region ImageCollection Internal Instance Methods
658                         // For use in ImageList
659                         internal void DestroyHandle()
660                         {
661                                 if (this.handleCreated) {
662                                         this.list = new ArrayList();
663                                         this.count = 0;
664                                         this.handleCreated = false;
665 #if NET_2_0
666                                         keys = new ArrayList();
667 #endif
668                                 }
669                         }
670
671                         // For use in ImageList
672                         internal Image GetImage(int index)
673                         {
674                                 if (index < 0 || index >= this.Count)
675                                         throw new ArgumentOutOfRangeException("index");
676
677                                 CreateHandle();
678                                 return (Image)list[index];
679                         }
680
681                         // For use in ImageListStreamer
682                         internal Image[] ToArray()
683                         {
684                                 Image[] images;
685
686                                 // Handle is created even when the list is empty.
687                                 CreateHandle();
688                                 images = new Image[list.Count];
689                                 list.CopyTo(images);
690                                 return images;
691                         }
692                         #endregion // ImageCollection Internal Instance Methods
693
694                         #region ImageCollection Public Instance Methods
695                         public void Add(Icon value)
696                         {
697 #if NET_2_0
698                                 Add(null, value);
699 #else
700                                 AddItem(new ImageListItem(value));
701 #endif
702                         }
703
704                         public void Add(Image value)
705                         {
706 #if NET_2_0
707                                 Add(null, value);
708 #else
709                                 AddItem(new ImageListItem(value));
710 #endif
711                         }
712
713                         public int Add(Image value, Color transparentColor)
714                         {
715 #if NET_2_0
716                                 return AddItem(null, new ImageListItem(value, transparentColor));
717 #else
718                                 return AddItem(new ImageListItem(value, transparentColor));
719 #endif
720                         }
721
722 #if NET_2_0
723                         public void Add(string key, Icon icon)
724                         {
725                                 // Argument has name icon but exceptions use name value.
726                                 AddItem(key, new ImageListItem(icon));
727                         }
728
729                         public void Add(string key, Image image)
730                         {
731                                 // Argument has name image but exceptions use name value.
732                                 AddItem(key, new ImageListItem(image));
733                         }
734
735                         public void AddRange(Image[] images)
736                         {
737                                 int index;
738
739                                 if (images == null)
740                                         throw new ArgumentNullException("images");
741
742                                 for (index = 0; index < images.Length; index++)
743                                         Add(images[index]);
744                         }
745 #endif
746
747                         public int AddStrip(Image value)
748                         {
749                                 int width;
750                                 int imageWidth;
751
752                                 if (value == null)
753                                         throw new ArgumentNullException("value");
754
755                                 if ((width = value.Width) == 0 || (width % (imageWidth = this.imageSize.Width)) != 0)
756                                         throw new ArgumentException("Width of image strip must be a positive multiple of ImageSize.Width.", "value");
757
758                                 if (value.Height != this.imageSize.Height)
759                                         throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
760
761 #if NET_2_0
762                                 return AddItem(null, new ImageListItem(value, width / imageWidth));
763 #else
764                                 return AddItem(new ImageListItem(value, width / imageWidth));
765 #endif
766                         }
767
768                         public void Clear()
769                         {
770                                 list.Clear();
771                                 if (this.handleCreated)
772                                         this.count = 0;
773 #if NET_2_0
774                                 keys.Clear();
775 #endif
776                         }
777
778 #if NET_2_0
779                         [EditorBrowsable(EditorBrowsableState.Never)]
780 #endif
781                         public bool Contains(Image image)
782                         {
783                                 throw new NotSupportedException();
784                         }
785
786 #if NET_2_0
787                         public bool ContainsKey(string key)
788                         {
789                                 return IndexOfKey(key) != -1;
790                         }
791 #endif
792
793                         public IEnumerator GetEnumerator()
794                         {
795                                 Image[] images = new Image[this.Count];
796                                 int index;
797
798                                 if (images.Length != 0) {
799                                         // Handle is created only when there are images.
800                                         CreateHandle();
801
802                                         for (index = 0; index < images.Length; index++)
803                                                 images[index] = (Image)((Image)list[index]).Clone();
804                                 }
805
806                                 return images.GetEnumerator();
807                         }
808
809 #if NET_2_0
810                         [EditorBrowsable(EditorBrowsableState.Never)]
811 #endif
812                         public int IndexOf(Image image)
813                         {
814                                 throw new NotSupportedException();
815                         }
816
817 #if NET_2_0
818                         public int IndexOfKey(string key)
819                         {
820                                 int index;
821
822                                 if (key != null && key.Length != 0) {
823                                         // When last IndexOfKey was successful and the same key was
824                                         // assigned to an image with a lower index than the last
825                                         // result and the key of the last result equals to key
826                                         // argument the last result is returned.
827
828                                         if (this.lastKeyIndex >= 0 && this.lastKeyIndex < this.Count && CompareKeys((string)keys[this.lastKeyIndex], key))
829                                                 return this.lastKeyIndex;
830
831                                         // Duplicate keys are allowed and first match is returned.
832                                         for (index = 0; index < this.Count; index++)
833                                                 if (CompareKeys((string)keys[index], key))
834                                                         return this.lastKeyIndex = index;
835                                 }
836
837                                 return this.lastKeyIndex = -1;
838                         }
839 #endif
840
841 #if NET_2_0
842                         [EditorBrowsable(EditorBrowsableState.Never)]
843 #endif
844                         public void Remove(Image image)
845                         {
846                                 throw new NotSupportedException();
847                         }
848
849                         public void RemoveAt(int index)
850                         {
851                                 if (index < 0 || index >= this.Count)
852                                         throw new ArgumentOutOfRangeException("index");
853
854                                 CreateHandle();
855                                 list.RemoveAt(index);
856 #if NET_2_0
857                                 keys.RemoveAt(index);
858 #endif
859                         }
860
861 #if NET_2_0
862                         public void RemoveByKey(string key)
863                         {
864                                 int index;
865
866                                 if ((index = IndexOfKey(key)) != -1)
867                                         RemoveAt(index);
868                         }
869
870                         public void SetKeyName(int index, string name)
871                         {
872                                 // Only SetKeyName throws IndexOutOfRangeException.
873                                 if (index < 0 || index >= this.Count)
874                                         throw new IndexOutOfRangeException();
875
876                                 keys[index] = name;
877                         }
878 #endif
879                         #endregion // ImageCollection Public Instance Methods
880
881                         #region ImageCollection Interface Properties
882                         object IList.this[int index] {
883                                 get {
884                                         return this[index];
885                                 }
886
887                                 set {
888                                         if (!(value is Image))
889                                                 throw new ArgumentException("value");
890
891                                         this[index] = (Image)value;
892                                 }
893                         }
894
895                         bool IList.IsFixedSize {
896                                 get {
897                                         return false;
898                                 }
899                         }
900
901                         bool ICollection.IsSynchronized {
902                                 get {
903                                         return false;
904                                 }
905                         }
906
907                         object ICollection.SyncRoot {
908                                 get {
909                                         return this;
910                                 }
911                         }
912                         #endregion // ImageCollection Interface Properties
913
914                         #region ImageCollection Interface Methods
915                         int IList.Add(object value)
916                         {
917                                 int index;
918
919                                 if (!(value is Image))
920                                         throw new ArgumentException("value");
921
922                                 index = this.Count;
923                                 this.Add((Image)value);
924                                 return index;
925                         }
926
927                         bool IList.Contains(object value)
928                         {
929                                 return value is Image ? this.Contains((Image)value) : false;
930                         }
931
932                         int IList.IndexOf(object value)
933                         {
934                                 return value is Image ? this.IndexOf((Image)value) : -1;
935                         }
936
937                         void IList.Insert(int index, object value)
938                         {
939                                 throw new NotSupportedException();
940                         }
941
942                         void IList.Remove(object value)
943                         {
944                                 if (value is Image)
945                                         this.Remove((Image)value);
946                         }
947
948                         void ICollection.CopyTo(Array array, int index)
949                         {
950                                 int imageIndex;
951
952                                 for (imageIndex = 0; imageIndex < this.Count; imageIndex++)
953                                         array.SetValue(this[index], index++);
954                         }
955                         #endregion // ImageCollection Interface Methods
956                 }
957                 #endregion // Sub-classes
958
959                 #region Public Constructors
960                 public ImageList()
961                 {
962                         images = new ImageCollection(this);
963                 }
964
965                 public ImageList(System.ComponentModel.IContainer container) : this()
966                 {
967                         container.Add(this);
968                 }
969                 #endregion // Public Constructors
970
971                 #region Private Instance Methods
972                 private void OnRecreateHandle()
973                 {
974                         if (this.recreateHandle != null)
975                                 this.recreateHandle(this, EventArgs.Empty);
976                 }
977
978 #if NET_2_0
979                 // For use in Designers
980                 private void ResetColorDepth()
981                 {
982                         this.ColorDepth = DefaultColorDepth;
983                 }
984
985                 // For use in Designers
986                 private void ResetImageSize()
987                 {
988                         this.ImageSize = DefaultImageSize;
989                 }
990
991                 // For use in Designers
992                 private void ResetTransparentColor()
993                 {
994                         this.TransparentColor = DefaultTransparentColor;
995                 }
996
997                 // For use in Designers
998                 private bool ShouldSerializeColorDepth()
999                 {
1000                         // ColorDepth is serialized in ImageStream when non-empty.
1001                         // It is serialized even if it has its default value when empty.
1002                         return images.Empty;
1003                 }
1004
1005                 // For use in Designers
1006                 private bool ShouldSerializeImageSize()
1007                 {
1008                         // ImageSize is serialized in ImageStream when non-empty.
1009                         // It is serialized even if it has its default value when empty.
1010                         return images.Empty;
1011                 }
1012
1013                 // For use in Designers
1014                 private bool ShouldSerializeTransparentColor()
1015                 {
1016                         return this.TransparentColor != DefaultTransparentColor;
1017                 }
1018 #endif
1019                 #endregion // Private Instance Methods
1020
1021                 #region Public Instance Properties
1022 #if !NET_2_0
1023                 [DefaultValue(DefaultColorDepth)]
1024 #endif
1025                 public ColorDepth ColorDepth {
1026                         get {
1027                                 return images.ColorDepth;
1028                         }
1029
1030                         set {
1031                                 images.ColorDepth = value;
1032                         }
1033                 }
1034
1035                 [Browsable(false)]
1036                 [EditorBrowsable(EditorBrowsableState.Advanced)]
1037                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1038                 public IntPtr Handle {
1039                         get {
1040                                 return images.Handle;
1041                         }
1042                 }
1043
1044                 [Browsable(false)]
1045                 [EditorBrowsable(EditorBrowsableState.Advanced)]
1046                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1047                 public bool HandleCreated {
1048                         get {
1049                                 return images.HandleCreated;
1050                         }
1051                 }
1052
1053                 [DefaultValue(null)]
1054                 [MergableProperty(false)]
1055                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1056                 public ImageCollection Images {
1057                         get {
1058                                 return this.images;
1059                         }
1060                 }
1061
1062                 [Localizable(true)]
1063                 public Size ImageSize {
1064                         get {
1065                                 return images.ImageSize;
1066                         }
1067
1068                         set {
1069                                 images.ImageSize = value;
1070                         }
1071                 }
1072
1073                 [Browsable(false)]
1074                 [DefaultValue(null)]
1075                 [EditorBrowsable(EditorBrowsableState.Advanced)]
1076                 public ImageListStreamer ImageStream {
1077                         get {
1078                                 return images.ImageStream;
1079                         }
1080
1081                         set {
1082                                 images.ImageStream = value;
1083                         }
1084                 }
1085
1086 #if NET_2_0
1087                 [Bindable(true)]
1088                 [DefaultValue(null)]
1089                 [Localizable(false)]
1090                 [TypeConverter(typeof(StringConverter))]
1091                 public object Tag {
1092                         get {
1093                                 return this.tag;
1094                         }
1095
1096                         set {
1097                                 this.tag = value;
1098                         }
1099                 }
1100 #endif
1101
1102                 public Color TransparentColor {
1103                         get {
1104                                 return images.TransparentColor;
1105                         }
1106
1107                         set {
1108                                 images.TransparentColor = value;
1109                         }
1110                 }
1111                 #endregion // Public Instance Properties
1112
1113                 #region Public Instance Methods
1114                 public void Draw(Graphics g, Point pt, int index)
1115                 {
1116                         this.Draw(g, pt.X, pt.Y, index);
1117                 }
1118
1119                 public void Draw(Graphics g, int x, int y, int index)
1120                 {
1121                         g.DrawImage(images.GetImage(index), x, y);
1122                 }
1123
1124                 public void Draw(Graphics g, int x, int y, int width, int height, int index)
1125                 {
1126                         g.DrawImage(images.GetImage(index), x, y, width, height);
1127                 }
1128
1129                 public override string ToString()
1130                 {
1131                         return base.ToString() + " Images.Count: " + images.Count.ToString() + ", ImageSize: " + this.ImageSize.ToString();
1132                 }
1133                 #endregion // Public Instance Methods
1134
1135                 #region Protected Instance Methods
1136                 protected override void Dispose(bool disposing)
1137                 {
1138                         if (disposing)
1139                                 images.DestroyHandle();
1140
1141                         base.Dispose(disposing);
1142                 }
1143                 #endregion // Protected Instance Methods
1144
1145                 #region Events
1146                 [Browsable(false)]
1147                 [EditorBrowsable(EditorBrowsableState.Advanced)]
1148                 public event EventHandler RecreateHandle {
1149                         add {
1150                                 this.recreateHandle += value;
1151                         }
1152
1153                         remove {
1154                                 this.recreateHandle -= value;
1155                         }
1156                 }
1157                 #endregion // Events
1158         }
1159 }