dcsimg
May 23, 2017
Hot Topics:

MultiApplet: Running Multiple Applets Inside an Applet Window

  • March 21, 2000
  • By Greg Travis
  • Send Email »
  • More Articles »

What, exactly, is an applet? A very rough definition might be "a Java program that runs inside a browser". However, there are definite differences between an applet and an unadorned, command-line Java application. Applets are inherently graphical; they are started from their

init()
method rather than their
main()
method; and so on.

In this article, we'll be creating an applet that contains other applets running inside of it. In doing so, we'll be exploring, in detail, the context in which an applet runs.

Practically speaking, it's not really interesting to simply run several applets inside one applet; after all, it's easier to just create a single Web page with several applets running within it. However, the techniques described here can be used to implement something more sophisticated—you could build an applet browser on top of the simple
AppletViewer
supplied with your Java Development Kit, or a custom in-browser applet debugger. In any case, you'll learn more about how applets work, and how they interact with their environments.

Overview

We'll be creating a class called
MultiApplet
. A multiapplet is an applet that contains other applets. The individual applets run as if they were being run normally, directly inside a browser window. However, they will be running in the artificial context of our multiapplet.

We'll demonstrate the multiapplet by creating a simple test example, one that simply loads several instances of an applet and displays them simultaneously in a single applet window.

In our examples, we'll be creating a multiapplet called

MultiAppletTest
, and we'll be stuffing several instances of
SimpleApplet
into it.
SimpleApplet
is a trivial applet that displays a graphical pattern that can be manipulated by the mouse.

Bringing a subapplet into the multiapplet is a two-step process:

  1. Create an instance of the subapplet
  2. Place it within the multiapplet window

We'll make the first step easy. To instantiate a new instance of SimpleApplet, you call

MultiApplet.addApplet()
:


  Applet applet = addApplet( "SimpleApplet" );  

After doing this, we have a new instance of our subapplet, but we can't see it until we display it in the window. The most sensible thing to do now is to stick it into the main window, using a layout manager. We'll use

GridLayoutManager
, as follows:


  setLayout( new GridLayout( 2, 2 ) );
  Applet applet = addApplet( "SimpleApplet" );  
  add( "1", applet );

Contexts and Stubs

So far, this all seems pretty easy -- not really much more complicated than placing one abstract windowing toolkit component inside another AWT component.

However, if you were to load a complicated applet into our multiapplet, you might find that it didn't work the way it did on its own. It might not work at all. Clearly, there's more to it than what we've already done.

An applet expects its context to supply a certain set of services. These services are defined through a pair of interfaces called

AppletContext
and
AppletStub
. (These interfaces are part of the
java.applet
package.)

Among other things, an

AppletContext
provides methods for loading images and audio clips across the Net from the applet's site. It also allows you to instruct the browser to load a new Web page. An
AppletContext
can be thought of as a set of services that an applet developer might want to make use of when writing their code.

An

AppletStub
, among other things, provides a way to get the codebase and documentbase of the applet—these are the URLs pointing to the applet's code, and the document containing the applet, respectively. It also provides access to the applet parameter tags that were specified in the HTML applet tag.

Both of these objects provide contextual services, but they are used in rather different ways. For one thing, an

AppletContext
is kind of a public thing—an applet can ask the system for the context using the
Applet.getAppletContext()
method. (In fact, any other class can ask for an applet's context, too.) On the other hand, an AppletStub is a hidden thing—you can't actually get the stub object from the system. It's like hidden glue used to connect the applet to the services in the browser or applet viewer.

In most applet programming, however, you don't really need to think about either of these objects. In fact, it's quite possible that you've done your share of applet programming without ever explicitly using either of these objects. Most of the services they provide are accessed via the applet itself; the code inside

Applet
takes care of calling either the
AppletContext
or
AppletStub
for you.

The Code

For example, here's the implementation of
Applet.getCodeBase()
from
Applet.java
in the Java source:


  public URL getCodeBase() {
    return stub.getCodeBase();  
  }

See?

Applet
itself doesn't know how to get the codebase—it has to ask the surrounding system, via the stub. Correspondingly, here's the implementation of
Applet.showImage()
:


  public Image getImage(URL url) {
    return getAppletContext().getImage(url);  
  }

Note, finally, that the stub itself supplies the

AppletContext
. Again, from
Applet.java
:


  public AppletContext getAppletContext() {  
    return stub.getAppletContext();
  }

In creating a multiapplet, we're going to have to create an artificial context that will provide all of these services. This isn't nearly as grueling as it sounds, however, since the multiapplet, itself, has been provided these very services by the browser. That is, while the subapplets are running inside our multiapplet, the multiapplet is running inside a proper context supplied by the browser. This context can be used to provide contexts for the subapplets. In most cases, it's as simple as forwarding a request from the subapplet to the

AppletContext
or
AppletStub
belonging to the multiapplet.

Remember that some services are provided by the stub, and some by the context. To deal with the stub services, we'll create an object called

MultiAppletStub
. A
MultiAppletStub
contains a reference to our multiapplet, so that it can forward requests to it.

The

MultiAppletStub
object implements the many methods of the
AppletStub
interface, but, in most cases, it just forwards the call to the applet:


  public URL getDocumentBase() {
    return applet.getDocumentBase();  
  }

The chaining of the method calls here is kind of tricky. Let's say our subapplet calls

getDocumentBase()
. Here's what happens:

  1. subapplet.getDocumentBase()
    calls
    MultiAppletStub.getDocumentBase()
  2. MultiAppletStub.getDocumentBase()
    calls
    MultiApplet.getDocumentBase()
  3. MultiApplet.getDocumentBase()
    calls
    [MultiApplet's stub].getDocumentBase()

It's hairy, but it works. Steps 1 and 3 are calls made by code within

Applet.java
; step 2 is implemented by the code fragment within MultiAppletStub (shown above). Step 2 is the little trick we use to make our real context serve as a kind of virtual context for the subapplets.

It's easier to implement the forwarding of requests to

AppletContext
. In this case, we can actually get our hands on the
AppletContext
object supplied to our multiapplet, so we just let the subapplets use that same object by having our
AppletStub
provide that object. Thus, we don't have to create a class called
MultiAppletContext
.

Here's the implementation of

MultiAppletStub.getAppletContext()
:


  public AppletContext getAppletContext() {  
    return applet.getAppletContext();
  }

Here's the chaining that goes on for a call to

getImage()
:

  1. subapplet.getImage()
    calls
    subapplet.getAppletContext()
  2. subapplet.getAppletContext()
    calls
    MultiAppletStub.getContext()
  3. MultiAppletStub.getContext()
    calls
    MultiApplet.getContext()
  4. MultiApplet.getContext()
    returns a browser-supplied context object
  5. subapplet.getImage()
    calls
    [context
    object].getImage()

That's even hairier, but at least we didn't have to create a

MultiAppletContext
class full of forwarding methods. And all we had to do was implement
MultiAppletStub.getAppletContext()
.

Handling Many Subapplets

MultiApplet
maintains a list of applets that have been loaded. Here's the implementation of
addApplet()
:


  public void addApplet( Applet applet ) {
    // Create a new, "artificial" stub for the subapplet  
    AppletStub stub = new MultiAppletStub( this );
    // Give it to the subapplet to use
    applet.setStub( stub );
    // Start up the subapplet
    applet.init();
    // Finally, store it in our list
    applets.addElement( applet );
  }

Note that this version of

addApplet
takes an
Applet
object as an argument. But we'll prefer to use the one that just takes a name:


public Applet addApplet( String className ) 
    throws ClassNotFoundException, IllegalAccessException,
           InstantiationException {
  // Create an instance of the named applet
  Class clas = Class.forName( className );
  Applet applet = (Applet)clas.newInstance();
  // Add it to the system
  addApplet( applet );
  // Return it, in case the caller wants to access the Applet  
  // object directly
  return applet;
}

This method throws a whole bunch of exceptions related to the act of creating an instance of an object from its name; you'll have to catch them when you use this method.

Other issues

Another important feature of an applet's environment consists of the execution control methods:
init() start(), stop(), and
destroy()
.

Each of these methods is called for you, automatically, by the browser environment. (You can also call them yourself, but that's not usually done.)

start()
and
stop()
are used to suspend and resume execution when a browser is directed away from the page containing the applet. Generally, this doesn't cause the applet to be removed from the system, but merely suspended.

init()
is called once when the applet is first started, and
destroy()
is called once when the applet is about to be removed from the system (such as when the containing browser window is closed). Since many applets have important initialization/cleanup code in these methods, it's essential that our multiapplet system attend to these functions.

In the case of

init()
, we make sure to call that as soon as the subapplet is loaded, from within
MultiApplet.addApplet()
.

For the others, we implement as follows:


public void start() {
 for (Enumeration e=applets.elements(); e.hasMoreElements();) {  
   ((Applet)e.nextElement()).start();
  }
}

That is, when the browser asks our multiapplet to start, the multiapplet, in turn, asks all the subapplets to start. The same goes for

stop()
and
destroy()
.

The Test Program

Our test program is an applet called
MultiAppletTest
that contains four instances of
SimpleApplet
. Here's how it goes:


public void init() {
  try {
    // Set up a layout that allows us to see all the subapplets
    setLayout( new GridLayout( 2, 2 ) );

    Applet applet;

    // Create four subapplets, and put them on-screen
    applet = addApplet( "SimpleApplet" );
    add( "1", applet );
    applet = addApplet( "SimpleApplet" );
    add( "2", applet );
    applet = addApplet( "SimpleApplet" );
    add( "3", applet );
    applet = addApplet( "SimpleApplet" );
    add( "4", applet );

    // There are several exceptions thrown by addApplet(); we'll  
    // just do the easy thing and catch everything here:
  } catch( Exception e ) {
    System.out.println( e );
  }
}

This code just sets up a 2x2 grid of areas for the subapplets to run in. Each applet is loaded and placed in one of the four slots.

Trying Out the Code

First, check out SimpleApplet . Click and drag in the window to see the image change. Then, try out MultiApplet , to see several instances of
SimpleApplet
running inside a single applet window. You can play around with each of the applets separately.

Getting Wackier

Just for fun, let's try running an instance of
MultiApplet
inside another instance of
MultiApplet
. This will lengthen the chain of calls used to implement the applet services, but we've taken care of that, and it should work correctly. It's easy to do—instead of


  applet = addApplet( "SimpleApplet" );  
  add( "2", applet );

we do this:


  applet = addApplet( "MultiAppletTest" );
  add( "2", applet );

This class is called

MetaMultiAppletTest
and you can try it out here .

Conclusion

By creating our artificial applet contexts and stubs, we've been able to provide a context for subapplets to work correctly in. We can be reasonably certain that most applets will work, unchanged, within our context (but see the next section for some possible problems with this).

Further Work

You might have noticed that
MultAppletStub.appletResize()
doesn't forward itself to anywhere. We've just ignored it. We did this because it's not clear how, in general, we should handle the sizing and layout of the subapplets. This depends crucially on how we lay the applets out. In our example, we used
GridLayout
, but this is by no means the best or only way to deal with this. There's no straightforward solution to this problem, because it touches on the one way in which our "simulated context" isn't identical to a real context: we are running as only one part of a larger applet window.

There are a few other things that could be improved to make it as easy as possible to move an applet from a real context to our simulated context. Applet parameters are a good example. Each applet can have its own set of parameters passed in from the HTML page. If we just stuffed all the subapplets' parameters into a single multiapplet tag, we might find some name clashes between the parameter sets.

One solution for dealing with this would be to give each subapplet a name within the context of the multiapplet. Then, each parameter name could be prefixed by this subapplet name, as follows:


  <param name="foo" value="20">  

becomes


  <param name="applet10-foo" value="20">  

where

"applet10"
is the name we've assigned to one of our subapplets. This solves the name conflict problem.

To make this even better, we could automate the process of stripping the applet name from the parameter name. Our subapplet could call

getParameter<!-- Changed getParamter to getParameter. -TM -->( "foo" )
, and
MultiAppletStub.getParameter
would look up the name of the requesting applet and return the value of
MultiApplet.getParameter<!-- Changed getParamter to getParameter. -TM -->( "applet10-foo" )
. This would allow our subapplets to run unchanged—the subapplets wouldn't have to know what their assigned names were.

Similar considerations could be contemplated for

getApplets()
.

In our example, we hardcoded the names and screen positions of each subapplet. There's no reason that these couldn't be specified as applet tags; then, you could have different instances of your multiapplet containing different subapplets without actually having to change the code.

One final thing to watch out for: avoid calling

init()
too early—if the multiapplet hasn't actually been plugged into the system, then it may not have access to stubs or contexts, and it may not be able to load images or audio clips. The way we've set things up above, it's fine, since everything starts from
MultiApplet.init()
, but if our multiapplet were created in a non-standard way (via
newInstance()
for example), then it might not have been properly initialized yet.

Source

MetaMultiAppletTest.java

// $Id$

import java.applet.*;
import java.awt.*;
import java.net.*;

public class MetaMultiAppletTest extends MultiApplet
{
  public void init() {
    try {
	  setLayout( new GridLayout( 2, 2 ) );
	  Applet applet = addApplet( "SimpleApplet" );
	  add( "1", applet );
	  applet = addApplet( "MultiAppletTest" );
	  add( "2", applet );
	  applet = addApplet( "MultiAppletTest" );
	  add( "3", applet );
	  applet = addApplet( "SimpleApplet" );
	  add( "4", applet );
    } catch( Exception e ) {
      System.out.println( e );
    }
  }
}

MultiApplet.java


// $Id$

import java.applet.*;
import java.net.*;
import java.util.*;

public class MultiApplet extends Applet
{
  public Vector applets = new Vector();

  public void addApplet( Applet applet ) {
    // Create a new, "artificial" stub for the subapplet
    AppletStub stub = new MultiAppletStub( this );
    // Give it to the subapplet to use
    applet.setStub( stub );
    // Start up the subapplet
    applet.init();
    // Finally, store it in our list
    applets.addElement( applet );
  }

  public Applet addApplet( String className ) 
      throws ClassNotFoundException, IllegalAccessException,
             InstantiationException {
    // Create an instance of the named applet
    Class clas = Class.forName( className );
    Applet applet = (Applet)clas.newInstance();
    // Add it to the system
    addApplet( applet );
    // Return it, in case the caller wants to access the Applet
    // object directly
    return applet;
  }

  public void start() {
    for (Enumeration e=applets.elements(); e.hasMoreElements();) {
      ((Applet)e.nextElement()).start();
    }
  }

  public void stop() {
    for (Enumeration e=applets.elements(); e.hasMoreElements();) {
      ((Applet)e.nextElement()).stop();
    }
  }

  public void destroy() {
    for (Enumeration e=applets.elements(); e.hasMoreElements();) {
      ((Applet)e.nextElement()).destroy();
    }
  }

  public void appletResize( int width, int height ) {
    System.out.println( "ach" );
  }
}

class MultiAppletStub implements AppletStub
{
  private Applet applet;

  public MultiAppletStub( Applet applet ) {
    this.applet = applet;
  }

  public boolean isActive() {
    return applet.isActive();
  }

  public URL getDocumentBase() {
    return applet.getDocumentBase();
  }

  public URL getCodeBase() {
	return applet.getCodeBase();
  }

  public String getParameter( String name ) {
    return applet.getParameter( name );
  }

  public AppletContext getAppletContext() {
	return applet.getAppletContext();
  }

  public void appletResize( int width, int height ) {
  }
}

MultiAppletTest.java


// $Id$

import java.applet.*;
import java.awt.*;
import java.net.*;

public class MultiAppletTest extends MultiApplet
{
  public void init() {
    try {
      // Set up a layout that allows us to see all the subapplets
	  setLayout( new GridLayout( 2, 2 ) );

	  Applet applet;

      // Create four subapplets, and put them on-screen
      applet = addApplet( "SimpleApplet" );
	  add( "1", applet );
	  applet = addApplet( "SimpleApplet" );
	  add( "2", applet );
	  applet = addApplet( "SimpleApplet" );
	  add( "3", applet );
	  applet = addApplet( "SimpleApplet" );
	  add( "4", applet );

      // There are several exceptions thrown by addApplet(); we'll
      // just do the easy thing and catch everything here:
    } catch( Exception e ) {
      System.out.println( e );
    }
  }
}

SimpleApplet.java

// $Id$

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class SimpleApplet extends Applet
  implements MouseListener, MouseMotionListener, Runnable
{
  private int dpx, dpy;
  static final private int spacing = 16;
  private int spacing_offset = 0;
  private int pause_ms;
  static final private int pause_min=10, pause_max=200;
  private Thread thread;
  Random random = new Random( System.currentTimeMillis() );
  private boolean yet_clicked=false;

  public SimpleApplet() {
    pause_ms = random.nextInt();
    pause_ms = Math.abs( pause_ms );
    pause_ms = pause_min + (pause_ms % (pause_max-pause_min));
  }

  public void init() {
    System.out.println( "init "+this );
    addMouseListener( this );
    addMouseMotionListener( this );
  }

  public void start() {
    System.out.println( "start "+this );
    if (thread!=null) {
      thread.stop();
      thread=null;
    }
    thread = new Thread( this );
    thread.start();
  }

  public void stop() {
    System.out.println( "stop "+this );
    thread.stop();
    thread=null;
  }

  public void destroy() {
    System.out.println( "destroy "+this );
    thread.stop();
    thread=null;
  }

  public void paint( Graphics g ) {
    if (!yet_clicked) {
      dpx = getSize().width/2;
      dpy = getSize().height/2;
    }
	int width=getSize().width, height=getSize().height;
	g.setColor( Color.black );
    for (int i=spacing_offset; i 

About the Author

Greg Travis is a freelance programmer living in New York City. His interest in computers can probably be traced back to the episode of "The Bionic Woman" where Jamie runs around trying to escape a building, whose lights and doors are controlled by an evil artificial intelligence. He's a devout believer in the idea that when a computer program works, it's a complete coincidence. He can be reached at mito@panix.com.






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date
Rocket Fuel