Testez votre applet en toute simplicité: transformez-la en application

OK Vous avez dépassé l'applet Hello World et passez à quelque chose de beaucoup plus grand, beaucoup plus intéressant. Vous avez toujours besoin d'une interface basée sur un navigateur, vous allez donc développer votre programme sous forme d'applet. Mais déboguer l'applet en insérant des printlns dans Netscape est un ours, et l'appletviewer ne semble plus jamais fonctionner correctement. Ou peut-être que vous écrivez un programme qui serait utile à la fois comme applet et comme application autonome. Vous pouvez insérer la mainfonction dans votre Appletsous-classe et ajouter du code pour gérer les arguments de ligne de commande, la manipulation de fenêtres et le chargement d'images vous-même, maintenant que le navigateur AppletContextn'est plus là pour vous.

AppletContextest une interface, vous ne pouvez donc même pas instancier un AppletContextobjet pour fournir les fonctions que le navigateur AppletContextfournit normalement. Mais vous pouvez implémenter l'interface. Et si vous l'avez implémenté de manière très générique, vous pouvez le ranger dans votre propre boîte à outils pour le réutiliser encore et encore. Cet article vous montre comment faire exactement cela. En fait, vous n'avez même pas besoin d'écrire l'implémentation vous-même car le code source est inclus à la fin de cet article.

La classe et les interfaces

Pour atteindre notre objectif de répliquer l'environnement basé sur le navigateur, nous devons en fait implémenter quelques interfaces - en particulier, AppletContextet AppletStub. AppletContextest censé représenter l'environnement de l'applet - normalement le navigateur et le document HTML englobant. Le AppletStubest utilisé par la Appletsuperclasse pour aider à implémenter les fonctions d'applet que vous pouvez appeler telles que getAppletContext()et getParameter(). Nous allons mettre en œuvre une autre interface ainsi: URLStreamHandlerFactory. Cela sera discuté plus tard.

Puisque nous n'implémentons que des interfaces jusqu'à présent, nous avons toujours la possibilité d'étendre quelque chose. Le navigateur fournit la fenêtre dans laquelle l'applet est dessinée, nous avons donc besoin d'un objet Frame. J'ai créé une classe que j'appelle DummyAppletContextqui s'étend Frame; sa définition commence:

classe publique DummyAppletContext étend Frame implémente AppletStub, AppletContext, URLStreamHandlerFactory { 

Initialisation

J'ai plusieurs façons d'instancier un DummyAppletContext; l'un des plus utiles provient directement d'une mainfonction (comme indiqué ci-dessous) dans la DummyAppletContextclasse elle-même. De cette façon, je n'ai pas besoin de définir maindans une applet pour l'exécuter en tant qu'application autonome. Je pourrai exécuter des applets tels quels, via mon DummyAppletContext.

public static void main (String args []) {new DummyAppletContext (args); }

Le nouvel opérateur ci-dessus appelle le constructeur qui prend la liste d'arguments. Je suppose que le premier argument est le nom de la Appletsous - classe et j'essaye d'instancier la classe. J'utilise la Classfonction statique forName()pour obtenir l' Classobjet, puis j'appelle sa newInstance()fonction pour instancier l'applet. Vous pouvez obtenir une foule d'exceptions à partir de cette seule ligne, et elles sont toutes non récupérables. Donc, si j'attrape une exception, je l'imprime simplement et je quitte. Si cela fonctionne, j'appelle une fonction d'initialisation privée que j'utilise dans tous les constructeurs. Voici le code du constructeur:

public DummyAppletContext (String args []) {

super (args [0]);

essayez {Applet applet = (Applet) Class.forName (args [0]) .newInstance ();

init (applet, 640, 480, args, 1); } catch (Exception e) {e.printStackTrace (); System.exit (1); }}

L'un des autres constructeurs (illustrés ci-dessous) prend un objet applet existant. J'utilise ce constructeur lorsque je veux implémenter la mainfonction dans une autre classe, telle que la Appletsous-classe elle-même. En fait, ce n'est qu'une commodité. Avec une mainfonction dans la Appletsous - classe, je peux démarrer un programme en exécutant l'interpréteur Java sur la Appletsous - classe, plutôt que de devoir l'exécuter DummyAppletContextet de spécifier la Appletsous - classe séparément ( java MyAppletcontre java DummyAppletContext MyApplet). Cela me permet également de spécifier une largeur et une hauteur par défaut dans l'applet. (Je fournis un autre constructeur comme celui-ci, qui ne nécessite pas les arguments de largeur et de hauteur par défaut.)

public DummyAppletContext (applet d'applet, int default_width, int default_height, String args []) {

super (applet.getClass (). getName ());

init (applet, largeur_par défaut, hauteur_par défaut, args, 0); }

La initfonction fait la plupart de la magie de l'installation. Ses arguments incluent l'objet applet, la taille par défaut, les arguments de ligne de commande et l'index de début des arguments. N'oubliez pas que nous avons utilisé le premier argument de l'un des constructeurs pour déterminer la Appletsous - classe à charger, par son nom uniquement. Dans ce cas, startidx- l'index à partir duquel commencer l'analyse des arguments et des paramètres de l'applet - est 1, mais sinon il vaut 0. La initfonction indique d'abord à la URLclasse que cet objet sera désormais la valeur par défaut URLStreamHandlerFactory. (Nous implémentons l'interface pour cela.) Il ajoute ensuite l'applet donné à un vecteur d'applets qui ne contiendra que cette applet, et il indique à l'applet que cet objet agira comme son AppletStub. Voici la initfonction:

private void init (applet d'applet, int default_width, int default_height, String args [], int startidx) {

URL.setURLStreamHandlerFactory (this);

applets.addElement (applet); applet.setStub (this);

largeur_initiale = largeur_par_défaut; hauteur_initial = hauteur_par défaut;

parseArgs (args, startidx);

status = nouveau TextField (); status.setEditable (faux);

add ("Centre", applet); add ("Sud", statut);

applet.init (); appletResize (largeur_initial, hauteur_initiale);

spectacle(); applet.start (); }

Les arguments sont analysés en parcourant simplement les éléments du tableau et en ajoutant chaque paire d'arguments à une table de hachage de paires nom / valeur . Les arguments -width et -height sont traités spécialement et remplacent la largeur et la hauteur par défaut de l'applet. Ils ne sont pas ajoutés à la table de hachage. L'analyse des arguments se produit dans la fonction parseArgs, illustrée ici:

public void parseArgs (String args [], int startidx) {for (int idx = startidx; idx <(args.length - startidx); idx + = 2) {try {if (args [idx] .equals ("-width") )) {largeur_initiale = Integer.parseInt (args [idx + 1]); } else if (args [idx] .equals ("-height")) {initial_height = Integer.parseInt (args [idx + 1]); } else {params.put (args [idx], args [idx + 1]); }} catch (NumberFormatException nfe) {System.err.println ("Attention: l'argument de ligne de commande" + args [idx] + "n'est pas un nombre valide."); }}}

La initfonction se poursuit en définissant la zone d'état (utilisée par la fonction showStatus) à l'aide d'un objet AWT Text non modifiable. Il ajoute les composants de l'applet et de la zone d'état au cadre (le DummyAppletContext) selon la BorderLayoutstratégie par défaut , appelle la initfonction de l'applet et redimensionne la fenêtre comme spécifié. Enfin, la fenêtre s'affiche, l'applet initet les startfonctions sont appelées. (Nous n'avons jamais besoin d'appeler stop, et startnous ne sommes plus jamais appelés car nous ne sommes pas dans un navigateur. De plus, je n'ai jamais utilisé la destroyméthode pour quoi que ce soit, donc je ne l'appelle pas. Mais si vous en avez besoin, recommande de l'appeler avant chaque System.exit()appel, avec un test d'abord pour voir s'il a init()été appelé.)

Je n'ai qu'à remplacer une fonction Frame handleEvent(), comme indiqué ci-dessous, afin de pouvoir attraper l'événement WINDOW_DESTROY si l'utilisateur appuie sur l'icône Fermer dans la barre de fenêtre.

public boolean handleEvent (Event evt) {

if (evt.id == Event.WINDOW_DESTROY) {System.exit (0); }

return super.handleEvent (evt); }

AppletStub

AppletStub

déclare quelques fonctions que nous devons implémenter:

  • isActive - renvoie toujours vrai

  • getDocumentBase - renvoie une URL "fichier" pour le répertoire courant

  • getCodeBase- renvoie la même chose qui getDocumentBaseretourne

  • getParameter- indexe la table de hachage que nous avons intégrée parseArgset renvoie la valeur correspondante ou null si elle n'est pas présente

  • getAppletContext- renvoie "cet" objet (notre DummyAppletContext)

  • appletResize - tente de redimensionner la fenêtre pour accueillir une demande de redimensionnement de l'applet

Most of these functions are pretty straightforward. However, I did have to do some special things to make getDocumentBase to work the way I wanted it to. I started by creating a reference to a dummy file. Using an object of the File class, I called getAbsolutePath() to get the full path name of the file. For DOS (Windows), I had a file name with a bunch of backslashes in it. My objective was to create a URL, so I had to replace these slashes with forward slashes. Also, the typical browser expects the colon (:) in a DOS filename to be replaced with a vertical bar (|) in the URL. The code below performs a transformation of the dummy file to what appears to be a Netscape-compliant URL.

 public URL getDocumentBase() { URL url = null; try { File dummy = new File( "dummy.html" ); String path = dummy.getAbsolutePath(); if ( ! File.separator.equals( "/" ) ) { StringBuffer buffer = new StringBuffer(); if ( path.charAt(0) != File.separator.charAt(0) ) { buffer.append( "/" ); } StringTokenizer st = new StringTokenizer( path, File.separator ); while ( st.hasMoreTokens() ) { buffer.append( st.nextToken() + "/" ); } if ( File.separator.equals( "\\" ) && ( buffer.charAt(2) == ':' ) ) ' );  else { } path = buffer.toString(); path = path.substring( 0, path.length()-1 ); } url = new URL( "file", "", -1, path ); } catch ( MalformedURLException mue ) { mue.printStackTrace(); } return url; } 

The only other AppletStub function implementation of note is appletResize(). In this function, I not only found that I needed to take into account the size of the status text box, but I also had to accomodate the window decorations (for example, the title bar). Java provides the function needed to get that information in Frame's insets() function. Here is the appletResize function:

public void appletResize( int width, int height ) {

Insets insets = insets();

resize( ( width + insets.left + insets.right ), ( height + status.preferredSize().height + insets.top + insets.bottom ) ); }

AppletContext

The functions required to implement

AppletContext

include:

  • getAudioClip -- returns null, because there doesn't seem to be a toolkit for audio clips in my JDK. (You could handle this differently, returning your own implementation of AudioClip.)

  • getImage -- gets an image from the given URL. For the purposes of the DummyAppletContext, all URLs are assumed to be references to a local file. Therefore getImage converts the URL to a file name, and uses the AWT Toolkit object to load the image.

  • getApplet -- is supposed to return an applet by name. I never name my applet, and there are no other applets, so this always returns null.

  • getApplets -- returns an Enumeration of the applets in this AppletContext. There is only one, so this returns an Enumeration of one element. The Enumeration is created from the Vector we filled in the init function.

  • showDocument -- There are two variations of this function, neither one of which actually shows a document. In a browser, showDocument requests that a document at the given URL be loaded. I actually do show this request in the status area, but I don't attempt to retrieve or show the document.

  • showStatus -- writes the given text to the Text object used as the status area.

The getImage() function uses a private function filenameFromURL() to convert the URL back to a legal file name for the current operating system. Again, I have to make special provisions for DOS, taking into account variations I have seen from time to time. In particular, I have to convert the URL's vertical bar back to a colon.

private String filenameFromURL (URL url) {String filename = url.getFile (); if (filename.charAt (1) == '|') {StringBuffer buf = new StringBuffer (filename); buf.setCharAt (1, ':'); nom de fichier = buf.toString (); } else if (filename.charAt (2) == '|') {StringBuffer buf = new StringBuffer (filename); buf.setCharAt (2, ':'); nom de fichier = buf.toString (); } retourne le nom du fichier; }

URLStreamHandlerFactory

URLStreamHandlerFactory

n'a qu'une seule fonction:

createURLStreamHandler()

. J'implémente cette fonction afin de provoquer mon implémentation de

URLStreamHandler

à utiliser chaque fois que l'applet tente d'ouvrir une connexion à une URL. Maintenant, quand j'appelle

openStream()

sur une URL dans mon application Java, il ouvre en fait un flux vers le fichier local pour l'entrée. Voici

createURLStreamHandler()