Einfache GUIsDieses Tutorial zeigt, wie JControl-Anwendungen mit einer grafischen Benutzeroberfläche auf Grundlage der Display-Klasse | |
Die Klasse jcontrol.io.Display
bietet viele einfache grafische Grundfunktionen, die z.B. das Setzen einzelner Pixel ermöglichen oder das Zeichnen von Linien, Rechtecken oder auch ganzen Bildern. Einfache grafische Benutzeroberflächen sind damit leicht realisierbar. Das oben gezeigte Beispiel aus dem SystemSetup wurde beispielsweise damit erstellt: Das ganze Menü besteht lediglich aus einem einzelnen Bild (JCIF), das zuvor mit dem Grafik-Editor JControl/PictureEdit erstellt wurde. Wird ein einzelner Menüpunkt (dargestellt durch die Icons) optisch selektiert, so braucht der Ausschnitt für das dazugehörige Icon lediglich noch einmal neu gezeichnet zu werden - nur eben invertiert.
Zwar erscheint die Klasse jcontrol.io.Display
im Vergleich mit einem kompletten GUI-Framework vielleicht etwas rudimentär, aber wie man noch sehen wird läßt sich mit ihr ohne allzuviel Aufwand treiben zu müssen eine ansprechende grafische Benutzeroberfläche realisieren. Wie das funktioniert, zeigt das nun folgende Tutorial. Um es aktiv nachvollziehen zu können, ist die Installation der JControl/IDE sowie ein JControl-Gerät mit grafischem Display erforderlich.
Download: http://www.jcontrol.org/examples/UIDemo.zip Dieses Tutorial mit allen Quelltexten und Ressourcen als Projekt für die JControl/IDE downloaden (als ZIP-Archiv)
Die in diesem Tutorial vorgestellte grafische Benutzeroberfläche besteht prinzipiell aus zwei Hierarchieebenen: Dem Hauptmenü sowie mehreren einzelnen Untermenüs. Da das Hauptmenü in der Regel nur wenige Menüpunkte enthält, spricht nichts dagegen, diese einzeln und bildschirmfüllend darzustellen. Auf diese Weise steht mehr Fläche für die Gestaltung der Menüpunkte zur Verfügung und die jeweilige Funktion läßt sich leichter auf einen Blick erkennen - was vor allem aus ergonomischer Sicht ein Vorteil ist. Bild 1 zeigt den Entwurf eines Hauptmenüs, das für die Bedienung mit einer Joystick-Tastatur entworfen wurde. Es handelt sich übrigens dabei um die grafische Benutzeroberfläche für ein Gerät zur Analyse der Übertragungseigenschaften von Signalen, die über das ganz normale Stromversorgungsnetz versendet werden (JControl/PLUI, ein sog. Power-Line-Analyzer) - aber das sei nur am Rande bemerkt.
Die Pfeile an den Bildschirmrändern sollen dem Benutzer zur besseren Orientierung dienen und zeigen an, in welche Richtung sich die Symbole bei Betätigung der Joystick-Tastatur verschieben werden. Bei der abgebildeten Anwendung ist der Menüpunkt "System Settings" der letzte Eintrag des Hauptmenüs, weshalb kein Pfeil nach unten angezeigt wird. Ein Tastendruck nach oben springt zum vorhergehenden Hauptmenüpunkt und ein Tastendruck auf Auswahl (mittlere Taste) wählt den angezeigten Menüpunkt aus.
In Bild 2 ist ein Untermenü dargestellt. Um mehr als einen Menüpunkt gleichzeitig anzeigen zu können, wählen wir für die einzelnen Untermenüs kleinere Piktogramme als wir es im Hauptmenü getan haben. Das Untermenü in Bild 2 hat sechs Einträge und passt somit genau auf das Display. Für Untermenüs mit noch mehr Einträgen könnte eine Scroll-Funktion implementiert werden, die den Bildschriminhalt nach links weiterschrebt wenn der siebente Menüpunkt angezeigt werden soll.
Bevor wir mit der Implementierung der einzelnen Menüs beginnen, müssen wir zunächst die Menüstruktur festlegen und entsprechende Piktogramme entwerfen. Für den Entwurf der Piktogramme bietet sich das Werkzeug JControl/PictureEdit an, das in die JControl/IDE integriert ist und im Tutorial Verwenden von ROM-Ressourcen: Bilder und Schriftarten bereits vorgestellt wurde.
Für unser Beispiel legen wir nun die folgende Menüstruktur fest:
Mit dem Werkzeug "JControl/PictureEdit" zeichnen wir nun vier Bilder. Diese können wir in einem Projekt über das Kontext-Menü (rechte Maustaste -> neues Bild erstellen) anlegen. Wer sich die Mühe ersparen möchte, kann die hier gezeigten Dateien auch direkt downloaden:
Um die Anwendung abzurunden, soll noch ein "Splashscreen" nach dem Einschalten angezeigt werden (Datei splashscreen.jcif
.) Außerdem gibt die Bilddatei background.jcif
den Haupt- und Untermenüs einen angemessenen grafischen Rahmen. Die Bilddateien up.jcif
, down.jcif
, left.jcif
, right.jcif
sowie big_right.jcif
stellen die Richtungen dar, in die der Joystick bewegt werden kann.
Um die Anwendung übersichtlich und leicht erweiterbar zu halten, wird sie in vier einzelne Klassen aufgeteilt: Die Hauptklasse UIDemo
(für die Initialisierung), die innere Klasse UIDemo$MainMenu
(sorgt für das Hauptmenü), sowie die beiden inneren Klassen UIDemo$ExamplesMenu
und UIDemo$SystemMenu
(für die Untermenüs). Übrigens werden bei der Programmiersprache Java innere Klasse immer durch ein "$"-Zeichen im Namen gekennzeichnet.
In diesem Abschnitt wollen wir nun die Hauptklasse unserer Anwendung implementieren. Betrachten Sie dazu das nachfolgende Listing der Klasse UIDemo
. Es enthält zum einen den Konstruktor der Klasse und zum anderen eine Methode init()
, die einmal bei der Instantiierung eines Objektes aufgerufen wird. Dabei werden die Objekte für die Ansteuerung des LC-Displays (jcontrol.io.Display
), der Tastatur (jcontrol.io.Keyboard
) sowie des Buzzers (jcontrol.io.Buzzer
) angelegt. Außerdem wird hier ein Begrüßungsbildschirm splashscreen.jcif
für die Dauer einer Sekunde angezeigt.
Die eigentliche "Logik" für die menügesteuerte Benutzerführung ist im Konstruktor implementiert: Eine Endlosschleife (eingeleitet durch die Befehlssequenz for (;;)
) ruft fortwährend die Methode choose
aus der Klasse UIDemo$MainMenu
auf, um danach anhand des Rückgabewertes zu entscheiden, in welches Untermenü verzweigt werden soll (switch
-Anweisung). Die Methode choose
arbeitet übrigens blockierend, was bedeutet, daß ihr Aufruf die Programmausführung solange anhält, bis ein genz bestimmtes Ereignis eingetreten ist: Ein Tastendruck.
1 | import java.io.IOException; |
2 | |
3 | import jcontrol.io.Buzzer; |
4 | import jcontrol.io.Display; |
5 | import jcontrol.io.Keyboard; |
6 | import jcontrol.io.Resource; |
7 | import jcontrol.lang.ThreadExt; |
8 | |
9 | /** |
10 | * UIDemo shows how to create a graphical user interface |
11 | * with JControl. |
12 | * |
13 | * <p>(C) DOMOLOGIC Home Automation GmbH 2003-2004</p> |
14 | */ |
15 | public class UIDemo { |
16 | static Keyboard keys; |
17 | static Display lcd; |
18 | static Buzzer buzzer; |
19 | |
20 | /** |
21 | * Inits the UIDemo user interface. Calls UIDemo$MainMenu |
22 | * to draw the main menu and calls the submenu classes |
23 | * on user's command. |
24 | */ |
25 | public UIDemo() { |
26 | int select = 0; |
27 | |
28 | // init |
29 | init(); |
30 | |
31 | UIDemo$MainMenu mm = new UIDemo$MainMenu(); |
32 | |
33 | // mainloop |
34 | for (;;) { |
35 | mm.draw(); |
36 | select = mm.choose(select); |
37 | switch (select) { |
38 | case 0: |
39 | new UIDemo$ExamplesMenu(); |
40 | break; |
41 | case 1: |
42 | new UIDemo$SystemMenu(); |
43 | break; |
44 | default: break; |
45 | } |
46 | } |
47 | } |
48 | |
49 | |
50 | /** |
51 | * Inits class variables, shows splashscreen |
52 | */ |
53 | private void init() { |
54 | keys = new Keyboard(); // get keyboard |
55 | lcd = new Display(); // get display |
56 | buzzer = new Buzzer(); // get buzzer |
57 | |
58 | // show splashscreen |
59 | try { |
60 | lcd.drawImage(new Resource("splashscreen.jcif"), 0,0); |
61 | } catch (java.io.IOException e) {} |
62 | |
63 | // wait a while |
64 | try { |
65 | ThreadExt.sleep(1000); |
66 | } catch (InterruptedException e) {} |
67 | |
68 | lcd.clearDisplay(); |
69 | } |
70 | |
71 | |
72 | /** |
73 | * Main method. Program execution starts here. |
74 | */ |
75 | public static void main(String[] args) { |
76 | new UIDemo(); |
77 | } |
Der obenstehende Quelltext ruft die Methoden draw()
und choose(int)
der Klasse UIDemo$MainMenu
auf. Diese wird im folgenden Abschnitt implementiert.
Im letzten Abschnitt haben wir gesehen, dass die Klasse UIDemo
die Methoden draw()
und choose(int)
aus der Klasse UIDemo$MainMenu
aufruft, die nun implementiert werden soll.
Die Aufgabe von draw()
ist, das Hintergrundbild zu zeichnen. Eigentlich ist diese Aufgabe so trivial, dass sie gar nicht in eine eigene Methode gekapselt werden müsste. Allerdings wäre es denkbar, die Methode in einem späteren Entwicklungsstadium der Software zu erweitern.
Die Methode choose(int)
besteht aus einer Schleife, in der das Piktogramm des jeweils angezeigten Hauptmenüs gezeichnet und dann auf eine Tastatureingabe gewartet wird. Durch Drücken eines Tasters wird die Schleife verlassen (Befehl break mainloop;
) und die Nummer des ausgewählten Menüpunktes zurückgegeben. Das folgende Listing zeigt die genaue Implementierung der beiden Methoden:
1 | import java.io.IOException; |
2 | |
3 | import jcontrol.io.Buzzer; |
4 | import jcontrol.io.Display; |
5 | import jcontrol.io.Keyboard; |
6 | import jcontrol.io.Resource; |
7 | import jcontrol.lang.ThreadExt; |
8 | |
9 | /** |
10 | * UIDemo shows how to create a graphical user interface |
11 | * with JControl. |
12 | * |
13 | * <p>(C) DOMOLOGIC Home Automation GmbH 2003-2004</p> |
14 | */ |
15 | public class UIDemo { |
16 | static Keyboard keys; |
17 | static Display lcd; |
18 | static Buzzer buzzer; |
19 | |
20 | /** |
21 | * Inits the UIDemo user interface. Calls UIDemo$MainMenu |
22 | * to draw the main menu and calls the submenu classes |
23 | * on user's command. |
24 | */ |
25 | public UIDemo() { |
26 | int select = 0; |
27 | |
28 | // init |
29 | init(); |
30 | |
31 | UIDemo$MainMenu mm = new UIDemo$MainMenu(); |
32 | |
33 | // mainloop |
34 | for (;;) { |
35 | mm.draw(); |
36 | select = mm.choose(select); |
37 | switch (select) { |
38 | case 0: |
39 | new UIDemo$ExamplesMenu(); |
40 | break; |
41 | case 1: |
42 | new UIDemo$SystemMenu(); |
43 | break; |
44 | default: break; |
45 | } |
46 | } |
47 | } |
48 | |
49 | |
50 | /** |
51 | * Inits class variables, shows splashscreen |
52 | */ |
53 | private void init() { |
54 | keys = new Keyboard(); // get keyboard |
55 | lcd = new Display(); // get display |
56 | buzzer = new Buzzer(); // get buzzer |
57 | |
58 | // show splashscreen |
59 | try { |
60 | lcd.drawImage(new Resource("splashscreen.jcif"), 0,0); |
61 | } catch (java.io.IOException e) {} |
62 | |
63 | // wait a while |
64 | try { |
65 | ThreadExt.sleep(1000); |
66 | } catch (InterruptedException e) {} |
67 | |
68 | lcd.clearDisplay(); |
69 | } |
70 | |
71 | |
72 | /** |
73 | * Main method. Program execution starts here. |
74 | */ |
75 | public static void main(String[] args) { |
76 | new UIDemo(); |
77 | } |
Damit wäre das Hauptmenu fertig. Was noch bleibt ist die Umsetzung der beiden Untermenüs (SystemMenu und ExamplesMenu), womit wir uns in den folgenden beiden Abschnitten beschäftigen wollen.
Dieser Abschnitt befasst sich mit der Implementierung des ersten Untermenüs "SystemMenu". Dazu wird im Wesentlichen das Bild des jeweiligen Untermenüs auf das LCD gezeichnet. Der vom Benutzer ausgewählte Untermenüpunkt soll durch eine Invertierung kenntlich gemacht werden. Der folgende Quelltext zeigt die Implementierung dieser Funktionalität anhand der Klasse UIDemo$SystemMenu
. An den markierten Stellen können Befehle eingefügt werden, die nach Auswahl eines Menüpunkts ausgeführt werden sollen. Unser Beispiel zeigt den ausgewählten Menüpunkt für 1 Sekunde als String auf dem Display an.
260 | /** |
261 | * Subclass that draws the system settings submenu. |
262 | */ |
263 | class UIDemo$SystemMenu { |
264 | public UIDemo$SystemMenu() { |
265 | int select = 0; |
266 | |
267 | loop:for (;;) { |
268 | |
269 | UIDemo.lcd.clearRect(0,10,90,7); |
270 | UIDemo.lcd.clearRect(1,18,126,45); |
271 | UIDemo.lcd.drawString("System Settings", 0, 10); |
272 | |
273 | select = choose(select); |
274 | switch (select) { |
275 | case 0: // insert code for menu item 1 here |
276 | case 1: // insert code for menu item 2 here |
277 | case 2: // insert code for menu item 3 here |
278 | case 3: // insert code for menu item 4 here |
279 | case 4: // insert code for menu item 5 here |
280 | UIDemo.lcd.clearRect(1,18,126,45); |
281 | UIDemo.lcd.drawString("Selected menu item: ".concat( |
282 | String.valueOf(select+1)), 10,20); |
283 | try { |
284 | ThreadExt.sleep(1000); |
285 | } catch (InterruptedException e) {} |
286 | case 5: return; |
287 | default: break; |
288 | } |
289 | } |
290 | } |
291 | |
292 | /** |
293 | * Draws system settings menu and highlights user's choice. |
294 | * @param select preselection |
295 | * @return int selected menu item |
296 | */ |
297 | int choose(int select) { |
298 | int oldselect = -1; |
299 | final int images = 5; |
300 | Display lcd = UIDemo.lcd; |
301 | |
302 | // draw menu |
303 | try { |
304 | lcd.drawImage(new Resource("syssettingschooser.jcif"), |
305 | 14, 23); |
306 | } catch (java.io.IOException e) {} |
307 | // left arrow |
308 | try { |
309 | lcd.drawImage(new Resource("left.jcif"), 1, 31); |
310 | } catch (IOException e) {} |
311 | |
312 | mainloop:for (;;) { |
313 | if (select!=oldselect) { |
314 | // set selection |
315 | lcd.setDrawMode(Display.XOR); |
316 | if (oldselect!=-1) |
317 | lcd.fillRect(14+oldselect*20, 23, 20, 22); |
318 | lcd.fillRect(14+select*20, 23, 20, 22); |
319 | oldselect = select; |
320 | lcd.setDrawMode(Display.NORMAL); |
321 | } |
322 | |
323 | switch (UIDemo.keys.read()) { |
324 | case 'u': |
325 | case 'U': |
326 | case 'L': |
327 | if (select == 0) { |
328 | select = images; |
329 | break mainloop; |
330 | } |
331 | else select--; |
332 | break; |
333 | case 'd': |
334 | case 'D': |
335 | case 'R': |
336 | if (select<images-1) select++; |
337 | break; |
338 | case 'S': |
339 | break mainloop; |
340 | } |
341 | } |
342 | return select; |
343 | } |
344 | } |
Für die Vollständigkeit dieser Demo-Applikation fehlt nun lediglich noch das Untermenu ExamplesMenu, welches im nächsten Abschnitt implementiert wird.
Die Implementierung des "Examples"-Untermenüs wäre eigentlich die gleiche wie bei den "System Settings", wenn nicht das "Examples"-Untermenü mehr Einträge hätte als gleichzeitig auf das Display passen. Abhilfe schafft der Befehl drawImage()
der Systemklasse jcontrol.io.Display
, der durch Angabe zusätzlicher Parameter ermöglicht, einen beliebigen Ausschnitt einer Bilddatei anzuzeigen. So können wir das Bild mit den Menüpunkten auf dem Bildschirm horizontal hin- und herschieben, während der Benutzer durch die Einträge scrollt. Dieses Vorgehen ist übrigens wesentlich effizienter als jeden Menüpunkt in einer eigenen Bilddatei abzulegen und das Menü "manuell" -- Eintrag für Eintrag -- auf das Display zu zeichnen.
Der folgende Quelltext zeigt die Implementierung der Klasse UIDemo$ExamplesMenu
, die zum Großteil der Implementierung von UIDemo$SystemMenu
entspricht. Der zusätzliche Code für die Scroll-Funktion ist farblich hervorgehoben.
152 | /** |
153 | * Subclass that draws the examples submenu. |
154 | */ |
155 | class UIDemo$ExamplesMenu { |
156 | public UIDemo$ExamplesMenu() { |
157 | int select = 0; |
158 | |
159 | loop:for (;;) { |
160 | |
161 | UIDemo.lcd.clearRect(0,10,90,7); |
162 | UIDemo.lcd.clearRect(1,18,126,45); |
163 | UIDemo.lcd.drawString("Examples", 0, 10); |
164 | |
165 | select = choose(select); |
166 | switch (select) { |
167 | case 0: // insert code for menu item 1 here |
168 | case 1: // insert code for menu item 2 here |
169 | case 2: // insert code for menu item 3 here |
170 | case 3: // insert code for menu item 4 here |
171 | case 4: // insert code for menu item 5 here |
172 | case 5: // insert code for menu item 6 here |
173 | case 6: // insert code for menu item 7 here |
174 | case 7: // insert code for menu item 8 here |
175 | UIDemo.lcd.clearRect(1,18,126,45); |
176 | UIDemo.lcd.drawString("Selected menu item: ".concat( |
177 | String.valueOf(select+1)), |
178 | 10,20); |
179 | try { |
180 | ThreadExt.sleep(1000); |
181 | } catch (InterruptedException e) {} |
182 | case 8: return; |
183 | default: break; |
184 | } |
185 | } |
186 | } |
187 | |
188 | |
189 | /** |
190 | * Trace user choices in network menu |
191 | */ |
192 | int choose(int select) { |
193 | int shift = 0, oldselect = -1, oldshift = -1; |
194 | final int images = 8; |
195 | Display lcd = UIDemo.lcd; |
196 | |
197 | mainloop:for (;;) { |
198 | if (select-shift>5) shift = select-5; |
199 | if (select-shift<0) shift = select; |
200 | if (shift!=oldshift) { |
201 | // the icons to choose |
202 | try { |
203 | lcd.drawImage(new Resource("exampleschooser.jcif"), |
204 | 4, 23, 120, 40, 20*shift,0); |
205 | } catch (java.io.IOException e) {} |
206 | if (oldshift==2) { |
207 | // delete obsolete pixels |
208 | lcd.clearRect(1,44,4,19); |
209 | lcd.clearRect(123,44,4,19); |
210 | } |
211 | // left arrow |
212 | try { |
213 | lcd.drawImage(new Resource("left.jcif"), 1, 31); |
214 | } catch (IOException e) {} |
215 | if (shift<(images-6)) { |
216 | // right arrow |
217 | try { |
218 | lcd.drawImage(new Resource("right.jcif"), 124, 31); |
219 | } catch (IOException e) {} |
220 | } else { |
221 | // no arrow |
222 | lcd.clearRect(124,30,3,9); |
223 | } |
224 | } |
225 | if (select!=oldselect) { |
226 | // set selection |
227 | lcd.setDrawMode(Display.XOR); |
228 | if (oldselect!=-1 && oldshift==shift) |
229 | lcd.fillRect(4+(oldselect-shift)*20, 23, 20, 22); |
230 | lcd.fillRect(4+(select-shift)*20, 23, 20, 22); |
231 | oldselect = select; |
232 | lcd.setDrawMode(Display.NORMAL); |
233 | oldshift = shift; |
234 | } |
235 | |
236 | switch (UIDemo.keys.read()) { |
237 | case 'u': |
238 | case 'U': |
239 | case 'L': |
240 | if (select == 0) { |
241 | select = images; |
242 | break mainloop; |
243 | } |
244 | else select--; |
245 | break; |
246 | case 'd': |
247 | case 'D': |
248 | case 'R': |
249 | if (select<images-1) select++; |
250 | break; |
251 | case 'S': |
252 | break mainloop; |
253 | } |
254 | } |
255 | return select; |
256 | } |
257 | } |
Nun sind alle Klassen und Methoden die für dieses Beispiel benötigt werden vorhanden. Der nächste Abschnitt beschreibt, wie das fertige UIDemo getestet wird.
Zum Testen der UIDemo: Laden Sie die Datei UIDemo.zip herunter und entpacken Sie das ZIP-Archiv in ein leeres Verzeichnis. Es enthält alle in diesem Tutorial vorgestellten Dateien, sowie eine JControl/IDE-Projektdatei . Öffnen Sie die Projektdatei mit der JControl/IDE und klicken Sie auf den Simulations-Button. Daraufhin öffnet sich der JControl-Simulator mit der Darstellung des ausgewählten JControl-Moduls und des grafischen Benutzermenüs (ähnlich wie in Bild 3).