Deployment Frameworks - MultiApplet
(Page 8 of 9 )
MultiApplet
package com.croftsoft.core.gui.multi;
[...]
import com.croftsoft.core.CroftSoftConstants;
import com.croftsoft.core.awt.image.ImageLib;
import com.croftsoft.core.gui.FullScreenToggler;
import com.croftsoft.core.gui.LifecycleWindowListener;
import com.croftsoft.core.lang.NullArgumentException;
import com.croftsoft.core.lang.Pair;
import com.croftsoft.core.lang.lifecycle.Lifecycle;
public class MultiApplet
extends JApplet
implements Lifecycle
/////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
{
The main class, MultiApplet, extends the Swing version of the Applet class, JApplet. It imports a number of other classes from the CroftSoft reusable code library. It implements the Lifecycle interface with its init(), start(), stop(), and destroy() methods so that it can be integrated into a generic Lifecycle framework container.
public static final String DEFAULT_NEWS_NAME = "News";
//
private final String appletInfo;
private final Pair [ ] appletPairs;
private final String newsName;
private final String newsHTML;
private final String newsPage;
The instance variable appletInfo is returned when the getAppletInfo() method is called. Variables DEFAULT_NEWS_NAME, newsName, newsHTML, and newsPage are used to create an instance of MultiAppletNews.
A Pair is a convenience class from package com.croftsoft.core.lang that I use to hold together a related pair of String objects, usually a name-value pair. I like to use an array of Pair instead of two separate String arrays because then I do not have to worry about the String arrays accidentally being of unequal length. In the MultiApplet class, I use the name to store the short name of a game applet to be displayed on a tab at the top of a JTabbedPane. The value is the class name of the corresponding game applet.
private JTabbedPane jTabbedPane;
private Component appletComponent;
private boolean isStarted;
private int index;
private MultiAppletStub multiAppletStub;
This JApplet subclass maintains an instance reference to a JTabbedPane Swing component. The JTabbedPane is used to contain your game applets. When the user clicks a tab, a different game is loaded.
The appletComponent is a reference to the game currently loaded. Note that it is an instance of Component, not Applet. The boolean flag isStarted provides life-cycle state information. The index variable indicates which tab is currently selected. The MultiAppletStub instance is used to propagate the AppletContext to the game applets.
public static void main ( String [ ] args )
//////////////////////////////////////////////////////////
{
launch (
CroftSoftConstants.DEFAULT_APPLET_INFO,
new Pair [ ] {
new Pair ( "Applet1", "javax.swing.JApplet" ),
new Pair ( "Applet2", "javax.swing.JApplet" ) },
DEFAULT_NEWS_NAME,
( String ) null,
CroftSoftConstants.HOME_PAGE,
"CroftSoft MultiApplet",
CroftSoftConstants.FRAME_ICON_FILENAME,
MultiApplet.class.getClassLoader ( ),
( Dimension ) null,
"Close CroftSoft MultiApplet?" );
}
You use static method main() to test the MultiApplet from the command line. It passes test data to the following launch() method.
public static void launch (
String appletInfo,
Pair [ ] appletPairs,
String newsName,
String newsHTML,
String newsPage,
String frameTitle,
String frameIconFilename,
ClassLoader frameIconClassLoader,
Dimension frameSize,
String shutdownConfirmationPrompt )
///////////////////////////////////////////////////////
{
JFrame jFrame = new JFrame ( frameTitle );
try
{
Image iconImage = ImageLib.loadBufferedImage (
frameIconFilename, frameIconClassLoader );
if ( iconImage != null )
{
jFrame.setIconImage ( iconImage );
}
}
catch ( Exception ex )
{
}
The static launch() method launches the program when it is not embedded in a web page as an applet. This allows it to be run as an executable JAR desktop application or as a Java Web Start application. It starts by creating a new frame and setting the frame icon image, that little picture at the top left corner of the frame. If it cannot find the image, it ignores it and moves on.
MultiApplet multiApplet = new MultiApplet (
appletInfo, appletPairs, newsName, newsHTML,
newsPage );
jFrame.setContentPane ( multiApplet );
FullScreenToggler.monitor ( jFrame );
LifecycleWindowListener.launchFrameAsDesktopApp (
jFrame,
new Lifecycle [ ] { multiApplet },
frameSize,
shutdownConfirmationPrompt );
}
It then creates an instance of the MultiApplet and sets it as the content page for the frame. Method FullScreenToggler.monitor(), described in Chapter 5, is used to allow the user to toggle between windowed and full screen mode. The static convenience method launchFrameAsDesktopApp in class LifecycleWindowListener shows the frame and calls the appropriate lifecycle methods on the MultiApplet instance when the user activates, deactivates, or closes the window.
public MultiApplet (
String appletInfo,
Pair [ ] appletPairs,
String newsName,
String newsHTML,
String newsPage )
///////////////////////////////////////////////////////
{
NullArgumentException.check ( this.appletInfo =
appletInfo );
NullArgumentException.check ( this.appletPairs =
appletPairs );
NullArgumentException.check ( this.newsName =
newsName );
this.newsHTML = newsHTML;
this.newsPage = newsPage;
}
The constructor method simply saves references to the constructor arguments for later use in the init() method.
public String getAppletInfo ( )
///////////////////////////////////////////////////// {
return appletInfo;
}
public void init ( )
////////////////////////////////////////////////////////// {
Container contentPane = getContentPane ( );
contentPane.setLayout ( new BorderLayout ( ) );
jTabbedPane = new JTabbedPane (
JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT );
contentPane.add ( jTabbedPane, BorderLayout.CENTER );
The init() code makes the JTabbedPane fill the frame. The tabs run along the top and can be scrolled left and right when there are too many to fit in the width of the window. By using the scroll tab layout, you can have a large number of tabs, one for each game, without using up additional screen real estate.
jTabbedPane.add (
new MultiAppletNews ( newsHTML, newsPage, this ),
newsName );
On the first tab, I put the special component called MultiAppletNews. I treat this component differently from the others because it downloads a web page from my web site. I do not want it to be destroyed when a user clicks another tab because it would then have to download the web page again when the user comes back to it. For this reason, as documented in the following code, the code treats this particular panel differently from the others.
for ( int i = 0; i < appletPairs.length; i++ )
{
jTabbedPane.add ( new JPanel ( ), appletPairs [ i ].name );
}
I then create all the other tabs as labeled with the short names of the games.
jTabbedPane.addChangeListener (
new ChangeListener ( )
{
public void stateChanged ( ChangeEvent changeEvent )
{
handleStateChange ( );
}
});
multiAppletStub = new MultiAppletStub ( this );
}
I add a ChangeListener to the JTabbedPane so that I can tell when a user clicks a different tab. I end the initialization method by creating a MultiAppletStub instance.
public void start ( )
/////////////////////////////////////////////////////////
{
multiAppletStub.setActive ( true );
try
{
if ( appletComponent instanceof Applet )
{
( ( Applet ) appletComponent ).start ( );
}
else if ( appletComponent instanceof Lifecycle )
{
( ( Lifecycle ) appletComponent ).start ( );
}
}
catch ( Exception ex )
{
ex.printStackTrace ( );
}
isStarted = true;
}
The start() method of MultiApplet delegates to the start() method of the current appletComponent if it can determine that it is an instance of Applet or Lifecycle. Note that to be included within MultiApplet, a game must extend Component but it does not have to extend Applet or implement Lifecycle. An example of this might be a game where the updates are driven exclusively by user-input events such as mouse clicks instead of a continuously running animation thread.
public void stop ( )
/////////////////////////////////////////////////////////
{
multiAppletStub.setActive ( false );
try
{
if ( appletComponent instanceof Applet )
{
( ( Applet ) appletComponent ).stop ( );
}
else if ( appletComponent instanceof Lifecycle )
{
( ( Lifecycle ) appletComponent ).stop ( );
}
}
catch ( Exception ex )
{
ex.printStackTrace ( );
}
isStarted = false;
}
public synchronized void destroy ( )
////////////////////////////////////////////////////////
{
try
{
if ( appletComponent instanceof Applet )
{
( ( Applet ) appletComponent ).destroy ( );
}
else if ( appletComponent instanceof Lifecycle )
{
( ( Lifecycle ) appletComponent ).destroy ( );
}
}
catch ( Exception ex )
{
ex.printStackTrace ( );
}
}
The stop() and destroy() methods are similar. The start() and stop() methods mutate the active state of the MultiAppletStub. These methods are usually called in response to windowing events such as window activated or deactivated, window minimized or maximized, or window closed.
private void handleStateChange ( )
////////////////////////////////////////////////////////
{
if ( isStarted )
{
stop ( );
}
If the user clicks a different tab and the currently selected game is running, the handleStateChange() method stops it.
if ( index > 0 )
{
jTabbedPane.setComponentAt ( index, new JPanel ( ) );
destroy ( );
appletComponent = null;
System.gc ( );
}
index = jTabbedPane.getSelectedIndex ( );
An index position of zero indicates that the tab panel containing Multi-AppletNews is being displayed. Unless the current index position is zero, the current appletComponent is destroyed and all references to it are removed. A garbage collection of system memory is forced at this point to reduce the chances that it will automatically run later and interrupt the animation of the newly selected game. The index position is updated to point to the newly selected tab panel.
if ( index > 0 )
{
try
{
appletComponent = ( Component ) Class.forName (
appletPairs [ index - 1 ].value ).newInstance ( );
If the new index position is not zero, the game is loaded into memory using dynamic linking.
if ( appletComponent instanceof Applet )
{
( ( Applet ) appletComponent ).setStub ( multiAppletStub );
}
if ( appletComponent instanceof JComponent )
{
FullScreenToggler.monitor ( ( JComponent ) appletComponent );
}
jTabbedPane.setComponentAt ( index, appletComponent );
If the appletComponent is an instance of Applet, the AppletStub is set. If it is an instance of JComponent, we can monitor it for keyboard events that toggle full screen mode. All the dynamically loaded classes must at least extend Component so that they can be added to the JTabbedPane.
try
{
if ( appletComponent instanceof Applet )
{
( ( Applet ) appletComponent ).init ( );
}
else if ( appletComponent instanceof Lifecycle )
{
( ( Lifecycle ) appletComponent ).init ( );
}
}
catch ( Exception ex )
{
ex.printStackTrace ( );
}
start ( );
}
catch ( Exception ex )
{
ex.printStackTrace ( );
}
}
}
The init() method of the new appletComponent instance is followed by a call to the start() method of the MultiApplet. Note that the init() method of the MultiApplet initializes the MultiApplet but its start() method initializes the appletComponent.
This article is excerpted from Advanced Java Game Programming by David Wallace Croft (Apress, 2004; ISBN 1590591232). Check it out at your favorite bookstore today. Buy this book now. |
Next: CroftSoftCollection >>
More Java Articles
More By Apress Publishing