5.6 ImageViewer Implementation (Take 3)

 < Day Day Up > 

Let's review the potential functional requirements for the ImageViewer class:

  • Load an image

  • Display an image

  • Crop an image to a particular rectangular "view region"

  • Display a border around the image

  • Display image load progress

  • Reposition the view region

  • Resize the view region

  • Pan (reposition) the image within the view region

  • Zoom (resize) the image within the view region

So far, we've successfully implemented the first four items. In the next version of the ImageViewer class, we'll add the fifth feature: display image load progress. We'll leave the remaining items unimplemented until the situation specifically requires them. (Plot spoiler: in Chapter 7 we'll return to the ImageViewer class to add two more features.)

To implement load-progress display for our ImageViewer class, we'll use the MovieClipLoader class, which was added to Flash Player 7 after a heartfelt petition to Macromedia for improved preloading support ( curious readers can view the petition at http:// moock .org/blog/archives/000010.html).

Our load-progress implementation involves these main changes to the ImageViewer class:

  • Add two new properties:


    imageLoader

    holds a MovieClipLoader instance


    statusDepth

    indicates the depth of a load-progress text field

  • Modify the ImageViewer constructor to first create a MovieClipLoader instance and then register the ImageViewer instance to receive events from it.

  • Modify the loadImage( ) method to:

    • Load the JPEG file using MovieClipLoader instead of loadMovie( )

    • Create a text field in which to display load progress

  • Add three methods onLoadProgress( ) , onLoadInit( ) , and onLoadError( ) to handle MovieClipLoader events.

The preceding design changes might seem like a pretty big leap given that we're adding only one feature (image load progress). Take heart. The code required to add preloading support might seem fairly complex if you're new to it, but luckily, preloading code doesn't vary much from situation to situation. Once you've implemented preloading support a few times, you'll be able to add it to your own classes in one fell swoop, as we've done here.

Let's look at each of the preceding changes in turn . First, here's the code for the new imageLoader and statusDepth properties:

 private var imageLoader:MovieClipLoader; private static var statusDepth:Number = 3; 

Next, here's the modified ImageViewer constructor (additions shown in bold):

 public function ImageViewer (target:MovieClip,                               depth:Number,                               x:Number,                               y:Number,                               w:Number,                               h:Number,                              borderThickness:Number,                              borderColor:Number) {   // Assign property values.   target_mc = target;   containerDepth = depth;   this.borderThickness = borderThickness;   this.borderColor = borderColor;  // Create the  MovieClipLoader  instance and store it in   // the new  imageLoader  property  .  imageLoader = new MovieClipLoader( );   // Register this  ImageViewer  instance to receive events   // from the  MovieClipLoader  instance  .  imageLoader.addListener(this);  // Set up the visual assets for this ImageViewer.   buildViewer(x, y, w, h); } 

Here's the revised loadImage( ) method. Note that even though the entire contents of the method have changed, the method's usage is unaltered, so the class's public API is not affected:

 public function loadImage (URL:String):Void {   // Use the   MovieClipLoader   instance to load the image. This line replaces   // the previous   loadMovie( )   call.   imageLoader.loadClip(URL, container_mc.image_mc);   // Create a load-status text field to show the user load progress.   container_mc.createTextField("loadStatus_txt", statusDepth, 0, 0, 0, 0);   container_mc.loadStatus_txt.background = true;   container_mc.loadStatus_txt.border = true;   container_mc.loadStatus_txt.setNewTextFormat(new TextFormat(                                                "Arial, Helvetica, _sans",                                                10, borderColor, false,                                                false, false, null, null,                                                "right"));   container_mc.loadStatus_txt.autoSize = "left";   // Position the load-status text field.   container_mc.loadStatus_txt._y = 3;   container_mc.loadStatus_txt._x = 3;   // Indicate that the image is loading.   container_mc.loadStatus_txt.text = "LOADING"; } 

Finally, Example 5-6 shows the three methods that handle image loading events: onLoadProgress( ) , onLoadInit( ) , and onLoadError( ) . The onLoadProgress( ) method fires automatically when a portion of the image has arrived. The onLoadInit( ) method fires automatically once the image has loaded completely and the image_mc 's _width and _height properties have been initialized . The onLoadError( ) method fires automatically when a load error such as "File not found" occurs.

The MovieClipLoader class also provides the onLoadStart( ) and onLoadComplete( ) events, which are not required by our ImageViewer class. For details, see Flash's Help under ActionScript Dictionary M MovieClipLoader.

Example 5-6. Image loading event handlers added to version 3 of the ImageViewer class
 public function onLoadProgress (target:MovieClip,                                  bytesLoaded:Number,                                  bytesTotal:Number):Void {   // Display load progress in the on-screen text field.   // Divide   bytesLoaded   and   bytesTotal   by 1024 to get kilobytes.   container_mc.loadStatus_txt.text = "LOADING: "        + Math.floor(bytesLoaded / 1024)       + "/" + Math.floor(bytesTotal / 1024) + " KB"; } public function onLoadInit (target:MovieClip):Void {   // Remove the loading message.   container_mc.loadStatus_txt.removeTextField( );   // Apply the mask to the loaded image. This cleanly replaces the    //   onEnterFrame( )   hack from version 2's   loadImage( )   method in Example 5-5.   container_mc.image_mc.setMask(container_mc.mask_mc); } public function onLoadError (target:MovieClip, errorCode:String):Void {   // Depending on the value of   errorCode   , display an appropriate    // error message in the on-screen text field.   if (errorCode == "URLNotFound") {     container_mc.loadStatus_txt.text = "ERROR: File not found.";   } else if (errorCode == "LoadNeverCompleted") {     container_mc.loadStatus_txt.text = "ERROR: Load failed.";   } else {     // Catch-all to handle possible future error codes.     container_mc.loadStatus_txt.text = "Load error: " + errorCode;   } } 

Version 3 of our ImageViewer class is nearly complete. However, we have one more issue to account for: cleaning up after each ImageViewer instance before it's deleted.

5.6.1 Deleting a Class's Resources

In programming, the fun's not over until you've put your proverbial toys away. Each instance of our ImageViewer class creates movie clip instances that persist in the Flash Player until they are explicitly removed, even if the ImageViewer instance that created them is, itself, deleted! For example, the following code creates an ImageViewer instance, loads an image, and then deletes the ImageViewer instance:

 var viewer:ImageViewer = new ImageViewer(this, 1, 100, 100,                                          250, 250, 10, 0xCE9A3C); viewer.loadImage("picture.jpg"); delete viewer; 

After the code executesdespite the statement delete viewer ;the picture.jpg loads and appears on screen. Why? Because the clips created by the ImageViewer instance were not removed before the variable viewer was deleted.

Leaving movie clips on stage is not the only way the ImageViewer class orphans resources. Under certain circumstances, an ImageViewer instance can also abandon itself in the MovieClipLoader listener list. Recall that when an ImageViewer instance is constructed , it registers itself as an imageLoader listener using imageLoader.addListener(this) . As a result, each ImageViewer instance is stored in its own imageLoader 's list of listener objects. After this line of code executes:

 var viewer:ImageViewer = new ImageViewer(this, 1); 

there are actually two references to the ImageViewer instance: one in viewer and the other in viewer.imageLoader 's list of listener objects. If the viewer instance is deleted during a load operation, the instance in viewer.imageLoader 's listener list will live on.

Of course, you might naturally expect that deleting viewer would also delete viewer.imageLoader and, consequently, viewer.imageLoader 's list of listener objects. In general, that would be true, but the MovieClipLoader class presents a special case: after a MovieClipLoader instance starts a loadClip( ) operation, the instance (along with its list of listener objects) is kept alive internally by the Flash Player until either the operation completes or the movie clip targeted by the load operation is removed. For example, in the following code, the mcl instance is kept in memory until level1.swf has finished loading into box :

 var mcl:MovieClipLoader = new MovieClipLoader( ); mcl.loadClip("level1.swf", box); delete mcl; 

Hence, even though we can successfully delete all external references to an ImageViewer instance, the instance may still exist internally in its own imageLoader 's listener list!

Each ImageViewer instance, therefore, should clean up its resources before it is deleted. We perform the cleanup in a custom method: destroy( ) . The destroy( ) method takes no parameters and must be invoked before an ImageViewer instance is deleted. The source code for destroy( ) is simple but also critical and imperative. It removes the ImageViewer instance from the imageLoader 's listener list and deletes the hierarchy of movie clips that display the image on screen (thus halting any load in progress):

 public function destroy ( ):Void {   // Cancel load event notifications   imageLoader.removeListener(this);   // Remove movie clips from Stage (removing   container_mc   removes subclips)   container_mc.removeMovieClip( ); } 

Now, to delete any ImageViewer instance, we first invoke destroy( ) , as follows :

 // Clean up the instance's resources viewer.destroy( ); // Delete the instance delete viewer; 

Incidentally, the name "destroy" is arbitrary. We could have synonymously used "die," "kill," or "remove" with the same effect.

Finally, it's worth noting that the imageLoader listener list issue is not a particularly unique situation. Any time an object is registered as a listener of another object, it must be unregistered before being deleted. Example 5-7 shows the danger of not unregistering an object before deleting it.

Example 5-7. A lost listener
 var obj:Object = new Object( ); obj.onMouseDown = function ( ):Void {   trace("The mouse was pressed."); } Mouse.addListener(obj); delete obj; 

Try the following:

  1. Place the Example 5-7 code on frame 1 of a movie.

  2. Run the movie in Test Movie mode (Control Test Movie).

  3. obj was deleted! The reference to the object in the variable obj may have been deleted, but another reference continues to exist in the Mouse object's list of listener objects. To safely delete the object obj , first unregister it as a Mouse listener, as follows:
     Mouse.removeListener(obj); delete obj; 

    This section discussed one specific case in which an object reference might inadvertently persist after deleting an instance of a class. Failure to free up resources results in memory waste, which over time could hinder performance or cause an application to fail. When you write a class, you should include a cleanup routine that a programmer can call before deleting an instance. Think carefully about which resources need to be freed. Don't simply avoid deleting instances as a way to avoid orphaning resources. You should delete instances when they are no longer needed and make sure that you free all resources before doing so. Remember: anything your code creates, it should also eventually destroy.


    5.6.2 The Final ImageViewer Code

    Example 5-8 shows the final code for version 3 of the ImageViewer class. Update your code in ImageViewer.as to match the example. For usage instructions, refer to the earlier section "Using ImageViewer (Take 2)" (nothing was added to the class's public API between version 2 and version 3, so its use has not changed). If you have any trouble getting things running properly, you can download finished source files for all three versions of the ImageViewer example from http://moock.org/eas2/examples.

    The download progress for a loading image will not display when the image is loaded off a local hard disk. To test your imageViewer.swf , be sure to post your images to a web server and play the movie in a web browser.


    We'll return to the ImageViewer class again in Chapter 7.

    Example 5-8 uses the JavaDoc commenting style to document the class's methods and constructor function. In Java, when comments are formatted according to JavaDoc conventions, automatic HTML documentation can be generated from a class source file. Unfortunately, at the time of this writing, Flash does not support JavaDoc directly, but a third-party tool or Flash itself could conceivably add support in the future. Either way, the JavaDoc style is a common, easy-to-follow convention that can greatly improve source code readability. For reference, the JavaDoc conventions used in Example 5-8 are:


    @author

    The author(s) of the class


    @version

    The version of the class


    @param

    A method or constructor parameter name and purpose

    For more information on JavaDoc, see:

    http://java.sun.com/j2se/javadoc
    http://java.sun.com/j2se/javadoc/writingdoccomments
    http://java.sun.com/j2se/1.4.2/docs/tooldocs/ windows /javadoc.html#javadoctags

    Example 5-8 shows our ImageViewer class to date.

    Example 5-8. The ImageViewer class, version 3
     /**  * ImageViewer, Version 3.  * An on-screen rectangular region for displaying a loaded image.  * Updates at:   http://moock.org/eas2/examples/   *  * @author: Colin Moock  * @version: 3.0.0  */ class ImageViewer {   // The movie clip that will contain all   ImageViewer   assets   private var container_mc:MovieClip;   // The movie clip to which   container_mc   will be attached   private var target_mc:MovieClip;   // Depths for visual assets   private var containerDepth:Number;     private static var imageDepth:Number = 0;   private static var maskDepth:Number = 1;   private static var borderDepth:Number = 2;   private static var statusDepth:Number = 3;   // The thickness of the border around the image   private var borderThickness:Number;   // The color of the border around the image   private var borderColor:Number;   // The   MovieClipLoader   instance used to load the image   private var imageLoader:MovieClipLoader;   /**    * ImageViewer Constructor    *    * @param   target           The movie clip to which the    *   ImageViewer   will be attached.    * @param   depth            The depth in   target   on which to     *                            attach the viewer.    * @param   x                The horizontal position of the viewer.    * @param   y                The vertical position of the viewer.    * @param   w                The width of the viewer, in pixels.    * @param   h                The height of the viewer, in pixels.    * @param   borderThickness  The thickness of the image border.    * @param   borderColor      The color of the image border.    *                       */   public function ImageViewer (target:MovieClip,                                 depth:Number,                                 x:Number,                                 y:Number,                                 w:Number,                                 h:Number,                                borderThickness:Number,                                borderColor:Number) {     // Assign property values     target_mc = target;     containerDepth = depth;     this.borderThickness = borderThickness;     this.borderColor = borderColor;     imageLoader = new MovieClipLoader( );     // Register this instance to receive events     // from the   imageLoader   instance     imageLoader.addListener(this);     // Set up the visual assets for this   ImageViewer   buildViewer(x, y, w, h);   }   /**    * Creates the on-screen assets for this   ImageViewer   .    * The movie clip hierarchy is:    *  [d]: container_mc    *         2: border_mc    *         1: mask_mc (masks image_mc)    *         0: image_mc    * where [d] is the user-supplied depth passed to the constructor.    *    * @param   x   The horizontal position of the viewer.    * @param   y   The vertical position of the viewer.    * @param   w   The width of the viewer, in pixels.    * @param   h   The height of the viewer, in pixels.    */   private function buildViewer (x:Number,                                  y:Number,                                  w:Number,                                  h:Number):Void {       // Create the clips to hold the image, mask, and border       createMainContainer(x, y);       createImageClip( );       createImageClipMask(w, h);       createBorder(w, h);   }   /**    * Creates a movie clip,   container_mc   , to contain     * the   ImageViewer   visual assets.    *    * @param   x   The horizontal position of the     *              container_mc movie clip.    * @param   y   The vertical position of the     *              container_mc movie clip.    */   private function createMainContainer (x:Number, y:Number):Void {     container_mc = target_mc.createEmptyMovieClip(                                            "container_mc" + containerDepth,                                             containerDepth);     container_mc._x = x;     container_mc._y = y;   }   /**    * Creates the clip into which the image is actually loaded    */   private function createImageClip ( ):Void {     container_mc.createEmptyMovieClip("image_mc", imageDepth);   }   /**    * Creates the mask over the image. Note that this method does    * not actually apply the mask to the image clip because a clip's    * mask is lost when new content is loaded into it. Hence, the mask    * is applied from   onLoadInit( )   .    *    * @param   w   The width of the mask, in pixels.    * @param   h   The height of the mask, in pixels.    */   private function createImageClipMask (w:Number,                                         h:Number):Void {     // Create the mask only if a valid width and height are specified     if (!(w > 0 && h > 0)) {       return;     }     // In the container, create a clip to act as the mask over the image     container_mc.createEmptyMovieClip("mask_mc", maskDepth);     // Draw a rectangle in the mask     container_mc.mask_mc.moveTo(0, 0);     container_mc.mask_mc.beginFill(0x0000FF);  // Use blue for debugging     container_mc.mask_mc.lineTo(w, 0);     container_mc.mask_mc.lineTo(w, h);     container_mc.mask_mc.lineTo(0, h);     container_mc.mask_mc.lineTo(0, 0);     container_mc.mask_mc.endFill( );        // Hide the mask (it will still function as a mask when invisible)     container_mc.mask_mc._visible = false;    }   /**    * Creates the border around the image.    *    * @param   w        The width of the border, in pixels.    * @param   h        The height of the border, in pixels.    */   private function createBorder (w:Number,                                  h:Number):Void {     // Create the border only if a valid width and height are specified.     if (!(w > 0 && h > 0)) {       return;     }     // In the container, create a clip to hold the border around the image     container_mc.createEmptyMovieClip("border_mc", borderDepth);        // Draw a rectangular outline in the border clip, with the     // specified dimensions and color     container_mc.border_mc.lineStyle(borderThickness, borderColor);     container_mc.border_mc.moveTo(0, 0);     container_mc.border_mc.lineTo(w, 0);     container_mc.border_mc.lineTo(w, h);     container_mc.border_mc.lineTo(0, h);     container_mc.border_mc.lineTo(0, 0);   }   /**    * Loads a JPEG file into the image viewer.    *    * @param   URL   The local or remote address of the image to load.    */   public function loadImage (URL:String):Void {     imageLoader.loadClip(URL, container_mc.image_mc);     // Create a load-status text field to show the user load progress     container_mc.createTextField("loadStatus_txt", statusDepth, 0, 0, 0, 0);     container_mc.loadStatus_txt.background = true;     container_mc.loadStatus_txt.border = true;     container_mc.loadStatus_txt.setNewTextFormat(new TextFormat(                                                  "Arial, Helvetica, _sans",                                                  10, borderColor, false,                                                  false, false, null, null,                                                  "right"));     container_mc.loadStatus_txt.autoSize = "left";     // Position the load-status text field     container_mc.loadStatus_txt._y = 3;     container_mc.loadStatus_txt._x = 3;     // Indicate that the image is loading     container_mc.loadStatus_txt.text = "LOADING";   }   /**    *   MovieClipLoader   handler. Triggered by   imageLoader   when data arrives.    *     * @param   target        A reference to the movie clip for which     *                        progress is being reported.    * @param   bytesLoaded   The number of bytes of target     *                        that have loaded so far.    * @param   bytesTotal    The total size of target, in bytes.    */   public function onLoadProgress (target:MovieClip,                                    bytesLoaded:Number,                                    bytesTotal:Number):Void {     container_mc.loadStatus_txt.text = "LOADING: "          + Math.floor(bytesLoaded / 1024)         + "/" + Math.floor(bytesTotal / 1024) + " KB";   }   /**    *   MovieClipLoader   handler. Triggered by   imageLoader   when loading is done.    *     * @param   target   A reference to the movie clip for which     *                   loading has finished.    */   public function onLoadInit (target:MovieClip):Void {     // Remove the loading message     container_mc.loadStatus_txt.removeTextField( );     // Apply the mask to the loaded image     container_mc.image_mc.setMask(container_mc.mask_mc);   }   /**    *   MovieClipLoader   handler. Triggered by   imageLoader   when loading fails.    *    *     * @param   target   A reference to the movie clip for which     *                   loading failed.    * @param   errorCode   A string stating the cause of the load failure.    */   public function onLoadError (target:MovieClip, errorCode:String):Void {     if (errorCode == "URLNotFound") {       container_mc.loadStatus_txt.text = "ERROR: File not found.";     } else if (errorCode == "LoadNeverCompleted") {       container_mc.loadStatus_txt.text = "ERROR: Load failed.";     } else {       // Catch-all to handle possible future error codes       container_mc.loadStatus_txt.text = "Load error: " + errorCode;     }   }   /**    * Must be called before the   ImageViewer   instance is deleted.    * Gives the instance a chance to destroy any resources it has created.    */   public function destroy ( ):Void {     // Cancel load event notifications     imageLoader.removeListener(this);     // Remove movie clips from the Stage     container_mc.removeMovieClip( );   } } 

 < Day Day Up > 


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

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