Westfälische Wilhelms-Universität Münster Ausarbeitung Grundlagen der MIDlet-Programmierung im Rahmen des Seminars: „Mobile Java“ Claus Alexander Usener Themensteller: Prof. Dr. Herbert Kuchen Betreuer: Dipl.-Wirt.Inform. Michael Poldner Institut für Wirtschaftsinformatik Praktische Informatik in der Wirtschaft Inhaltsverzeichnis 1 Einleitung................................................................................................................... 3 2 Programmierwerkzeuge............................................................................................. 4 3 2.1 Java ME Wireless Toolkit................................................................................. 4 2.2 Eclipse ME Plugin ............................................................................................ 5 2.3 Installation auf einem Endgerät ........................................................................ 5 Grundlagen der MIDlet-Programmierung ................................................................. 6 3.1 MIDlet und MIDlet-Suite ................................................................................. 6 3.2 Der MIDlet-Lebenszyklus ................................................................................ 7 3.2.1 3.2.2 4 Die MIDlet-Zustände.................................................................................... 7 Die MIDlet-Zustandsübergänge ................................................................... 9 GUI-Programmierung.............................................................................................. 12 4.1 Die Klasse Display.......................................................................................... 13 4.2 Kommandos .................................................................................................... 15 4.3 High-Level-Display-Komponenten ................................................................ 16 4.3.1 4.3.2 4.3.3 4.3.4 Alert ............................................................................................................ 16 List .............................................................................................................. 17 TextBox ...................................................................................................... 18 Form............................................................................................................ 18 5 Zusammenfassung ................................................................................................... 21 A MIDlet ErstesMIDlet ............................................................................................... 22 B MIDlet SkiGuide ..................................................................................................... 24 Literaturverzeichnis ........................................................................................................ 30 II Kapitel 1: Einleitung 1 Einleitung Schon vor 3 Jahren zeichnete sich ein Trend auf dem Handy-Markt ab: viele Mobiltelefone wurden mit einer Java Kilobyte Virtual Maschine (KVM) ausgestattet um für Java2 Micro Edition – Anwendungen gerüstet zu sein. Warum nun Java2 Micro Edition (J2ME) und nicht Java2 Standard Edition(J2SE)? Die Java2 Micro Edition ist speziell für Mobile Endgeräte entwickelt worden. Ob Mobiltelefon, Personal Digital Assistant (PDA) oder eine Mischform wie der Mobile Digital Assistant (MDA), alle drei haben eines gemeinsam: Sie sind für den mobilen Markt konzipiert. Sie sollen kompakt und handlich sein. Damit sie nicht ständig aufgeladen werden müssen, sollen sie zudem noch wenig Energie verbrauchen. Diese Eigenschaften gehen jedoch mit Abstrichen in Punkt Leistung einher. So besitzt ein modernes Mobiltelefon nur einen sehr kleinen Prozessor (meistens wenige 100 MHz) und nur einen geringen Speicher (ab 4 MB aufwärts). J2ME ist genau für diese Art von Geräten mit wenig Leistung und geringen Ressourcen entwickelt worden. Bei J2ME selbst handelt es sich nur um eine Menge von Spezifikationen. Die J2ME Architektur basiert auf Konfigurationen, die um bestimmte Profile erweitert werden. Eine Konfiguration hält in Verbindung mit einem Profil eine bestimmte Anzahl von Application Programming Interfaces (APIs) bereit. Die Anzahl der J2ME-APIs ist jedoch wegen der beschränkten Ressourcen im Vergleich zu den J2SEAPIs sehr gering. Anwendungen für das Mobile Information Device Profile (MIDP) wurden in Anlehnung an die Applets der J2SE von den Sun Entwicklern MIDlets getauft. Diese Seminararbeit führt in die Grundlagen der MIDlet-Programmierung ein und beschränkt sich dabei auf das Programmieren in der Konfiguration CLCD1.1 und in dem Profil MIDP 2.0. Die Arbeit ist in drei Kapitel gegliedert. Das erste Kapitel Programmierwerkzeuge geht hierbei auf die notwendigen bzw. hilfreichen Programmierumgebungen ein. Das Kapitel Grundlagen der MIDlet-Programmierung gibt einen Überblick über alle notwendigen Methoden zur Steuerung der möglichen Zustände, die von einem MIDlet angenommen werden können. Das letzte Kapitel GUI-Programmierung legt die Grundlagen für die Erstellung von grafischen Benutzerschnittstellen. 3 Kapitel 2: Programmierwerkzeuge Ziel ist es, mit dieser Arbeit eine Anleitung an die Hand zu geben, mit der jeder, der schon J2SE-Programme geschrieben hat, in die Lage versetzt wird, kleine MIDlets selbst zu programmieren. 2 Programmierwerkzeuge Voraussetzung zur Erstellung von MIDlets ist eine Installation des Java 2 Development Kit [JDK06], da für die Entwicklung mindestens der Compiler javac, der Interpreter java sowie das Archivprogramm jar notwendig sind. Zusätzlich wird mindestens die MIDP-Referenzimplementierung [MR06] benötigt. Es enthält Klassenbibliotheken und Werkzeuge um MIDlets anzeigen, ausführen und debuggen zu können. Der große Nachteil der MIDP-RI besteht jedoch darin, dass sämtliche Aufrufe über die Eingabekonsole ausgeführt werden müssen. Die MIDP-RI stellt nur die Funktionen zur Verfügung, jedoch keine grafische Benutzerschnittstelle. (vergl. Sc04) Auch wichtige Dateien wie das Manifest oder der Applikationsdeskriptor müssen vom Programmierer von Hand erstellt und in das MIDlet eingebunden werden. Mit den oben genannten Installationen sind zwar die Grundlagen für die Erstellung von MIDlets gelegt. Es empfiehlt sich jedoch aus Gründen der Praktikabilität an Stelle der MIDP-RI das Java ME Wireless Toolkit zu verwenden. 2.1 Java ME Wireless Toolkit „You write the source code and the toolkit takes care of the rest“ [UG04 S. 3]. Das Java ME Wireless Toolkit (WTK) bietet dem Anwender eine grafische Benutzerschnittstelle, mit der sämtliche Vorgänge rund um die Entwicklung von MIDlets schnell und benutzerfreundlich handhaben kann. Hierbei unterstützt das WTK bei dem Anlegen des Projekts, dem Übersetzen, der Verifizierung und dem Starten der Anwendung auf dem mitgelieferten Emulator. Selbst ein Debugger wird bei dem WTK mitgeliefert. Eine ausführliche Installationsanleitung ist in [BM06] im Kapitel Erste Schritte zu finden. 4 Kapitel 2: Programmierwerkzeuge 2.2 Eclipse ME Plugin Da das WTK ohne Editor installiert wird, empfiehlt es sich zum komfortablen Programmieren den Editor Eclipse [Ec06a] um das Plugin EclipseME [Ec06b] zu erweitern. Damit wird eine integrierte Möglichkeit zur Konstruktion von Java MEAnwendungen angeboten. EclipseME bietet die wichtigsten Eigenschaften (Projekte anlegen, compilieren emulieren..), die auch von dem WTK bereitgestellt werden. Da EclipseME jedoch in die Entwicklungsumgebung Eclipse eingebunden wird, ermöglicht es zusätzlich das Entwickeln von J2ME-Anwendungen, wie man es von J2SE-Anwendungen gewöhnt ist. Eine ausführliche Installationsanleitung ist unter [Ec06b] zu finden. 2.3 Installation auf einem Endgerät Die Installation eines MIDlets auf dem mobilen Endgerät kann auf zwei Arten geschehen. Zum Einen über die „over-the-air“-Schnittstelle (OTA) oder zum Anderen über den PC. Zuvor muss das MIDlet jedoch zu einem jar-Archiv (vergl. hierzu Kapitel 3.1) paketiert werden. Hierzu stellen die oben genannten Tools die passenden Funktionen bereit. Damit das MIDlet über die OTA-Schnittstelle installiert werden kann, wird es erst auf einen Web-Server übertragen, um es dann über den Browser des mobilen Endgerätes herunterzuladen. Danach kann es durch eine Bestätigung des Benutzers installiert werden. Soll das MIDlet über den PC übertragen werden, so kann es entweder drahtlos per Bluetooth bzw. Infrarot oder aber über ein USB-Kabel übertragen werden. Fast alle Hersteller bieten zu diesem Zweck eigene Software an. Nach der Übertragung wird auch in diesem Fall der Benutzer noch einmal aufgefordert, die Installation zu bestätigen, bevor das MIDlet von dem mobilen Endgerät selbständig installiert wird. 5 Kapitel 3: Grundlagen der MIDlet-Programmierung 3 Grundlagen der MIDlet-Programmierung 3.1 MIDlet und MIDlet-Suite Ein MIDlet besteht aus mindestens einer Klasse und ist dafür bestimmt, auf einem Mobile Information Device Profile (MIDP) ausgeführt zu werden. Hierzu muss eine Klasse des MIDlets auf der abstrakten Klasse MIDlet basieren und erbt somit alle notwendigen Methoden, so dass die Application Management Software (AMS) des mobilen Endgerätes das MIDlet steuern, also geordnet starten, pausieren und beenden kann. (vergl. [Sc04]) Folgender Codeausschnitt zeigt, welche abstrakten Methoden die Klasse MIDlet bereitstellt und somit zu implementieren sind: import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; public class Example extends MIDlet { //Konstruktor des MIDlets public Example() {...} //Methode, die von der AMS zum Beenden des MIDlets ausgeführt wird protected void destroyApp(boolean arg0) throws MIDletStateChangeException {...} //Diese Methode wird ausgeführt wenn das MIDlet pausiert wird protected void pauseApp() {...} //Methode zum (Neu-)Starten des MIDlets protected void startApp() throws MIDletStateChangeException {...} } Darüber hinaus ist es wichtig, dass in einem MIDlet nur Methoden verwendet werden, die auch in den APIs des MIDP definiert sind. Methoden aus der J2SE werden von dem MIDP nicht unterstützt. Die kleinste auf einem MIDP installierbare Einheit nennt sich MIDlet-Suite. Eine Suite besteht aus einem jar-Archiv sowie einer optionalen jad-Datei, dem so genannten Applikationsdeskriptor. In dem Applikationsdeskriptor sind die Merkmale einer MIDlet-Suite gespeichert. Die Merkmale beschreiben unter anderem, für welche Konfiguration die MIDlet-Suite geschrieben ist, welches Profil verwendet wird, die Versionsnummer der Suite und wie groß die jar-Datei ist. Der Benutzer kann auch selbst definierte Merkmale hinzufügen. Alle Merkmale können im Programm über die Methode getAppProperty(String name) abgefragt werden. Da der Applikationsdeskritor bereits vor der Installation des 6 Kapitel 3: Grundlagen der MIDlet-Programmierung Programms gelesen werden kann, ist es schon an dieser Stelle möglich, die Installation Aufgrund der falschen Konfiguration oder des falschen Profils zu untersagen. Im Gegensatz zum Applikationsdeskriptor enthält das jar-Archiv: • mindestens ein MIDlet • eventuell benötigte Ressourcen wie Bilder, Sound-Dateien und Hilfstexte • sowie einem Manifest: ähnlich dem Applikationsdeskriptor, jedoch erst nach dem Entpacken des Archivs zugreifbar. Der Applikationsdeskriptor sowie das jar-Archiv können automatisch durch das Wireless Toolkit generiert werden. Für weiterführende Informationen zum Applikationsdeskriptor und dem jar-Archiv sei auf [Sc04] und [BM06] verwiesen. 3.2 Der MIDlet-Lebenszyklus Ähnlich einem Applet in der Java 2 Standard Edition hat auch ein MIDlet einen besonderen Lebenszyklus und ist deshalb nicht vergleichbar mit einem J2SE-Objekt. Ein MIDlet wird nicht mit der Methode public static void main(String[] args) gestartet und auch nicht mit der Methode System.exit(int status) been- det. Diese Methoden werden von der Application Management Software nicht unterstützt. Da ein MIDlet für ein mobiles Endgerät geschrieben wird, welches für gewöhnlich wenig Rechenleistung und Ressourcen zur Verfügung stellt, ist ein besonderer MIDletLebenszyklus entwickelt worden. 3.2.1 Die MIDlet-Zustände Ein MIDlet kann immer nur einen der drei möglichen Zustände zerstört, pausierend oder aktiv annehmen. Diese Einteilung in unterschiedliche Zustände ist notwendig, um das MIDlet während der Ausführung situtationsbedingt (z. B. eingehendes Telefongespräch) unterbrechen zu können. In diesen Fall müssen dann notwendige Ressourcen, wie Bildschirm und Tasten für die Anzeige und Annahme des Telefongesprächs, zur Verfügung gestellt werden. Der Zustandswechsel wird nicht immer von der AMS veranlasst; in den häufigsten Fällen wird er sogar vom MIDlet selbst initiiert. 7 Kapitel 3: Grundlagen der MIDlet-Programmierung Abbildung 1 verdeutlicht, welche Methoden von der AMS und welche von dem MIDlet aufgerufen werden, um zwischen den einzelnen Zuständen zu wechseln. Abbildung 1: Zustandsübergänge eines MIDlets (in Anlehnung an [Sc04]) Dieser Zyklus besteht aus den oben genannten drei Zuständen: • Pausierend (paused): Dieser Zustand wird direkt nach der Initialisierung über den parameterlosen Konstruktor angenommen. In diesem Zustand darf das MIDlet nur geringe Ressourcen halten. Alle nicht benötigten Ressourcen, wie Netzwerkverbindungen, große Objekte oder unwichtige Threads, müssen freigegeben werden. Hierbei besteht die Möglichkeit, dass ein anderes MIDlet im Vordergrund läuft und die knappen Ressourcen benötigt. Hält in diesem Fall ein pausierendes MIDlet zu viele oder von anderen benötigte Ressourcen, kann es von der AMS terminiert werden. • Aktiv (active): Beim Übergang in diesen Zustand fordert das MIDlet üblicherweise sämtliche Ressourcen an, die es beim Übergang in den Zustand pausierend freigegeben hat. In diesem Zustand läuft das MIDlet im Vordergrund und hat Zugriff auf sämtliche freien Ressourcen wie Bildschirm, Tastatur und die Rechenleistung. • Zerstört (destroyed): Dieser Zustand ist im Gegensatz zu den anderen beiden endgültig. Alle zu einem späteren Zeitpunkt wichtigen Informationen müssen vorher persistent gespeichert werden. Sämtliche vom MIDlet noch nicht freige- 8 Kapitel 3: Grundlagen der MIDlet-Programmierung gebenen Ressourcen werden von dem Garbage Collector für andere Zwecke zur Verfügung gestellt. 3.2.2 Die MIDlet-Zustandsübergänge Man unterscheidet zwei Möglichkeiten um zwischen Zuständen zu wechseln: entweder kann der Zustandswechsel von der AMS initiiert sein, oder aber das MIDlet veranlasst selbst den Zustandsübergang. Um AMS initiierte Zustandswechsel durchzuführen, muss das MIDlet die drei abstrakten Methoden startApp(), pauseApp() und destroyApp() aus der Superklasse javax.microedition.midlet.MIDlet implementieren. Soll der Zustandsübergang jedoch durch das MIDlet selbst veranlasst werden, so muss das MIDlet der AMS dieses durch den Aufruf der jeweiligen final Methode notifyPaused(), notifyDestroyed(boolean) oder resumeRequest() mittei- len. Zu beachten ist, dass bei einem Aufruf von resumeRequest() nicht, wie es bei den anderen beiden Methoden der Fall ist, der Zustand direkt gewechselt wird. Ein Aufruf dieser Methode bewirkt lediglich einen Vermerk bei der AMS, dass das MIDlet ausgeführt werden will. Diesem Wunsch kann die AMS zu gegebener Zeit nachkommen. Das Beispiel-MIDlet (siehe Anhang A) verdeutlicht die wesentlichen Aufgaben der Methoden startApp(), pauseApp() und destroyApp(). Es läuft wie folgt ab: Nach dem Start wird auf dem Display des mobilen Endgeräts fortlaufend die schon vergangene Zeit seit dem Wechsel in den Zustand aktiv angezeigt. Der Benutzer kann das MIDlet per Tastendruck in den Zustand pausierend wechseln oder das MIDlet beenden. Ist das MIDlet in dem Zustand pausierend, versucht es selbstständig nach einer festgelegten Zeit wieder in den Zustand aktiv zu wechseln. Zu Beginn eines MIDlet-Aufrufs werden die Variablen gebunden und danach der parameterlose Konstruktor ausgeführt, in dem einmalig durchzuführende Initialisierungen durchgeführt werden können: public class ErstesMIDlet extends MIDlet { private private private private Form gui; int wartezeit; //Wiederaufwachintervall Aktion aktion; //Aktions-Thread zur Ausgabe der Zeit boolean erstmalsGestartet=true; 9 Kapitel 3: Grundlagen der MIDlet-Programmierung public ErstesMIDlet() { //Einlesen und Speichern des Attributs Wartezeit aus dem Manifest. String wartezeit=getAppProperty("Wartezeit"); this.wartezeit=Integer.parseInt(wartezeit)*1000; //Anlegen einer Bildschirmausgabe mit Kommando Ende und Pause GUIerstellen(); } Nach dem Konstruktoraufruf ist ein Objekt des aktuellen MIDlets erzeugt worden. Das MIDlet wechselt nun in den Zustand pausierend, deshalb sollten im Konstruktor noch keine Ressourcen angefordert oder große Objekte erzeugt werden. Zum Starten des MIDlets wird von der AMS die Methode startApp() ausgeführt, um alle notwendigen Ressourcen anzufordern und das eigentliche Programm zu starten. Falls benötigte Ressourcen noch anderweitig genutzt, aber in absehbarer Zeit frei werden (transient error), kann eine StateChangeException ausgelöst werden. Das MIDlet verbleibt dann im Zustand pausierend. Der eigentliche Programmcode (im Bsp. der Thread aktion) sollte immer in einen gesonderten Thread ausgelagert werden, da sonst die Methode startApp() unnötig lange ausgeführt wird und so den Zustandswechsel blockiert. protected void startApp() throws MIDletStateChangeException { if (erstmalsGestartet) { //erster Start des MIDlets erstmalsGestartet=false; print("1. Start des MIDlets"); } else { //erneuter Start des MIDlets gui.deleteAll(); print("MIDlet aufgewacht"); } Display.getDisplay(this).setCurrent(gui); //Ressourcen anfordern synchronized (this) //Programmthread starten { aktion=new Aktion(); aktion.start(); } } Ist das MIDlet im Zustand aktiv, so kann es entweder von der AMS oder von dem MIDlet selbst in den Zustand pausierend gesetzt werden. Ersteres führt zur Ausführung der Methode pauseApp(). In dieser Methode sollten alle nicht benötigten Ressourcen freigegeben werden, um sie anderen Prozessen zur Verfügung zu stellen. Diese Art des Zustandswechsels kann eintreten, wenn ein Anruf auf dem mobilen Endgerät eintrifft. protected void pauseApp() { synchronized (this) { aktion=null; } //Löschen des Threads new Timer().schedule(new TimerTask() {public void run() {resumeRequest();}}, wartezeit); //Versuch das MIDlet nach "wartezeit" Sekunden wieder neu zu starten 10 Kapitel 3: Grundlagen der MIDlet-Programmierung } Ein gängiges Verfahren, den laufenden Thread zu unterbrechen, wird in der run()Methode verdeutlicht. Durch die Realisierung als innere Klasse hat der Thread Zugriff auf die Instanzvariable aktion, der umgebenden Klasse ErstesMIDlet. Die Schleife der run()-Methode wird solange ausgeführt, wie der durch aktion referenzierte Thread mit dem aktuell ablaufenden Thread identisch ist. Andernfalls wird die Schleife verlassen und der Thread beendet. Ein Nachteil dieser Methode ist darin zu sehen, dass der Thread erst kurz nach dem Zustandswechsel terminieren kann. Das tritt dann ein, wenn der Schleifenkopf erst nach dem Zustandswechsel wieder erreicht wird. Eine weitere Möglichkeit besteht darin, den Thread durch den Aufruf von Thread.interrupt() zu terminieren, jedoch können mit dieser Handhabung eventu- ell kritische Passagen in der Methode unterbrochen werden. private class Aktion extends Thread { public void run() { long startzeit=new Date().getTime(); while(aktion==this) { print("aktive Zeit: "+ (new Date().getTime()-startzeit)/ 1000 +"s"); try {sleep(1000);} catch (Exception e) {} } } } Veranlasst das MIDlet selbst den Zustandsübergang, indem das Programm dem Benutzer ermöglicht durch eine Interaktion in den Zustand pausierend zu wechseln, dann muss dieser Zustandswechsel der AMS durch den Methodenaufruf notifyPaused() mitgeteilt werden. Sämtliche nicht mehr benötigten Ressourcen müssen vorher vom MIDlet freigegeben werden. In dem Beispiel-MIDlet wird durch eine Benutzerinteraktion der Zustandswechsel durchgeführt, indem die Methode pausieren() aufgerufen wird: private void pausieren() { pauseApp(); //Freigabe der Ressourcen notifyPaused(); //Mitteilung des Zustandswechsels an die AMS } Auch der Zustand zerstört kann durch das MIDlet oder die AMS initiiert werden. Ist sie AMS initiiert, so wird nur die Methode destroyApp(boolean unconditional) ausgeführt. Der Parameter unconditional gibt dabei an, wie in dem Fall verfahren werden soll, dass wichtige Aktionen, wie eine nicht vollendete Datenübertragung, ablaufen. Hat unconditional den Wert false, so wird eine 11 Kapitel 4: GUI-Programmierung MIDletStateChangeException zurückgegeben und der Zustand bleibt unverändert. In allen anderen Fällen wird das MIDlet ohne Rücksicht auf laufende Prozesse beendet. protected void destroyApp(boolean arg0) throws MIDletStateChangeException { synchronized (this) { aktion=null; } gui.append("Adiöööö"); } Veranlasst das MIDlet selbst den Zustandswechsel, so muss es selbst die „Aufräumarbeiten“ übernehmen und danach die Methode notifyDestroyed() aufrufen, um daraufhin in den Zustand zerstört zu wechseln. Im Beispiel-MIDlet wird dieses durch Aufruf der Methode schließen() nach einer Benutzerinteraktion veranlasst: private void schließen() { try { destroyApp(false); //Freigabe der Ressourcen notifyDestroyed(); //Mitteilung des Zustandswechsels an die AMS } catch (MIDletStateChangeException e) {} } Es gibt noch eine weitere Methode, durch die das MIDlet in den eigenen Lebenszyklus eingreifen kann. Befindet sich das MIDlet im Zustand pausierend, so kann es der AMS durch den Aufruf von resumeRequest() mitteilen, dass es wieder in den aktiven Status wechseln will. Die AMS wartet nun so lange, bis eine Aktivierung des MIDlets in Frage kommt und ruft daraufhin die Methode startApp() auf. Für das MIDlet gibt es, abgesehen von dieser Methode, keine andere Möglichkeit, in den Zustand aktiv zu wechseln. Zu erwähnen bleibt, dass es sich bei den oben erwähnten Methoden notifyPaused(), notifyDestroyed() und resumeRequest() um final Methoden handelt, die von dem Programmierer nicht umgeschrieben werden können. 4 GUI-Programmierung Mobile Informationssysteme unterscheiden sich von Desktopsystemen in vielerlei Hinsicht; besonders hervorzuheben ist die Interaktion mit dem Benutzer. Das Display eines mobilen Endgerätes ist vergleichsweise klein. Oftmals ist nur eine Tastatur mit einem Bruchteil der Tasten einer normalen Desktoptastatur vorhanden, ganz abgesehen von einer fehlenden Maus, die allenfalls durch einen Stift ersetzt wird. Hinzu kommt noch, dass die Bildschirme der mobilen Endgeräte oft unterschiedliche Größen haben. 12 Kapitel 4: GUI-Programmierung Um diese Nachteile zu kompensieren ist das Package javax.microedition.lcdui entwickelt worden. Es stellt dem Programmierer Schnittstellen bereit, Ausgaben auf dem Bildschirm anzuzeigen und Eingaben über die Tastatur entgegen zu nehmen. Das Package javax.microedition.lcdui umfasst zwei Ausprägungen der LCDUI: das High-Level-API sowie das Low-Level-API. Das High-Level-API ist für „business“-Anwendungen entwickelt worden. Portabilität zwischen den Endgeräten ist für diese Anwendungen besonders wichtig. Um diese Portabilität gewährleisten zu können, stellt das High-Level-API dem Programmierer nur wenig Kontrolle über das „look and feel“ seiner Anwendungen zur Verfügung. Eine visuelle Gestaltung der Komponenten ist nicht möglich. Die Komponenten selbst besitzen schon vorgefertigte Fähigkeiten, wie Navigation oder Scrollen. Das Abfragen von konkreten Tasten (Taste gedrückt bzw. losgelassen) ist ebenso wenig möglich wie das pixelgenaue Setzen der Komponenten. Das Low-Level-API hingegen ist für Anwendungen konzipiert, bei denen die Komponenten pixelgenau gesetzt werden müssen und Eingaben direkt entgegen genommen werden sollen. Anwendungen müssen somit genauer auf das Endgerät abgestimmt werden und sind deshalb in der Portabilität eingeschränkt. Anwendung findet das LowLevel-API bei der Entwicklung von Spielen. Da diese Seminararbeit nur auf die HighLevel-API eingeht, und nur im begrenzten Umfang auf die GUI-Programmierung eingehen kann, wird für die Low-Level-API auf [Sc04] und [API06] verwiesen. 4.1 Die Klasse Display Über die Klasse Display verwaltet das MIDlet das Display und die Eingabegeräte. Hierzu wird jedem MIDlet genau ein Objekt der Klasse Display zugeordnet. Über die Methode getDisplay() kann dieses Objekt für das MIDlet abgefragt werden. Als Parameter wird das MIDlet selbst übergeben. Alle auf dem Display angezeigten Objekte müssen vom Typ Displayable sein. Displayable ist die Superklasse der Klassen Screen und Canvas. 13 Kapitel 4: GUI-Programmierung Abbildung 2: Auszug aus dem Klassendiagramm des LCDUI (Quelle [To02]) Screen wiederum ist die Superklasse für alle Bildschirmobjekte der High-Level-APIs und Canvas die Superklasse der Low-Level-APIs. Um dem Display ein Objekt zu übergeben wird die Methode setCurrent() aufgerufen, der als Parameter ein Displayable Objekt übergeben wird. Über die Methode setCurrent() können Objekte der Klasse Displayable auf dem Bildschirm angezeigt werden. Objekte, die mit setCurrent() an die Display-Instanz übergeben werden, zeigt der Bildschirm jedoch nur dann an, wenn das MIDlet, zu dem die Display-Instanz gehört, auch im Vordergrund läuft. Die Anzeige einer Textbox könnte wie folgt aufgerufen werden: Display display; protected void startApp() throws MIDletStateChangeException { if(display==null) { //erster Aufruf der startApp() display=Display.getDisplay(this); Displayable textbox=new TextBox("Hello World","Ich bin eine TextBox mit 100 Zeichen",100,TextField.ANY); display.setCurrent(textbox); } ... } Die Klasse Displayable ist die abstrakte Basisklasse für alle auf dem Bildschirm darstellbare Objekte und stellt die gemeinsamen Eigenschaften zusammen. So kann jedes Objekt einen Titel, ein Laufband (engl. Ticker) sowie eine Vielzahl von Kommandos haben. 14 Kapitel 4: GUI-Programmierung 4.2 Kommandos Damit der Benutzer interaktiv Aktionen ausführen kann, werden den DisplayableKomponenten Command- und CommandListener-Objekte zugeordnet. Ein CommandObjekt beinhaltet dabei nur einen Namen, einen Typ sowie eine Priorität. Wird einer Displayable-Instanz ein Kommando mit Hilfe der Methode addCommand() zuge- ordnet, so werden bei der Anzeige dieser Displayable-Instanz die Softbuttons mit den Kommandos belegt. Ist die Anzahl der Kommandos größer als die Anzahl der Softbuttons, so werden die Kommandos in einem Menü zusammengefasst.Die auszuführende Aktion ist im Command nicht enthalten. Sie wird in einem CommandListener gekapselt. Hierzu wird dem Displayable-Objekt der passende CommandListener mit durch Methode setCommandListener() zugeordnet. Die wichtigsten Command-Typen sind die folgenden: Command-Typ SCREEN BACK CANCEL EXIT ITEM OK STOP Bedeutung Bildschirmbezogenes Kommando, z.B. Laden, Speichern etc. Rückkehr zum vorherigen Bildschirm Dialog-Abbruch Programm-Abbruch Kommando für Elemente eines Bildschirms Bestätigung durch den Benutzer Abbruch der laufenden Operation Tabelle 1: Command-Typen, vergl. [API06] Ein Kommando zum Beenden eines Programms könnte dann wie folgt aussehen: cmdExit=new Command("Beenden",Command.EXIT,1); dispayable.addCommand(cmdExit); dispayable.setCommandListener(new CommandListener(){ public void commandAction(Command c,Displayable d) { if(c==cmdExit) notifyDestroyed(); ... }}); Der letzte Parameter gibt die relative Priorität des Kommandos in Bezug auf die anderen auf dem Bildschirm angezeigten Kommandos an. Dabei stehen niedrige Werte für eine hohe Priorität. Die Anordnung der Elemente auf dem Bildschirm kann durch den Kommandotyp in Verbindung mit der Priorität beeinflusst werden. Die genaue Anordnung ist jedoch geräteabhängig. 15 Kapitel 4: GUI-Programmierung 4.3 High-Level-Display-Komponenten Die High-Level-LCDUI stellt dem Programmierer vier Displayable-Klassen zur Verfügung: • Alert: weist den Benutzer auf besondere Umstände wie Fehler o. Ä. hin • List: bietet dem Benutzer eine Auswahlmöglichkeit • TextBox: ermöglicht dem Benutzer das Lesen und Editieren von Texten • Form: Beliebige Zusammenstellung von Bildern, Texteingabefeldern etc. In der Anwendung SkiGuide aus dem Anhang B werden die nachfolgenden Erläuterungen anhand eines Beispiels verdeutlicht. Das MIDlet bietet die Möglichkeit aus einem List-Objekt zwischen dem Anzeigen der Ski-Regeln, dem Speichern der Urlaubsadresse oder dem Speichern von Aufgaben/ Hinweisen zu wählen. Dabei werden in den Untermenüs alle nachfolgenden Komponenten mindestens einmal verwendet um eine exemplarische Verwendung zu zeigen. 4.3.1 Alert Um den Benutzer bestimmte Hinweise wie Fehler, Warnhinweise oder Bestätigungen zu geben, stellt die High-Level-API die Klasse Alert bereit. Ein Alert beinhaltet einen Titel, einen Hinweistext, möglicherweise ein Bild sowie einen der nachfolgenden AlertTypen. In Abhängigkeit vom Alert Typ kann das Verhalten und das Aussehen variieren. Alert-Typ ALARM CONFIRMATION ERROR INFO WARNING Bedeutung Benachrichtigung wie z. B. ein Wecker Rückmeldung aufgrund einer vollendeten Aktion Fehlermeldung Informativer Hinweis Warnhinweis Tabelle 2: Alert-Typen, vergl. [API06] Der Alert ist das einzige Objekt, dass nach der Anzeige selbstständig auf den nachfolgenden Bildschirm umschalten kann. Hierzu wird mit der Methode setTimeout() die Anzahl der Millisekunden als Anzeigedauer zugeordnet und der Methode display.setCurrent(alert, nextDisplayable) der nachfolgende Bildschirm übergeben. Soll der Alert durch den Aufruf eines Kommandos beendet werden, so erhält die Methode setTimeout() den Parameter Alert.FOREVER. 16 Kapitel 4: GUI-Programmierung Alert alert=new Alert("Hinweis","Das MIDlet ist das erste Mal gestartet worden",null /*kein Image*/,AlertType.INFO); alert.setTimeout(5000); //Anzeigedauer des Alerts display.setCurrent(alert,textbox); //zuerst den Alert anzeigen, Nachfolgebildschirm definieren alert.getType().playSound(display); //Alerttyp-spezifischen Sound abspielen Wird dem Befehl setCurrent() nur das Alert-Objekt übergeben, so erscheint anschließend der vorherige Bildschirm erneut angezeigt. 4.3.2 List List repräsentiert eine Liste von Einträgen, die jeweils aus einem Text und einem opti- onalen Image bestehen. Aus dieser Liste kann der Benutzer je nach Listentyp einen oder mehrere Einträge selektieren. Man unterscheidet die Listentypen IMPLICIT, MULTIPLE und EXCLUSIVE. Mit dem Aufruf list=new List(„Titel“,List.IMPLICIT); wird eine Liste erzeugt, bei der die Selektion eines Listenelements eine frei programmierbare Aktion auslöst. Hierzu wird der Liste mit Hilfe der Methode setSelectCommand() ein Command-Objekt zugeordnet. Im CommandListener wird nach der Selektion eines Elements mit der Methode list.getSelectedIndex() der aktuell markierte Index abgefragt und daraufhin die zugehörige Aktion ausgeführt. Bei der MULTIPLE-List wird dem Benutzer ermöglicht, mehrere Elemente z. B. mit Hilfe einer CheckBox zu selektieren. Die Abfrage der selektierten Elemente geschieht hierbei über die Methode list.isSelected(index), wobei index den Index zwischen 0 und list.size()-1 definiert. Die EXCLUSIVE-List ist ähnlich der MULTIPLE-List, erlaubt allerdings nicht die mehrfache Selektion innerhalb einer Liste. Dem Benutzer wird z. B. durch „Radio Buttons“ nur eine Selektion erlaubt. Um der Liste Elemente anzufügen wird die Methode list.append() verwendet und als Parameter der Name sowie ein optionales Image- Objekt übergeben: list=new List("Lieblingseis",List.MULTIPLE); //erzeuge Liste list.append("Zitroneneis", imgZitrone); //füge Elemente an list.append("Vanilleeis", null /*kein Bild*/); list.append("Himbeereis", imgHimbeere); list.setSelectedIndex(1, true); display.setCurrent(list); 17 Kapitel 4: GUI-Programmierung 4.3.3 TextBox Die Textbox ist ein Bildschirm, der dem Benutzer ermöglicht einen Text einzugeben und/oder zu editieren. Der Text darf eine vorher maximal definierte Länge nicht überschreiten. Für den Fall, dass der Text nicht vollständig auf dem Bildschirm angezeigt werden kann, wird die Textbox automatisch um einen Scrollbalken ergänzt. Um syntaktische Fehleingaben möglichst gering zu halten, besteht die Möglichkeit, die Eingabe durch Konstanten zu beschränken. TextField-Konstante ANY EMAILADDR NUMERIC URL PHONENUMBER Bedeutung keine Beschränkung nur eine E-Mailadresse ist erlaubt nur Ziffern und Minuszeichen sind erlaubt ULR bzw. URI ist erlaubt Ziffern und ggf. „+“ und „-“ Tabelle 3: TextField-Konstanten, vergl. [API06] Die Textbox könnte wie folgt initialisiert werden: TextBox textBox=new TextBox("Titel","Inhalt",100 /*maximale Länge des Inhalts*/,TextField.ANY); String neuerText="Dem Textfeld wird ein neuer Inhalt übergeben"; if(textBox.getMaxSize()>neuerText.length()); textBox.setString("neuer Text"); In diesem Beispiel wird der Inhalt der Textbox von dem Programm selbst geändert. Hierbei ist zu beachten, dass die maximale Länge der Textbox nicht überschritten wird, da es sonst zu einer IllegalArgumentException kommt. 4.3.4 Form Form ist ein Bildschirm-Objekt mit dem mehrere Item-Objekte (Bilder, Texteingabefel- der, Texte, Fortschrittsanzeigen…) gleichzeitig auf dem Bildschirm angezeigt werden können. Falls der Bildschirm zu klein ist, wird das Form-Objekt automatisch um einen Scrollbalken ergänzt. Auf die Anordnung der Items innerhalb der Form hat der Programmierer nur geringen Einfluss. Maßgeblich wird die Anordnung durch die Reihenfolge bestimmt, in der die Items der Form zugewiesen werden. Die Items werden dann zeilenweise von links oben nach rechts unten in die Form eingefügt. Jedoch besteht die Möglichkeit, den Abstand der Items über ein Spacer-Objekt zu steuern. Dies wird durch den Konstruktoraufruf Spacer(mindHöhe,mindBreite) erzeugt und der Form mit dem Befehl 18 Kapitel 4: GUI-Programmierung form.append(spacer) angefügt. mindHöhe bzw. minBreite definieren hierbei den Mindestabstand in Pixel zum nachfolgenden Item-Element der Form. StringItem Der Zweck des StringItems liegt im Anfügen von String-Objekten an ein Form-Objekt. Grundsätzlich besteht das StringItem aus einem Label und einem Text. Das Label ist optional und dem Text vorangestellt. Um es vom Text abzusetzen, wird es vom WTKEmulator in Fett-Schrift dargestellt. Erzeugt wird das StringItem durch den Konstruktoraufruf StringItem(label, text). Ist kein Label erwünscht, so kann auch der Wert null übergeben werden. Die- ses führt jedoch zu demselben Ergebnis wie der Aufruf der überladenen Methode form.append(String). TextField Das TextField ist vergleichbar mit der TextBox und dient dem Benutzer als editierbares Textelement. Im Gegensatz zu TextBox wird es jedoch der Form angefügt. Als Parameter erhält der Konstruktor einen Titel, einen anfänglichen Text oder null, eine maximale Zeichenanzahl sowie einen Zeichen-Constraint: textfield=new TextField("Titel","Inhalt",30,TextField.ANY); form.append(textfield); ImageItem Die Programmierschnittstelle Image bietet die Möglichkeit, Grafiken im Portable Network Graphics – Format (PNG) zu repräsentieren. Dabei wird die Grafik mittels der Methode Image.createImage(“\Skifahrer.png“) in das Programm geladen und in einem Image-Objekt gespeichert. Ist das Image erstmal in das Programm geladen, kann es als ImageItem der Form übergeben werden. Hierzu wird der Konstruktor ImageItem(“Titel“, image, Item.LAYOUT_CENTER, “Alternativtext“) aufgerufen und das resultierende Objekt an die Form übergeben. ChoiceGroup Die ChoiceGroup repräsentiert eine Gruppe von auswählbaren Einträgen. Die Funktionalität ist weitgehend mit der List identisch. Neben den von List bekannten Listenarten IMPLICIT, MULTIPLE und EXCLUSIVE bietet die ChoiceGroup mit POPUP noch eine zusätzliche Variante. POPUP ist vergleichbar mit einer DropDown-Auswahl bei der 19 Kapitel 4: GUI-Programmierung nur das aktuell ausgewählte Element angezeigt wird. Die weiteren Elemente sind jedoch über ein Popup-Menü auswählbar. Erzeugt wird die POPUP-ChoicGroup über den den Konstruktor: ChoiceGroup(„Titel“, ChoiceGroup.POPUP). Das Hinzufügen weiterer Ele- mente wird über die Methode append(“Name“,image) realisiert, wobei image ein optionaler Parameter ist. 20 Kapitel 5: Zusammenfassung 5 Zusammenfassung In den vorangegangenen Kapiteln sind die wesentlichen Grundlagen der Programmierung mit JavaME erläutert worden. Zu Beginn wurden die zur Erstellung von MIDlets notwendigen Umgebungen (JDK, MIDP-RI) und hilfreichen Tools (WTK, EclipseMEPlugin) kurz erläutert sowie die möglichen Wege zur Übertragung und Installation auf dem mobilen Endgerät aufgezeigt. Ein wichtiger Unterschied zu den aus dem JDK bekannten Objekten ist der besondere Lebenszyklus von MIDlets, der sich aus den drei Zuständen pausierend, aktiv und zerstört ergibt. Auf diese Besonderheit konzentriert sich das Kapitel Grundlagen der MIDlet-Programmierung. Es wurde auf die für den Zustandswechsel wichtigen Methoden eingegangen und erklärt, worauf bei der Implementierung zu achten ist. Verdeutlicht werden diese Methoden an dem Beispiel ErstesMIDlet. Bedingt durch die begrenzten Ressourcen der mobilen Endgeräte, sowohl der knappe Speicher und die geringe Prozessorleistung, als auch die kleine Anzeigeeinheit und das fehlende Zeigergerät unterscheiden sich die GUI-Objekte in ihrer Verwendung und ihrem Funktionsumfang von den GUI-Objekten, wie sie aus dem JDK bekannt sind. Aus diesem Grund wurden in dem Kapitel GUI-Programmierung die High-Level-DisplayKomponenten als eine Möglichkeit der GUI-Erzeugung, vorgestellt. Die Verwendung der Komponenten wird in dem MIDlet SkiGuide dargestellt. Da es im Rahmen einer Seminararbeit nicht möglich ist, alle Einzelheiten zum Thema MIDlet-Programmierung zu erwähnen, und sie vielmehr nur einen Überblick geben soll, sei für weitere Informationen insbesondere auf die MID Profile Specification API sowie auf die weiteren Quellen im Literaturverzeichnis verwiesen. 21 Anhang A: MIDlet ErstesMIDlet A MIDlet ErstesMIDlet import import import import import import javax.microedition.midlet.MIDlet; javax.microedition.midlet.MIDletStateChangeException; javax.microedition.lcdui.*; java.util.Timer; java.util.TimerTask; java.util.Date; public class ErstesMIDlet extends MIDlet { private private private private Form gui; int wartezeit; //Wiederaufwachintervall Aktion aktion; //Aktions-Thread zur Ausgabe der Zeit boolean erstmalsGestartet=true; public ErstesMIDlet() { //Einlesen und Speichern der Zeit bis zum resumeRequest String wartezeit=getAppProperty("Wartezeit"); this.wartezeit=Integer.parseInt(wartezeit)*1000; //Anlegen einer Bildschirmausgabe mit Kommando Ende und Pause GUIerstellen(); } private class Aktion extends Thread { public void run() { long startzeit=new Date().getTime(); while(aktion==this) { print("aktive Zeit: "+ (new Date().getTime()startzeit)/1000+"s"); try {sleep(1000);} catch (Exception e) {} } } } protected void startApp() throws MIDletStateChangeException { if (erstmalsGestartet) { //erster Start des MIDlets erstmalsGestartet=false; print("1. Start des MIDlets"); } else { //erneuter Start des MIDlets gui.deleteAll(); print("MIDlet aufgewacht"); } Display.getDisplay(this).setCurrent(gui);//Ressourcen anfordern synchronized (this) //Programmthread starten { aktion=new Aktion(); aktion.start(); } } protected void pauseApp() { synchronized (this) { aktion=null; } //Löschen des Threads new Timer().schedule(new TimerTask() {public void run() {resumeRequest();}}, wartezeit); //Versuch das MIDlet nach "wartezeit" Sekunden wieder neu zu starten } private void pausieren() { pauseApp(); //Freigabe der Ressourcen notifyPaused(); //Mitteilung des Zustandswechsels an die AMS } Anhang A: MIDlet ErstesMIDlet protected void destroyApp(boolean MIDletStateChangeException { synchronized (this) { aktion=null; gui.append("Adiöööö"); } arg0) throws } private void schließen() { try { destroyApp(false); //Freigabe der Ressourcen notifyDestroyed(); //Mitteilung des Zustandswechsels an die AMS } catch (MIDletStateChangeException e) {} } private void GUIerstellen() { gui = new Form("Das erste MIDlet!"); gui.addCommand(new Command("ENDE", Command.EXIT,1)); gui.addCommand(new Command("Pause", Command.STOP ,1)); gui.setCommandListener(new KommandoListener()); gui.setTicker(new Ticker("ErstesMIDlet")); } // Funktion zur Ausgabe des String text auf der GUI private void print(String text) { gui.append(text+"\n"); System.out.println(text); } // CommandListener der das Pausieren und Schließen behandelt private class KommandoListener implements CommandListener { public void commandAction(Command c,Displayable d) { if(c.getCommandType()==Command.EXIT) schließen(); else{ pausieren(); } } } } Anhang B: MIDlet SkiGuide B MIDlet SkiGuide import import import import import java.io.InputStreamReader; javax.microedition.midlet.MIDlet; javax.microedition.midlet.MIDletStateChangeException; javax.microedition.rms.RecordStore; javax.microedition.lcdui.*; public class SkiGuide extends MIDlet implements CommandListener{ Display display; //Bildschirmobjekt Image skifahrer=null; //Exit-Command; Command cmd_exit; Command cmd_safe; //Save-Command; Command cmd_ok; Command cmd_back; Alert ale_welcome; //Willkommensbildschirm List lis_hauptmenü; //Hauptmenü Form for_Adresse; //Adresseingabebildschirm StringItem si_info; //Komponenten des Adresseingabebildschirms TextField tf_Name; TextField tf_Adresse; TextField tf_Ort; TextField tf_TelNr; ChoiceGroup cg_Unterkunftsart; List lis_pistenregeln; //1.Dimension Regelname, 2.Dimension Regeldetails String[][] regeln={ {"Rücksicht auf die anderen Skifahrer","Jeder Skifahrer muss sich so verhalten, dass er keinen anderen gefährdet oder schädigt."}, {"Beherrschung der Geschwindigkeit und der Fahrweise","Jeder Skifahrer muss auf Sicht fahren. Er muss seine Geschwindigkeit und seine Fahrweise seinem Können und den Gelände-, Schnee- und Witterungsverhältnissen sowie der Verkehrsdichte anpassen."}, {"Wahl der Fahrspur","Der von hinten kommende Skifahrer muss seine Fahrspur so wählen, dass er vor ihm fahrende Skifahrer nicht gefährdet."}, {"Überholen","Ueberholt werden darf von oben oder unten, von rechts oder links, aber immer nur mit einem Abstand, der dem überholten Skifahrer für alle seine Bewegungen genügend Raum lässt."}, {"Einfahren und Anfahren", "Jeder Skifahrer, der in eine Skiabfahrt einfahren oder nach einem Halt wieder anfahren will, muss sich nach oben und unten vergewissern, dass er dies ohne Gefahr für sich und andere tun kann."}, {"Anhalten","Jeder Skifahrer muss es vermeiden, sich ohne Not an engen oder unübersichtlichen Stellen einer Abfahrt aufzuhalten. Ein gestürzter Skifahrer muss eine solche Stelle so schnell wie möglich freimachen."}, {"Aufstieg und Abfahrt","Ein Skifahrer, der aufsteigt oder zu Fuß absteigt, muss den Rand der Abfahrt benutzen."}, {"Beachten der Zeichen","Jeder Skifahrer muss die Markierung und die Signalisation beachten."}, {"Hilfeleistung","Bei Unfällen ist jeder Skifahrer zur Hilfeleistung verpflichtet."}, Anhang B: MIDlet SkiGuide {"Ausweispflicht","Jeder Skifahrer, ob Zeuge oder Beteiligter, ob verantwortlich oder nicht, muss im Falle eines Unfalles seine Personalien angeben."}}; Form for_Regel; //Anzeige für Regeldetails TextBox tb_sonstiges; RecordStore rs=null; public SkiGuide() { cmd_back=new Command("Zurück","zum letzten Bild//Initialisierung der Kommandos schirm",Command.BACK,2); cmd_exit=new Command("Beenden","Programm beenden",Command.EXIT,1); cmd_ok=new Command("OK","Auswahl",Command.OK,1); cmd_safe=new Command("Speichern","Speichert die Eingabe",Command.OK,2); try { //lese Image aus dem Stammordner /res ein. skifahrer=Image.createImage("/Skifahrer.png"); } catch (Exception e) { skifahrer=null; } ale_welcome=new Alert("Skiguide","\n This is a skiers guide\n ->> D o n ' t P a n i c <<-",skifahrer,AlertType.INFO); ale_welcome.setTimeout(3000); //setze Anzeigedauer des Alerts } protected void startApp() throws MIDletStateChangeException { if(display==null) { display=Display.getDisplay(this); //Initialisierung Displayobjekt initialisieren(); //Initialisieren der einzelnen Fenster display.setCurrent(ale_welcome, lis_hauptmenü); } try{ //RecordStore Objekt erstellen/laden rs=RecordStore.openRecordStore("SkiGuideDB",true); } catch (Exception e) {} laden(rs); //Laden der Benutzerdaten, falls welche eingegeben wurden } protected void destroyApp(boolean arg0) throws MIDletStateChangeException { //Ressourcen freigeben try { rs.closeRecordStore(); } catch (Exception e) {} } protected void pauseApp() { //Ressourcen freigeben und ein Wiederaufnahmehinweis laden try { rs.closeRecordStore(); } catch (Exception e) {} Alert pause=new Alert("Wilkommen zurück"," ->>W E I T E R G E H T S<<- ",skifahrer,AlertType.INFO); pause.setTimeout(3000); display.setCurrent(pause); } private void initialisieren() { lis_hauptmenü=new List("Hauptmenü",List.IMPLICIT); //Hauptmenü initialisieren Anhang B: MIDlet SkiGuide lis_hauptmenü.setCommandListener(this); //CommandListener für das Hauptmenü definieren lis_hauptmenü.addCommand(cmd_exit); //Kommandos hinzufügen lis_hauptmenü.addCommand(cmd_ok); lis_hauptmenü.setSelectCommand(cmd_ok); lis_hauptmenü.append("Adresse der Unterkunft", null); //Listenelemente anfügen lis_hauptmenü.append("Pistenregeln", null); lis_hauptmenü.append("Noch zu merken", null); //Initialisierung der Form Adresse mit allen Komponenten for_Adresse=new Form("Urlaubsadresse"); for_Adresse.setCommandListener(this); for_Adresse.addCommand(cmd_back); for_Adresse.addCommand(cmd_safe); si_info=new StringItem(null/*kein Label*/,"Speichern Sie hier die Adresse Ihrer Unterkunft ab, um für den Fall der Fälle vorbereitet zu sein."); for_Adresse.append(si_info); for_Adresse.append(new Spacer(5,5)); cg_Unterkunftsart=new ChoiceGroup("Unterkunftsart:",ChoiceGroup.POPUP); cg_Unterkunftsart.append("Hotel", null /*kein Image*/); cg_Unterkunftsart.append("Ferienwohnung", null); for_Adresse.append(new Spacer(5,5)); for_Adresse.append(cg_Unterkunftsart); tf_Name=new TextField("Name: ","Unterkunftsname",30,TextField.ANY); for_Adresse.append(tf_Name); tf_Adresse=new TextField("Adresse: ","Straße, Nr.",30,TextField.ANY); for_Adresse.append(tf_Adresse); tf_Ort=new TextField("PLZ, Ort:","12345 Musterstadt",40,TextField.ANY); for_Adresse.append(tf_Ort); tf_TelNr=new TextField("TelefonNr.:","0",20,TextField.PHONENUMBER); for_Adresse.append(tf_TelNr); //Initialisierung der Übersicht über die Pistenregeln lis_pistenregeln=new List("Pistenregeln",List.IMPLICIT); lis_pistenregeln.setCommandListener(this); lis_pistenregeln.addCommand(cmd_back); lis_pistenregeln.addCommand(cmd_ok); lis_pistenregeln.setSelectCommand(cmd_ok); for(int i=0;i<regeln.length;i++) { lis_pistenregeln.append(regeln[i][0], null); } //Fenster für die Erläuterung der Regel for_Regel=new Form("1. Regel"); for_Regel.setCommandListener(this); for_Regel.addCommand(cmd_back); for_Regel.setTicker(new Ticker("Regeltext")); for_Regel.append(new Spacer(for_Regel.getWidth(),5)); for_Regel.append(new ImageItem("",skifahrer,ImageItem.LAYOUT_CENTER,"")); Anhang B: MIDlet SkiGuide for_Regel.append(new Spacer(0,for_Regel.getHeight()/2-5//Die detailierte Regel soll in der skifahrer.getHeight())); unteren Hälfte stehen for_Regel.append(new StringItem(null,"Details")); //Fenster für sonstige Informationen tb_sonstiges=new TextBox("Nicht vergessen:","In diesem Textfeld koennen Sie wichtige Informationen speichern!",300,TextField.ANY); tb_sonstiges.setCommandListener(this); tb_sonstiges.addCommand(cmd_back); tb_sonstiges.addCommand(cmd_safe); } private void shutdown() throws MIDletStateChangeException { destroyApp(false); notifyDestroyed(); } public void commandAction(Command c, Displayable d) { if (c==cmd_exit) try { shutdown(); } catch (MIDletStateChangeException e) {} //Hauptmenuebefehle else if (d==lis_hauptmenü&&(c==cmd_ok)) { if(lis_hauptmenü.getString(lis_hauptmenü.getSelectedIndex()).equals( "Adresse der Unterkunft")) display.setCurrent(for_Adresse); else if(lis_hauptmenü.getSelectedIndex()==1) //Skiregeln sollen angezeigt werden display.setCurrent(lis_pistenregeln); else display.setCurrent(tb_sonstiges); } //Adresseingabe-Befehle else if(d==for_Adresse) { if(c==cmd_back) { display.setCurrent(lis_hauptmenü); laden(rs);} else if(c==cmd_safe) { speichern(rs); commandAction(cmd_back, d); } } //lis_pistenregeln-Befehle else if(d==lis_pistenregeln) { if(c==cmd_back) display.setCurrent(lis_hauptmenü); else if(c==cmd_ok) { int ausgewählt=lis_pistenregeln.getSelectedIndex(); for_Regel.setTicker(new Ticker(regeln[ausgewählt][0])); //Ticker setzen for_Regel.setTitle(ausgewählt+1+". Regel"); ((StringItem)for_Regel.get(3)).setText(regeln[ausgewählt][1]); //Text in der Form setzen display.setCurrent(for_Regel); } } Anhang B: MIDlet SkiGuide //Befehl des Fensters für Regeldetails else if(d==for_Regel) display.setCurrent(lis_pistenregeln); //Befehle für das Fenster "Noch zu merken" else if(d==tb_sonstiges) { if(c==cmd_back) { display.setCurrent(lis_hauptmenü); laden(rs); } else if(c==cmd_safe) { speichern(rs); commandAction(cmd_back, d); } } } /** * Die Funktion läd Daten aus der Datenbank. */ public void laden(RecordStore rs) { try { //ließt Daten aus der Datenbank cg_Unterkunftsart.setSelectedIndex(Integer.parseInt(bytetoString(rs. getRecord(1))), true); tf_Name.setString(bytetoString(rs.getRecord(2))); tf_Adresse.setString(bytetoString(rs.getRecord(3))); tf_Ort.setString(bytetoString(rs.getRecord(4))); tf_TelNr.setString(bytetoString(rs.getRecord(5))); tb_sonstiges.setString(bytetoString(rs.getRecord(6))); } catch(Exception e){} } /** * Funktion castet jedes Byte eins byte-Array zu einem Character und fügt es dem return String an. * @param a Byte-Array, welches umgewandelt werden soll * @return String der gecasteten Byteelemente */ public String bytetoString(byte[]a) { String b=""; for(int i=0;i<a.length;i++) b+=(char)a[i]; return b; } public void speichern(RecordStore rs) { try { byte[] a; if(rs.getNumRecords()==0) //falls noch kein Record gespeichert wurde, erzeuge leere Records for(int i=0;i<6;i++) rs.addRecord(null,0,0); a=(cg_Unterkunftsart.getSelectedIndex()+"").getBytes(); rs.setRecord(1,a , 0, a.length); a=tf_Name.getString().getBytes(); rs.setRecord(2,a , 0, a.length); a=tf_Adresse.getString().getBytes(); rs.setRecord(3,a , 0, a.length); a=tf_Ort.getString().getBytes(); rs.setRecord(4,a , 0, a.length); a=tf_TelNr.getString().getBytes(); rs.setRecord(5,a , 0, a.length); a=tb_sonstiges.getString().getBytes(); rs.setRecord(6,a , 0, a.length); Anhang B: MIDlet SkiGuide }catch (Exception e) {} } /** * Die Methode einlesen ließt von der im Parameter übergebenen Datei ein und speichert die Datei Zeilenweise in Arrayelementen * @param datei Dateiname als String * @return Eingelesener Text. wobei alle ungeraden Zeilen in [x][0] gespeichert werden und alle gerade in [x][1] */ private String[][] einlesen(String datei) { InputStreamReader is =new InputStreamReader(getClass().getResourceAsStream(datei)); String[][] a=null; try { int Anzahl=0; while(is.ready()) //Ließ die Anzahl der Zeilen ein { int c=is.read(); if(c==13) { is.read(); Anzahl++; } } Anzahl=(Anzahl+1)/2; is.reset(); a=new String[Anzahl][2]; for(int g=0;g<a.length;g++) //Initialisiere das Array mit dem leeren String a[g][0]=a[g][1]=""; int i=0, j=0; while(is.ready()) { int c=is.read(); if(c==13) //kommt ein Zeilenumbruch gehe zum nächsten Arrayelement; { is.read(); i+=(++j/2); j%=2; } //ansonsten füge das nächste Zeichen an das aktuelle else ArrayElement an a[i][j]+=(char)c+""; } } catch(Exception e){e.printStackTrace();} return a; } } Anhang B: MIDlet SkiGuide Literaturverzeichnis [Sc04] Klaus-Dieter Schmatz: Java 2 Micro Edition, 1. Aufl., dpunkt.verlag, 2004. [To02] Kim Topley: J2ME in a Nutshell, 1st. ed., O’Reilly, 2002. [BM06] Ulrich Breymann, Heiko Mosemann: Java ME - Anwendungsentwicklung für Handys, PDA und Co., 2. erweiterte Aufl., Hanser, 2006 [UG04] User’s Guide: J2ME Wireless Toolkit 2.2, Sun Microsystems, 2004 [API06] MID Profile Specification API, Version 2.0, Sun Microsystems, 2006 [JDK06] Sun Microsystems, Inc.: Java SE Downloads - Previous Release - JDK 5 http://java.sun.com/j2se/1.5.0/download.jsp, Abrufdatum: 18.10.2006 [MR06] Sun Microsystems, Inc.: Mobile Information Device Profile http://java.sun.com/products/midp/, Abrufdatum: 18.10.2006 [Ec06a] Eclipse Foundation Web Site: Eclipse downloads http://www.eclipse.org/downloads/, Abrufdatum: 20.10.2006 [Ec06b] Craig Setera: EclipseME - Installation http://eclipseme.org/docs/installation.html, Abrufdatum: 20.10.2006