Adding for-in Enumeration Functionality to a Delphi Class


The for-in loop is, by default, supported by arrays, strings, sets, and the following classes: TList, TCollection, TStrings, TInterfaceList, TComponent, TMenuItem, TCustomActionList, TFields, TListItems, TTreeNodes, and TToolbar.

To use a class with the for-in loop, you first need to create a collection class, that is, a class like the TList component or the TImageCache component that has items that we can loop through. This section uses the TImageCache component we created in Chapter 26 to illustrate how to implement for-in enumeration functionality in a class. When you're done, you (and other developers) will be able to use the for-in loop to browse through the Images property of the TImageCache class.

To use a class with the for-in loop, you need to create another class, an enumerator class, which is used by the for-in loop to loop through the main class. In this case, the TImageCacheEnumerator enumerator class is created for the main TImageCache class:

 TImageCacheEnumerator = class(TObject)  end;  TImageCache = class(TComponent)  ... end;

At this stage, these classes are completely unrelated. One of the easiest ways to link the enumerator class with the main class is to create a custom constructor that accepts an instance of the main class. Here's how you can link the enumerator and the main class:

  TImageCacheEnumerator = class(TObject)   private     FImageCache: TImageCache;   public     constructor Create(AImageCache: TImageCache);   end;   TImageCache = class(TComponent)   end; implementation constructor TImageCacheEnumerator.Create(AImageCache: TImageCache); begin   inherited Create;        // call TObject's constructor   // link the enumerator with a TImageCache instance   FImageCache := AImageCache; end;

If you try to compile the code now, you'll receive an undeclared identifier error in the FImageCache: TImageCache line. This error occurs because the TImageCache class is declared after the TImageCacheEnumerator class. It cannot be resolved by declaring the TImageCacheEnumerator class after the TImageCache class because the TImageCache class also has to reference the TImageCacheEnumerator class. If you declare the TImageCacheEnumerator class after the TImageCache, you'll later receive an error that will tell you the TImageCacheEnumerator identifier is undeclared.

When two classes are mutually dependent, like the TImageCache and TImageCacheEnumerator classes are, you have to make a forward class declaration. When you create a forward class declaration, mutually dependent classes are possible and the code successfully compiles because the identifier is no longer undeclared.

The syntax of a forward class declaration is extremely simple:

type TClassName = class; 

So, to enable the TImageCacheEnumerator class to have a TImageCache field, create a forward declaration of the TImageCache class, as shown in Listing 30-1.

Listing 30-1: Forward class declaration

image from book
type   { forward declaration of the TImageCache class }   TImageCache = class;   TImageCacheEnumerator = class(TObject)   private     FImageCache: TImageCache;   public     constructor Create(AImageCache: TImageCache);   end;   TImageCache = class(TComponent)   end;
image from book

The only thing the main class must have in order to be used by the for-in loop is the public GetEnumerator method that returns an instance of the enumerator class. When the for-in loop begins, it calls the GetEnumerator method of the object to acquire an instance of the enumerator class and then uses the methods of the enumerator object to loop through the main object.

In the case of the TImageCache class, the GetEnumerator method has to return an instance of the TImageCacheEnumerator class (see Listing 30-2). The for-in loop will then use the TImageCacheEnumerator object to loop through the Images property of the TImageCache object (this isn't implemented yet).

Listing 30-2: Instantiating the enumerator class in the GetEnumerator method

image from book
  TImageCache = class(TComponent)   public     { Public declarations }     function GetEnumerator: TImageCacheEnumerator;   end; implementation function TImageCache.GetEnumerator: TImageCacheEnumerator; begin   // passing Self links the TImageCache instance with   // the enumerator object used by the for-in loop   Result := TImageCacheEnumerator.Create(Self); end;
image from book

Although the enumerator and the main class are now linked, the enumerator class is pretty much worthless to the for-in loop. In order to be successfully used by the for-in loop, an enumerator class must have two things: a public read-only property called Current that provides access to the currently selected element in the collection, and a public function called MoveNext, which must advance the loop to the next element in the collection and return a Boolean value to tell the loop whether or not the end of the collection is reached.

Listing 30-3 shows the complete and thoroughly commented TImageCacheEnumerator class.

Listing 30-3: The TImageCacheEnumerator class

image from book
type   { forward declaration of the TImageCache class }   TImageCache = class;   TImageCacheEnumerator = class(TObject)   private     FImageCache: TImageCache;     FIndex: Integer;     function GetCurrent: TBitmap;   public     constructor Create(AImageCache: TImageCache);     function MoveNext: Boolean;     property Current: TBitmap read GetCurrent;   end;   TImageCache = class(TComponent)   public     { Public declarations }     function GetEnumerator: TImageCacheEnumerator;   end; implementation constructor TImageCacheEnumerator.Create(AImageCache: TImageCache); begin   inherited Create;        // call TObject's constructor   // link the enumerator with a TImageCache instance   FImageCache := AImageCache;   // enumerators are always initialized to -1   FIndex := -1; end; function TImageCacheEnumerator.MoveNext: Boolean; begin   // if the currently selected image is not the last one,   // return True and move to the next image   Result := FIndex < Pred(FImageCache.Count);   if Result then Inc(FIndex); end; function TImageCacheEnumerator.GetCurrent: TBitmap; begin   // return the currently selected image from the Images property   Result := FImageCache.Images[FIndex]; end; // ---------------------------------------------------------------------------- function TImageCache.GetEnumerator: TImageCacheEnumerator; begin   // passing Self links the TImageCache instance with   // the enumerator object used by the for-in loop   Result := TImageCacheEnumerator.Create(Self); end;
image from book

Now that we've created the enumerator class for the TImageCache component, we need to test it to see if it works. Let's test the for-in loop by adding an Export Images option to the Cache Viewer application that was built in Chapter 26.

The Export Images option needs to allow the user to select a directory and then export all images from the cache file to the selected directory. To allow users to select a directory, you should use the Browse for Folder dialog box.

image from book
Figure 30-1: The Browse for Folder dialog box

To display the Browse for Folder dialog box in a Delphi application, add a TBrowseForFolder standard action to a TActionList or a TActionManager component. The Execute method of the action displays the dialog box, and if the user presses OK to select a directory, the selected directory is stored in the action's Folder property and the OnAccess event is fired. So, to export the images from an image cache file, add the TBrowseForFolder action to the Cache Viewer application and then write the export code in the action's OnAccess event (see Listing 30-4). The result of the export code is displayed in Figure 30-2.

image from book
Figure 30-2: Images exported using the for-in loop and the TImageCacheEnumerator class

Listing 30-4: Exporting images

image from book
procedure TMainForm.BrowseForFolderActionAccept(Sender: TObject); var   folder: string;   image: TBitmap;   prefix: string;   startIndex: Integer; const   FILENAME = '%s%d.bmp'; begin   folder := BrowseForFolderAction.Folder;   { if the final "\" is missing, add it }   if folder[Length(folder)] <> '\' then     folder := folder + '\';   startIndex := 100;   prefix := 'Image';   for image in ImageCache do   begin     image.SaveToFile(folder + Format(FILENAME, [prefix, startIndex]));     Inc(startIndex);   end;   MessageDlg('Finished!', mtInformation, [mbOK], 0); end;
image from book



Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net