Look-and-Feel

Transcrição

Look-and-Feel
Interaktive Systeme A
Prof. Dr. Andreas M. Heinecke
Fachhochschule Gelsenkirchen
Fachbereich Informatik
Skript zur Vorlesung ISYA
Interaktive Systeme A: Fortgeschrittene Benutzungsoberflächenprogrammierung
im Sommersemester 2010
Stand 04.08.2010
Textelemente (Textbearbeitung / html)
2
Inhalt
1 2 3 Textelemente (Textbearbeitung / html)
1.1 Mehrzeilige Eingaben (JTextArea)
4 1.2 Textfeldeigenschaften (JTextComponent)
4 1.3 JEditorPane
4 1.4 JTextPane
4 1.5 Modelle
4 1.6 Editorkit
4 1.7 Caret und Highlite
4 1.8 Text, Text mit Attributen, HTML
4 Undo / Redo
5 2.1 Einfache Undo / Redo-Implementierung
5 2.2 Fortgeschrittenes Undo / Redo
6 2.3 Theoretischer Exkurs: Exploratives Undo / Redo
9 Internationalisierung und Lokalisierung
11 3.1 Exkurs: I18N / L10N an Beispielen aus der Spieleindustrie
11 3.2 Ressourcen
16 3.2.1 Einfache Internationalisierung
16 3.2.2 ResourceBundle
18 3.2.3 PropertyResourceBundle
20 3.2.4 ListResourceBundle
20 3.2.5 Planung von ResourceBundles
21 3.3 Lokale Formate / lokale Einheiten
22 3.3.1 Zahlenformate
22 3.3.2 Datumsangaben und Uhrzeiten
23 3.3.3 Maßeinheiten
23 3.4 4 4 Fortgeschrittene Internationalisierung
24 3.4.1 Zusammengesetzte Texte / Meldungssignalisierung
24 3.4.2 Der Umgang mit dem Plural
27 Accessibility
30 4.1 Assistive Technologien
30 4.2 Aufbereitung der Anwendung
31 4.2.1 Die Java Accessibility API
32 4.2.2 Die IBM Java Accessibility Checkliste
33 4.2.3 Testen auf Accessibility
34 4.2.4 Ein einfaches Swing-Programm
35 2
Textelemente
Mehrzeilige
(Textbearbeitung
Eingaben (JTextArea)3
/ html)
3
5 Drag and Drop
44 6 Clipboard / Zwischenablage
45 7 Look-and-Feel
46 7.1 8 46 7.1.1 Das „alte“ Java Look-and-Feel (Metal Look-and-Feel)
46 7.1.2 Betriebsystemen nachempfundene Look-and-Feels
47 7.1.3 Skinnable Look-and-Feel (Synth Look-and-Feel)
48 7.1.4 Das „neue“ Java Look-and-Feel (Nimbus Look-and-Feel)
48 7.2 Wechsel des Look-and-Feels
49 7.3 Exkurs: Erstellen eines eigenen Skins
50 7.4 Exkurs: Schreiben eines eigenen Look-and-Feels
64 Anhang
8.1 3
Das Konzept des Pluggable-Look-and-Feel (PLAF)
Barrierefreie Informationstechnik-Verordnung (BITV)
76 76 Textelemente (Textbearbeitung / html)
4
1
Textelemente (Textbearbeitung / html)
Siehe Vorlesung vom 09., 16. und 23.04.2009
1.1
Mehrzeilige Eingaben (JTextArea)
1.2
Textfeldeigenschaften (JTextComponent)
1.3
JEditorPane
1.4
JTextPane
1.5
Modelle
1.6
Editorkit
1.7
Caret und Highlite
1.8
Text, Text mit Attributen, HTML
4
Einfache Undo / Redo-Implementierung5
Undo / Redo
5
2
Undo / Redo
Inhalt
zu ergänzen.
Lernziele
–
zu ergänzen.
Das Undo/Redo-Konzept basiert auf dem Command-Pattern. In Java wird es sehr
komfortabel durch den UndoManager umgesetzt. Dieser sammelt einzelne Aktionen,
im Fall von Benutzereingaben z.B. jedes getippte Zeichen, wobei die Anzahl der zu
speichernden Aktionen ist beschränkt ist, sich aber anpassen lässt.
Die Implementierung von Undo und Redo erfolgt in zwei Schritten: Als erstes müssen
sich die rückgängig zu machenden Änderungen gemerkt werden, als zweites muss in
der Benutzeroberfläche vorgesehen werden, auf die Undo- und Redo-Kommandos
zuzugreifen.
2.1
Einfache Undo / Redo-Implementierung
Als Beispiel für einen einfachen Undo/Redo-Mechanismus soll hier ein Textfeld zum
Einsatz kommen, in dem die Benutzereingaben rückgängig gemacht und auch
wiederhergestellt werden können. Dazu wird der anfangs erzeugte UndoManager mit
dem Document-Objekt des Textfeldes verbunden. Das Document informiert den
UndoManager nun über Änderungen im Textfeld, der diese bis zu dem angegebenen
Limit (Standard: 100) speichert. Über einen Menüeintrag kann der Benutzer nun seine
Änderungen rückgängig machen:
um = new UndoManager();
textarea.getDocument().addUndoableEditListener(um);
um.setLimit(3);
private void undoActionPerformed(ActionEvent ae) {
if (um.canUndo()) {
um.undo();
}
textarea.requestFocus();
}
private void redoActionPerformed(ActionEvent ae) {
if (um.canRedo()) {
um.redo();
}
textarea.requestFocus();
}
Diese einfache Implementierung
veranschaulicht die Funktionalität des
UndoManagers sehr deutlich. Bis zu dem Limit
von (hier) drei Undo-Schritten können die
5
Undo / Redo
6
Änderungen des Benutzers
rückgängig gemacht werden.
Diese rückgängig gemachten
Änderungen können zur Gänze
wiederhergestellt werden, aber
nur dann, wenn zwischendurch
keine andere Eingabe erfolgt ist.
Für die Liste zum Mitloggen der
Ereignisse kommt ein leicht modifizierter UndoManager zum
Einsatz. Darüber hinaus werden
die ActionPerformed()Methoden um folgende Zeile ergänzt:
listmodel.addElement(ue.getPresentationName());
public class MyUndoManager extends UndoManager {
DefaultListModel model;
public MyUndoManager (DefaultListModel model) {
super();
this.model = model;
}
public void undoableEditHappened(UndoableEditEvent e) {
UndoableEdit ue = e.getEdit();
model.addElement(ue.getPresentationName());
addEdit( ue );
}
}
2.2
Fortgeschrittenes Undo / Redo
Standardmäßig besteht eine rückgängigzumachende Änderung aus genau einem
Zeichen. Für einen sehr einfachen Texteditor mag das ausreichen, für andere
Anwendungen hingegen nicht. Eine Möglichkeit wäre, mehrere Änderungen zu einer
Gruppe zusammenzufassen und bei Betätigung der Undo-Funktion alle Aktionen
innerhalb einer Gruppe rückgängig zu machen.
Der Dreh- und Angelpunkt des Undo/Redo-Mechanismus‘ ist das UndoableEditInterface. Jede Klasse, die reversible Aktionen anbietet, muss dieses Interface
implementieren. Das UndoableEdit-Interface hat 11 Methoden, von denen die ersten
4 keiner näheren Erläuterung bedürfen:
public
public
public
public
void undo() throws CannotUndoException;
boolean canUndo();
void redo() throws CannotRedoException;
boolean canRedo();
6
FortgeschrittenesUndo
Undo // Redo7
Redo
7
public
public
public
public
public
public
public
String getPresentationName();
String getUndoPresentationName();
String getRedoPresentationName();
boolean isSignificant();
boolean addEdit(UndoableEdit anEdit);
boolean replaceEdit(UndoableEdit anEdit);
void die();
Die drei get(…)PresentationName()-Methoden liefern nur Status-Informationen
zurück und können ebenfalls vernachlässigt werden.
Die isSignificant()-Methode teilt mit, ob eine Änderung signifikant ist. Was das
bedeutet, lässt sich am einfachsten an einem Beispiel veranschaulichen:
1. Diverse Text-Hinzufügen-Aktionen in
Namensfeld
2. Namensfeld verliert den Fokus
3. Diverse Text-Hinzufügen-Aktionen in
Vornamensfeld
4. Vornamensfeld verliert den Fokus
Dieses kleine Anschauungsbeispiel macht deutlich, bei welchen Aktionen es sich um
signifikante Änderung handelt. Die focusLost()-Aktionen gehören nicht dazu und
sollten deshalb bei einem undo() auch nicht rückgängig gemacht werden.
Die Methoden addEdit() und replaceEdit() ermöglichen es, mehrere
Änderungen zu einer zusammenzufassen. Die die()-Methode wird benötigt, um
Änderungen aus dem UndoManager zu entfernen, die nicht mehr rückgängig
gemacht werden können.
Als Standard-Implementierung des UndoableEdit-Interfaces dient die Klasse
AbstractUndoableEdit, die anders, als ihr Name vermuten lässt, gar nicht abstrakt
ist.
Um Änderungen in Gruppen zusammenzufassen und diese Änderungsgruppen als
eine Änderung (im Sinne von Undo- und Redo-Aktionen) zu verwalten, kommt man
mit dem UndoableEdit-Interface allein nicht aus. Eine Unterklasse von
AbstractUndoableEdit namens CompoundEdit hilft hier weiter. Ein
CompoundEdit-Objekt dient als Behälter für andere Änderungen. Es ist zu Beginn leer
und befindet sich „in Bearbeitung“, d. h. die CompoundEdit-Methode
isInProgress() liefert als Antwort den Wert „true“ zurück. Mit der addEdit()Methode können Änderungen zum CompoundEdit-Objekt hinzugefügt werden. Das
CompoundEdit-Objekt kann nun mit der end()-Methode (ebenfalls eine
CompoundEdit-Methode)„abgeschlossen“ werden, das bedeutet, der Änderungsstatus (isInProgress()) liefert „false“. Ab diesem Moment liefert die addEdit()Methode nur noch ein false zurück, ohne das CompoundEdit-Objekt zu verändern.
Ab jetzt ist es nun möglich, die undo()-Methode aufzurufen, die vor Aufruf der
7
Undo / Redo
8
end()-Methode lediglich eine CannotUndoException geworfen hätte. Im
folgenden Skript wird der UndoManager um ein CompoundEdit erweitert, so dass
nach jedem getippten Leerzeichen oder Return eine Undo-Aktion der vorhergehenden Zeichen erzeugt wird und so wortweise statt zeichenweise rückgängiggemacht bzw. wiederhergestellt wird.
public class MyUndoManager extends UndoManager {
DefaultListModel model;
CompoundEdit ce;
public MyUndoManager(DefaultListModel model) {
super();
model = model;
ce = new CompoundEdit();
}
public void undoableEditHappened(UndoableEditEvent e) {
UndoableEdit ue = e.getEdit();
if (!ce.isInProgress()){
ce = new CompoundEdit();
}
ce.addEdit(ue);
}
public void closeCompoundEdit() {
if (ce.isInProgress()) {
ce.end();
this.addEdit(ce);
model.addElement("CE hinzugefügt");
}
}
public boolean isCEClosed() {
return !ce.isInProgress();
}
}
Die undo- und redoActionPerformed()-Methoden werden erweitert und
zusätzlich eine keyTyped()-Methode geschrieben, die beim Tippen der gewünschten
Zeichen (Leerzeichen und Return) das Ende des CompoundEdit triggert.
private void undoActionPerformed (ActionEvent ae) {
if (um.canUndo()) {
um.closeCompoundEdit();
um.undo();
}
model.addElement("UNDO");
textarea.requestFocus();
8
Theoretischer Exkurs: ExplorativesUndo
Undo // Redo9
Redo
9
}
private void redoActionPerformed(ActionEvent ae) {
if (um.canRedo() && um.isCEClosed()) {
um.redo();
}
model.addElement("REDO");
textarea.requestFocus();
}
private void textareaKeyTyped(KeyEvent ke) {
char keyChar = ke.getKeyChar();
if (keyChar == ' ' || keyChar == '\n') {
um.closeCompoundEdit();
}
}
Hinweis: Diese Beispielanwendung ist weit davon entfernt perfekt zu funktionieren.
Sie soll lediglich die Funktionsweise von Compound-Edit veranschaulichen.
2.3
Theoretischer Exkurs: Exploratives Undo / Redo
Über das CompoundEdit lässt sich die Undo-/Redo-Funktionalität komfortabler
gestalten, richtig benutzerfreundlich wäre jedoch eine explorative Undo-/RedoFunktionalität. Beim Explorieren in einem interaktiven System führt der Benutzer
Operationen aus, ohne zu wissen, ob diese zum Erreichen des Handlungsziels
beitragen. Stellt sich nach dem Ausführen einer Operation heraus, dass die Aktion die
Entfernung zum Handlungsziels nicht reduziert oder vergrößert hat, kann die
Operation rückgängig gemacht und zum vorherigen Zustand zurückgekehrt werden.
Soweit ist dies noch mit einem einfachen UndoManager bewältigbar, sollten aber
mehrere Pfade ausprobiert (und verglichen) werden, kommt man damit nicht mehr
weiter. Dieses wäre mit Hilfe eines UndoManagers möglich, der die Änderungen nicht
nur linear sondern auch in einer Baumstruktur ablegt. Da dies sehr schnell zu hohen
Datenmengen führen kann, wäre hier zu überlegen, ob das Anlegen sogenannter
Snapshots (Schnappschüsse) nur bestimmte Pfade zur Weiterverfolgung markiert, um
das Datenaufkommen zu reduzieren.
Als Beispiel in diesem kleinen Exkurs soll ein von Studenten entwickeltes Projekt zu
diesem Thema, die Umsetzung eines Zauberwürfels dienen.
Hier wurde ein eine explorative Undo-/Redo-Funktionalität mit Hilfe einer
Baumstruktur realisiert. Der Benutzer kann zu jeder Zeit zu einer früheren Version des
Zauberwürfels zurückkehren, von dort aus weiter versuchen, alternative parallele
Pfade probieren, ohne das irgendeiner der Würfelstellungen nicht mehr rekonstruierbar wäre.
Ein Netzwerk aus Knoten speichert die Interaktionshistorie des Benutzers. Über den
Wurzelknoten, der den ältesten gespeicherten Zustand darstellt, kann über
Vorgänger- und Nachfolgebeziehungen zwischen den einzelnen Knoten navigiert
werden. Eine Referenz zeigt auf den Knoten, der den aktuellen Zustand repräsentiert.
Soll von einem Würfelzustand zum nächsten gewechselt werden, ermittelt die
9
Undo / Redo
10
Befehlshistorie den optimalen Weg vom aktuellen zum Zielknoten und führt dann
alle notwendigen Operationen aus.
Dieses Vorgehen lässt sich auch auf andere Anwendung übertragen. Die Komplexität
steigt jedoch mit Menge der möglichen Änderungen. Im Beispiel des Zauberwürfels
lassen sich die wenigen Aktionen problemlos in sehr kleinen Datenstrukturen
unterbringen. Andere Anwendungen wie Zeichenprogramme sind da weit weniger
genügsam.
Wesentliche Punkte des Kapitels
–
zu ergänzen.
10
Exkurs: I18N
Internationalisierung
/ L10N an Beispielen ausund
der Spieleindustrie11
Lokalisierung
11
3
Internationalisierung und Lokalisierung
Sollen Programme nicht nur für den deutschsprachigen Raum entwickelt werden,
muss man sich Gedanken über die multinationale Umsetzung machen. Die
Programme für jede benötigte Sprache neu zu schreiben ist zeitaufwendig und sehr
wartungsintensiv und damit unpraktikabel. Um mehrsprachige Anwendungen zu
realisieren bietet Java die Möglichkeit der Internationalisierung, Internationalization
genannt oder einfach abgekürzt: i18n. (i + 18 Buchstaben 'nternationalizatio' + n).
Mit der Internationalisierung wird ein Programm so vorbereitet, dass es später - ohne
Anpassungen im Quellcode vornehmen zu müssen – für den internationalen Markt
angepasst werden kann. Hierzu gehören nicht nur Variablen für Beschreibungstexte,
sondern auch Vorbereitungen für kulturell unterschiedliche Datumsformatierungen
und Oberflächengestaltung (unterschiedlich langer Text, Schreibrichtung, etc.). Erst
nachdem ein Programm internationalisiert wurde, kann es mit Hilfe der Lokalisierung
für die entsprechende Kultur angepasst werden.
3.1
Exkurs: I18N / L10N an Beispielen aus der Spieleindustrie
Möglichkeiten Software zu internationalisieren gibt es viele. Komplexe PCAnwendungen wie PC-Spiele sind hervorragend dazu geeignet, die
Herangehensweisen mit ihren möglichen Fußangeln näher zu beleuchten.
Anwendungen, für die ursprünglich keine Internationalisierung vorgesehen war,
lassen es nur sehr schwer zu, sie nachträglich dafür anzupassen. Internationalisierung
bedeutet ja nicht allein Text zu übersetzen und ein paar Datumsformate anzupassen.
Unterschiedliche Grammatik, Anreden uvm. machen einem hier das Leben schwer.
Gründliche Vorüberlegungen sind ein Muss, gar keine Internationalisierung richtet
oft weniger Schaden an, als eine schlecht umgesetzte. Zur Einstimmung sollen hier
ein paar Pannen aus einem der bekanntesten Internationalisierungsflops der letzten
Jahre dienen:
11
Internationalisierung und Lokalisierung
12
Nicht nur, dass in diesem Spiel teilweise haarsträubend (wenn
überhaupt) übersetzt wurde, auch andere Pannen sind
passiert. So sollte es „Elena die Zigeunerin“ selbstverständlich
nur einmal geben, dass im Bild oben gleich die ganze Gruppe
so heißt, ist nicht dem Übersetzungsbüro sondern den
Programmierern vorzuwerfen. Es ist für den Spieler sehr
frustrierend, wenn es ihm solche falschen Zuordnungen
unmöglich machen, den richtigen Gegner zu finden, weil
Aufgabenbeschreibung und Gegner zwar denselben Namen
tragen, es sich aber intern um zwei unterschiedliche Dinge
handelt. Oder umgekehrt der Gegner in der
Aufgabenbeschreibung anders heißt, als es der Name über
seinem Kopf angibt, schlicht deshalb, weil der Schlüssel in der
Datenbank doppelt vorkommt und so von zwei unterschiedlichen Leuten übersetzt
wurde. Solche Inkonsistenzen machen dem Benutzer einer solch schlecht
internationalisierten Anwendung das Leben schwer. Doppelte Namensgebung, die
häufig und gerne zu solchen Inkonsistenzen führt, ist in vielen Spielen ein Problem:
12
Exkurs: I18N
Internationalisierung
/ L10N an Beispielen ausund
der Spieleindustrie13
Lokalisierung
13
Da es sich aus den Fehlern anderer gut lernen lässt, sollen hier noch ein paar weitere
Beispiele für beliebte Internationalisierungsfehler gezeigt werden. Die beiden
vorherigen und die folgenden Abbildungen stammen aus Spielen, die sich in der
Betaphase befanden, die meisten der hier gezeigten Fehler wurden inzwischen
behoben.
Neben doppelter Namensgebung ist ein weiterer häufig auftretender Planungsfehler
falsch bemessener Platz für Textcontainer, Menüs und Schaltflächen. Nicht jede
Sprache ist so kurz wie die englische, hier muss im Voraus mehr Platz eingeplant
werden, um abgeschnittenen Text und unschöne Überlappungen zu vermeiden:
Nicht vergessen werden sollte außerdem, dass andere Länder andere Zeichensätze
verwenden. Verstümmelte Umlaute sind ein häufiges Problem in schlecht
lokalisierter Software. Interessanterweise gibt es auch hier Inkonsistenzen. Dass Texte
13
Internationalisierung und Lokalisierung
14
in ein und demselben Programm mal mit
korrekten, mal mit falschen und mal ganz ohne
Umlaute dargestellt werden, ist keine
Seltenheit.
Programme, die sich noch in der Betaphase
befinden sind eine hervorragende Quelle, um
daraus zu lernen. Viele Teile der Software sind
zu diesem Zeitpunkt noch nicht vollständig
lokalisiert und so lässt sich manchmal
erkennen, wie die Programmierer vorgehen.
Platzhalter dienen dazu, Texte, die lokalisiert werden müssen, aus dem Quelltext
auszulagern, so dass die Übersetzung von Personen ohne Programmierkenntnisse
vorgenommen werden kann. Bestimmte Begriffe, wie z.B. Eigennamen, die übersetzt
werden sollten werden aber zentral „verwaltet“, so dass hier keine Inkonsistenzen
auftreten. So wird „Frodo Baggins“ nicht in jedem Text neu übersetzt (und heißt dann
möglicherweise Beutlin, Beutelchen oder Beutlein), sondern spezielle, wichtige
Begriffe erhalten eigene Platzhalter, auf die dann innerhalb des Fließtextes Bezug
genommen wird, um Inkonsistenzen auszuschließen.
14
Exkurs: I18N
Internationalisierung
/ L10N an Beispielen ausund
der Spieleindustrie15
Lokalisierung
15
Nicht alle Programme werden im englischsprachigen Raum entwickelt. Software, die
beispielsweise aus Asien stammt, wird häufig nicht jedesmal aus der Ursprungssprache übersetzt, sondern wird oft zuerst ins Englische und dann weiterübersetzt.
Dies hat mehrere Vorteile. Zum einen lassen sich leichter Übersetzer finden, die aus
dem Englischen übersetzen, zum anderen wird englisch weltweit verstanden, was für
die asiatischen Sprachen nicht unbedingt gilt. Lücken in der Übersetzung sind so für
den Benutzer leichter zu verschmerzen.
Die Internationalisierung ist ein sehr komplexes Thema. Die Auswirkungen
mangelhafter Internationalisierung sind nicht immer auf den ersten Blick ersichtlich.
Man sollte beim Entwickeln von Software für den internationalen Markt immer ganz
besondere Sorgfalt walten lassen, damit der Aufwand, der nötig ist, um im
Nachhinein Fehler auszubessern nicht ins Unermessliche steigt.
15
Internationalisierung und Lokalisierung
16
3.2
Ressourcen
Um Programme für den globalen Einsatz vorzubereiten, müssen die lokalisierbaren
Daten aus dem Quelltext entfernt und in sogenannten Property-Dateien bereitgestellt
werden. Das betrifft u.a. folgende Elemente:
Beschriftung von GUI-Elementen, Dokumentation, Meldungen
Datums- und Zeitformat: Reihenfolge, Trennzeichen, Sprache, Benutzereinstellungen
Zahlenformat: Tausendertrennung, Dezimaltrennung, Währungssymbol
Sortierung von Zeichenketten in der landesüblichen Reihenfolge
Einbinden der sprachspezifischen Umlaute, Symbole und Sonderzeichen
Der Vorteil der Lokalisierung mit Hilfe der Property-Files besteht darin, dass das
Programm nicht verändert oder neu kompiliert werden muß. Die Property-Files
müssen nicht vom Programmierer erstellt werden, die Lokalisierung für eine neue
Sprache ist auch später noch möglich. Der Programmierer arbeitet nur noch mit
länderunabhängigen Bezeichnern, die durch ein Mapping in die jeweilige Sprache
übersetzt werden.
3.2.1
Einfache Internationalisierung
Das folgende leicht erweiterte “Hello World”-Programm soll für den mehrsprachigen
Einsatz umgeschrieben werden:
public class I18n {
public static void main(String[] args) {
System.out.println("Hallo Welt");
System.out.println("Wie geht es Dir?");
System.out.println("Java macht Spaß!");
System.out.println("Auf Wiedersehen.");
}
}
Dieses Programm soll nun die entsprechenden Texte in englisch, französisch und
spanisch ausgeben können. Dazu werden die länderspezifischen Texte ausgelagert
und übersetzt, der Quelltext wird so angepaßt, daß er die entsprechenden Texte aus
den Property-Files ausliest.
Das Programm ändert sich wie folgt:
public class I18n {
public static void main(String[] args) {
Locale locale = new Locale("es", "ES");
ResourceBundle texte =
ResourceBundle.getBundle("MessageBundle", locale);
16
Internationalisierung und Lokalisierung
Ressourcen17
17
System.out.println(texte.getString("gruss"));
System.out.println(texte.getString("befinden"));
System.out.println(texte.getString("java"));
System.out.println(texte.getString("abschied"));
}
}
Als nächstes wird ein standard Property-File erstellt, welches dann übersetzt werden
kann. Bei der Datei handelt es sich um eine normale Text-Datei, die in jedem
Texteditor ganz einfach erstellt werden kann.
MessagesBundle.properties
gruss=Hello World!
befinden=How are you?
java=Java is fun!
abschied=Good bye.
Diese Datei wird dann übersetzt und wie folgt benannt:
MessagesBundle_de_DE.properties
gruss=Hallo Welt!
befinden=Wie geht es Dir?
java=Java macht Spaß!
abschied=Auf Wiedersehen.
MessagesBundle_en_US.properties
gruss=Hello World!
befinden=How are you?
java=Java is fun!
abschied=Good bye.
MessagesBundle_es_ES.properties
gruss=¡Hola Mundo!
befinden=¿Cómo es usted?
java=¡Java es diversión!
abschied=Adiós.
MessagesBundle_fr_FR.properties
gruss=Bonjour Monde !
befinden=Comment allez-vous ?
java=Java est amusement !
abschied=Au revoir.
Hinweis:
Normalerweise ist es üblich, die Java VM nach der Locale des Benutzers zu „fragen“,
indem man in der ResourceBundle.getBundle-Methode kein Locale übergibt. In
diesem Fall wird die Locale des Benutzers verwendet. Hier wird die Locale nur zu
17
Internationalisierung und Lokalisierung
18
Testzwecken händisch gesetzt. Wird eine Locale gesetzt, die entweder ungültig ist
oder aber für die keine Ressource existiert, wird die Locale des Rechners verwendet.
Nach dem Aufruf von ResourceBundle.getBundle() kann mit Hilfe der
getLocale()-Methode erfragt werden, welche Locale tatsächlich verwendet wird.
3.2.2
ResourceBundle
Ein ResourceBundle repräsentiert eine Menge von benannten Resourcen, die
zusammengehören und für verschiedene Locales vorliegen. Unter Berücksichtigung
der Locale wird einem Schlüssel der entsprechende Wert zugeordnet. Wie im obigen
Beispiel funktioniert das folgendermaßen:
ResourceBundle rb;
rb = ResourceBundle.getBundle(“Basisname_des_Bundles”);
String s = rb.getString(“schluessel”);
Existiert für die Locale des Benutzers kein entsprechendes Property-File, verwendet
das Programm die Datei MessagesBundle.properties. Das bedeutet, dass auf einem
österreichischen Rechner beim Ausführen des folgenden Programms der englische
und nicht der deutsche Text angezeigt würde, da die Benutzer-Locale nicht de_DE
sondern de_AT ist.
public class I18n {
public static void main(String[] args) {
Locale locale = new Locale("de", "AT");
ResourceBundle texte =
ResourceBundle.getBundle("MessageBundle", locale);
System.out.println(texte.getString("gruss"));
System.out.println(texte.getString("befinden"));
System.out.println(texte.getString("java"));
System.out.println(texte.getString("abschied"));
}
}
Auf einem Rechner in Deutschland hingegen wird der deutsche Text ausgegeben, da
die tatsächlich verwendete Locale de_DE und nicht das übergebene de_AT ist. Dieses
Verhalten macht das Testen etwas schwer und es sollte immer berücksichtigt werden,
dass im Problemfall auf die Locale des Rechners zurückgegriffen wird.
Unabhängig von dem obigen Verhalten muss man sich klarmachen, dass ein
österreichischer benutzer (um bei unserem Beispiel zu bleiben) hier den englischen
Text angezeigt bekommt und nicht den deutschen, weil eben keine Ressource für
de_AT existiert und so auf die Standardressource zurückgegriffen wird..
18
Internationalisierung und Lokalisierung
Ressourcen19
19
Nun könnte man das ResourceBundle um die Datei für Österreich erweitern, das wäre
genau dann sinnvoll, wenn sich die Texte von den deutschen unterscheiden. Ist das
nicht der Fall, besteht die Möglichkeit auf ein weniger spezialisiertes ResourceBundle
zurückzugreifen. Hier wäre das die Datei MessagesBundle_de.properties. Findet das
Programm also kein genau passendes Property-File (de_AT) sucht es als nächstes nach
einer weniger spezialisierten Variante (de) und danach erst nach dem Standardfile.
Aus diesem Grund ist es wichtig, immer zuerst ein standard Property-File zu erstellen.
Darüberhinaus erben spezialisierte Resourcen von weniger spezialisierten. So ist ein
selektives Überschreiben durch die Spezialisierung möglich. Verdeutlich wird das an
dem folgenden Beispiel:
public class I18n {
public static void main(String[] args) {
ResourceBundle.getBundle("Beispiel", locale);
System.out.println(texte.getString("gruss"));
System.out.println(texte.getString("befinden"));
}
}
Beispiel.properties
gruss=Hello!
befinden=How are you?
Beispiel_de.properties
gruss=Guten Tag!
befinden=Wie geht es Dir?
Beispiel_de_AT.properties
gruss=Servus!
Beispiel_de_CH.properties
gruss=Grüezi!
Ein Österreich würde hier als Gruß “Servus!” angezeigt bekommen, wohingegen ein
Schweizer “Grüezi!” lesen würde. In Deutschland und Luxemburg würde “Guten Tag”
auf dem Bildschirm erscheinen. Die Frage nach dem Befinden wäre jedoch für alle
gleich. Benutzer anderer Länder bekommen hier nur den englischen Text zu lesen.
19
Internationalisierung und Lokalisierung
20
3.2.3
PropertyResourceBundle
Die abstrakte Klasse ResourceBundle besitzt zwei Unterklassen, eine davon ist die
Klasse PropertyResourceBundle. Diese Klasse greift wie in den bisherigen Beispielen auf Property-Dateien zurück. Dabei handelt es sich um reine Textdateien,
die die Übersetzungen enthalten. Diese Dateien sind nicht Teil des Quellcodes und sie
können nur String-Objekte als Schlüssel und Werte enthalten. Kommentare sind
durch ein der Zeile vorangestelltes #-Zeichen möglich.
Wie bereits angesprochen sollte immer zuerst eine standard Property-Datei erstellt
werden. Diese beginnt mit dem Basisnamen des „Bundles“ und endet auf .properties.
Von dieser Datei ausgehend wird dann übersetzt, wobei die Schlüssel nicht verändert
werden dürfen.
Zu Testzwecken ist es möglich, über alle Schlüssel zu iterieren:
ResourceBundle beispiel =
ResourceBundle.getBundle("BeispielBundle", currentLocale);
Enumeration beispielKeys = beispiel.getKeys();
while (beispielKeys.hasMoreElements()) {
String key = (String)beispielKeys.nextElement();
String value = beispiel.getString(key);
System.out.println("key = " + key + ", " + "value = " +
value);
}
Für die meisten Anwendungsfälle ist man mit einem PropertyResourceBundle gut
beraten. Es gibt aber auch Fälle, in denen ein PropertyResourceBundle nicht
ausreicht:
3.2.4
ListResourceBundle
Die zweite Unterklasse von ResourceBundle heißt ListResourceBundle. Diese Klasse
greift auf class-Dateien und enthält einfache Schlüssel-Werte-Listen, wobei es sich bei
den Werten auch um Objekte handeln darf. Beim Ableiten von der abstrakten Klasse
ListResourceBundle muß die getContents()-Methode überschrieben werden, die die
Liste mit den Schlüssel-Werte-Paaren zurückgibt.
Beispiel:
import java.util.*;
public class BeispielBundle_de_DE extends ListResourceBundle {
public Object[][] getContents() {
return contents;
}
private Object[][] contents = {
{"Land", "Deutschland"},
{"Einwohner", new Integer(82440000)},
{"Flaeche", new Integer(357023)},
};
}
20
Internationalisierung und Lokalisierung
Ressourcen21
21
Und so gelangt man an die Objekte:
ResourceBundle beispiel =
ResourceBundle.getBundle("BeispielBundle"; locale);
Integer bevoelkerung = (Integer)beispiel.getObject("Einwohner");
Ein wichtiger Anwendungszweck für ListResourceBundles sind Situationen, in
denen die zu lokalisierenden Daten aus Listen bestehen. So hat jedes Land eine
unterschiedliche Anzahl von Bundesländern / Staaten, etc., so dass man hier mit
einem PropertyResourceBundle nicht weiterkommt. Hier ist das
ListResourceBundle die erste Wahl, in einem „echten“ Beispiel würde man die
entsprechenden Listen wahrscheinlich jedoch zusätzlich in Textdateien auslagern,
um diese dann lokalisieren zu lassen. Um das Beispiel kurz zu halten, wurde auf dieses
Vorgehen hier verzichtet:
public class ListBundle_de_DE extends ListResourceBundle {
private Object[][] contents = {
{"laender", new String [] {
"Baden-Würtemberg",
"Bayern",
"Berlin",
"…"}}};
protected Object[][] getContents() {
return contents;
}
}
ResourceBundle laender =
ResourceBundle.getBundle("i18n.ListBundle", locale);
for (String land : laender.getStringArray("laender")) {
System.out.println(land);
}
3.2.5
Planung von ResourceBundles
Oftmals ist es sinnvoll mehrere ResourceBundles zu verwenden. Die Dateien sind
so einfacher zu lesen und zu warten, die Objekte werden schneller in den Speicher
gelesen und darüberhinaus wird der Speicherverbrauch dadurch reduziert, dass nur
die benötigten Bundles in den Speicher gelesen werden. Es ist aber auch nicht nötig,
für jedes Schlüssel-Werte-Paar ein eigenes Bundle zu verwenden. Deswegen sollte
man sich vorab eine sinnvolle Struktur überlegen. Als grobe Einteilung könnte man
beispielweise die unterschiedlichen Objektarten wie Strings, Bilder, Farben und
Audio-Clips wählen. Werden verschiedene fachliche Themen abgedeckt, wäre eine
Einteilung sinnvoll, bei der die entsprechende zu übersetzende Datei an den
Übersetzer weitergereicht wird, der die Kenntnisse in diesem Bereich aufweist.
21
Internationalisierung und Lokalisierung
22
3.3
Lokale Formate / lokale Einheiten
Da jede Kultur andere Datums- und Zahlenformate verwendet, ist es notwendig, die
Internationalisierung der Anwendung auch auf diesen Bereich auszuweiten. Ebenso
wie die sprachspezifischen Unterschiede, sollten auch die Zahlenformatierungen
nicht fest einprogrammiert sondern variabel sein. Programmintern werden Zahlen
und Datumsangaben in einem neutralen Format verwendet, die Ausgabe auf dem
Bildschirm erfolgt dann als String, der die jeweilige landesspezifische Formatierung
enthält. Für die meisten dieser Formate stellt Java Klassen bereit.
3.3.1
Zahlenformate
Für primitive Zahlentypen, wie z.B. double bietet die NumberFormat-Klasse geeignete
Methoden:
double zahl = 123456.78;
NumberFormat formatter;
String zahlAusgabe;
formatter = NumberFormat.getNumberInstance(locale);
zahlAusgabe = formatter.format(zahl);
Die Ausgabe wäre dann wie folgt:
de: 123.456,78
en: 123,456.78
fr: 123 456,78
Bei Währungen funktioniert es analog:
double waehrung = 123456.78;
NumberFormat formatter;
String waehrungAusgabe;
formatter = NumberFormat.getCurrencyInstance(locale);
waehrungAusgabe = formatter.format(waehrung);
ebenso bei Prozentwerten:
double prozent = 0.75;
NumberFormat formatter;
String prozentAusgabe;
formatter = NumberFormat.getPercentInstance(locale);
prozentAusgabe = formatter.format(prozent);
22
Internationalisierung
Lokale Formate
und
/ lokale
Lokalisierung
Einheiten23
23
3.3.2
Datumsangaben und Uhrzeiten
Genau wie Zahlen, werden Datumsangaben in Java in einem neutralen Format
behandelt und die Ausgabe erfolgt als String. Dieses neutrale Format sollte alle
möglicherweise benötigten Informationen über das Datum enthalten, im Falle eines
Programms für Performance-Messungen wäre es nötig, die vergangenen
Millisekunden zur Verfügung zu haben, ein Kalender, der lediglich den Wochentag
anzeigt braucht diese Information natürlich nicht. Trotzdem muß das javainterne
Datum alle Informationen beinhalten.
Die Formatierung des Datums für die Ausgabe ist genauso einfach, wie die der Zahlen:
Date heute;
DateFormat dateFormatter;
DateFormat timeFormatter;
DateFormat dateTimeFormatter;
String heuteDatum;
String heuteZeit;
String heuteDatumZeit;
heute = new Date(); // enthält automatisch das aktuelle Datum
dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT,
locale);
timeFormatter = DateFormat.getTimeInstance(DateFormat.DEFAULT,
locale);
dateTimeFormatter =
DateFormat.getDateTimeInstance(DateFormat.LONG,
locale);
heuteDatum = dateFormatter.format(heute);
heuteZeit = timeFormatter.format(heute);
heuteDatumZeit = dateTimeFormatter.format(heute);
Wem das DEFAULT-Datumsformat nicht zusagt, hat außerdem die Möglichkeit, das
Datum mit den Modifiern SHORT, MEDIUM, LONG und FULL anders anzeigen zu lassen.
Darüberhinaus besteht auch die Möglichkeit, mit der SimpleDateFormat-Klasse ganz
eigene Datumsformate zu erzeugen.
3.3.3
Maßeinheiten
Bei Maßeinheiten beschränkt sich die Lokalisierung nicht nur auf die unterschiedliche Darstellungsweise von Zahlen. So gibt es das metrische und das imperiale
System, die beide verwendet werden. Beide Systeme lassen sich mehr oder weniger
einfach ineinander konvertieren, anders sieht es da beispielsweise bei Maßen für
Kleidergrößen aus. Für Anwendungen, in denen Sie mit Maßeinheiten arbeiten,
23
Internationalisierung und Lokalisierung
24
werden Sie nicht drumherum kommen, diese Fälle ebenfalls bei Ihrer
Internationalisierung zu berücksichtigen.
3.4
Fortgeschrittene Internationalisierung
Leider ist die Internationalisierung von Anwendungen nicht immer so einfach wie in
den vorangegangenen Kapiteln. Oftmals gibt es einige Fußangeln zu umschiffen, wie
beispielsweise aus unterschiedlichen Variablen zusammengesetzter Text oder
sprachliche Unterschiede in der Grammatik.
3.4.1
Zusammengesetzte Texte / Meldungssignalisierung
Ein zusammengesetzter Text kann unterschiedliche Variablen beinhalten, Datumsangaben, Zeiten, Strings, Zahlen, Währungen, usw. Um einen zusammengesetzten
Text sprachunabhängig zu formatieren ist es nötig ein MessageFormat-Objekt zu
erstellen, dem ein Muster zugewiesen wird. Dieses Muster wird dann in einem
ResourceBundle abgelegt.
Angenommen, der folgende Text soll internationalisiert werden:
Um 13:15 Uhr am 13. April 2009 haben wir 7 rote Raumschiffe auf dem Planeten
Mars.entdeckt
Datum
Datum
Zahl String
String
Vorgehensweise:
Schritt: Erstellen des ResourceBundles für die Nachricht (Message)
ResourceBundle messages =
ResourceBundle.getBundle("CompoundBundle", locale);
Schritt: Erzeugen des Default Property-Files und der Übersetzungen
CompoundBundle.properties:
template = At {0,time,short} on {0,date,long}, we detected \
{1,number,integer} {2} spaceships on the planet {3}.
planet = Mars
color = red
CompoundBundle_de.properties:
template = Um {0,time,short} am {0,date,long} haben wir \
{1,number,integer} {2} Raumschiffe auf dem Planeten {3}. \
entdeckt.
planet = Mars
color = rote
24
Internationalisierung
Fortgeschrittene Internationalisierung25
und Lokalisierung
25
Hierbei enthält die erste Zeile unserer Property-Datei das Muster (template oder
pattern) für den Text. Innerhalb der Klammern stehen die Parameter, die später durch
die entsprechenden Werte ersetzt werden. Dabei ist der 1. Parameter immer der Index
eines Elementes innerhalb eines Object-Arrays mit den einzufügenden Werten. Der 2.
und 3. Parameter geben genaue Informationen zu dem Typ des einzufügenden
Wertes.
Wird nur der Index als Parameter angegeben, handelt es sich immer um einen String.
Andere Formate können aus der folgenden Tabelle abgelesen werden:
Format
Type
Format Style
Subformat Created
NumberFormat.getInstance(getLocale())
NumberFormat.getIntegerInstance(getLocale())
NumberFormat.getCurrencyInstance(getLocale())
NumberFormat.getPercentInstance(getLocale())
new DecimalFormat(subformatPattern,
DecimalFormatSymbols.getInstance(getLocale())
DateFormat.getDateInstance(DateFormat.DEFAULT,
getLocale())
short
DateFormat.getDateInstance(DateFormat.SHORT,
getLocale())
medium
DateFormat.getDateInstance(DateFormat.DEFAULT,
getLocale())
long
DateFormat.getDateInstance(DateFormat.LONG,
getLocale())
full
DateFormat.getDateInstance(DateFormat.FULL,
getLocale())
SubformatPattern new SimpleDateFormat(subformatPattern, getLocale())
DateFormat.getTimeInstance(DateFormat.DEFAULT,
getLocale())
short
DateFormat.getTimeInstance(DateFormat.SHORT,
getLocale())
medium
DateFormat.getTimeInstance(DateFormat.DEFAULT,
getLocale())
long
DateFormat.getTimeInstance(DateFormat.LONG,
getLocale())
full
DateFormat.getTimeInstance(DateFormat.FULL,
getLocale())
SubformatPattern new SimpleDateFormat(subformatPattern, getLocale())
SubformatPattern new ChoiceFormat(subformatPattern)
Quelle: Sun, Java MessageFormat-API
integer
currency
number
percent
SubformatPattern
date
time
choice
Schritt: Den Parametern Werte zuweisen
Als nächstes bekommen die Parameter in dem Template die entsprechenden Werte
zugewiesen. Dazu wird ein Array aus Objekten erzeugt, in dem sich die Werte
befinden, bzw. die im Falle einer Übersetzung zuerst aus dem Property-File ausgelesen
werden.
25
Internationalisierung und Lokalisierung
26
Object [] werte = {
new Date()
//heute, Index: 0
new Integer(7),
// Index: 1
messages.getString("color"),
// Index: 2
messages.getString("planet"),
// Index: 3
};
Schritt: Erstellen des Formatierers
Zum Formatieren des Textes wird nun ein MessageFormat-Objekt benötigt, da der
Text Zahlen und Datumsangaben enthält, die auf die entsprechende Sprache
abgestimmt werden sollen.
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(locale);
Schritt: Formatierung des Textes
Zum Schluß müssen Text und Werte zusammengeführt werden. Dazu werden das
Template und die Werte dem Formatierer übergeben.
formatter.applyPattern(messages.getString("template"));
String output = formatter.format(werte);
Das komplette Beispiel sieht dann so aus:
public class MessageFormatDemo {
static public void main(String[] args) {
Locale locale = new Locale("en", "US");
ResourceBundle messages =
ResourceBundle.getBundle("i18n.MessageBundle", locale);
Object[] messageArguments = {
new Date(),
new Integer(7),
messages.getString("color"),
messages.getString("planet")
};
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(locale);
formatter.applyPattern(messages.getString("template"));
String output = formatter.format(messageArguments);
System.out.println(output);
}
}
26
Internationalisierung
Fortgeschrittene Internationalisierung27
und Lokalisierung
27
MessageBundle_de_DE.properties:
template = Um {0,time,short} am {0,date,long} haben wir \
{1,number,integer} {2} Raumschiffe auf dem Planeten {3} entdeckt.
planet = Mars
color = rote
3.4.2
Der Umgang mit dem Plural
Die Worte eines Textes können variieren, je nachdem, ob der Text im Singular oder
im Plural angezeigt werden soll. In vielen Sprachen sind die Singular- und die PluralForm eines Wortes unterschiedlich, darüberhinaus sind oftmals andere Artikel
und/oder Adjektive nötig. z.B.
Sie haben keine neuen Nachrichten in Ihrer Mailbox INBOX.
Sie haben eine neue Nachricht in Ihrer Mailbox INBOX.
Sie haben 2 neue Nachrichten in Ihrer Mailbox INBOX.
Eine einfache, aber unelegante Lösung des Problems wäre:
Sie haben {0, number} neue(n) Nachricht(en) in Ihrer Mailbox {1}.
Eleganter kann man das Problem mit Hilfe der ChoiceFormat-Klasse lösen. Dazu sollte
man zuerst die Variablen aus dem Text extrahieren:
Sie haben keine neuen Nachrichten
in Ihrer Mailbox
INBOX.
Sie haben eine neue Nachricht in Ihrer Mailbox
INBOX.
Sie haben 2 neue Nachrichten
in Ihrer Mailbox
INBOX.
Als nächstes werden die Variablen im Text durch Parameter ersetzt, so daß ein
Template entsteht:
Sie haben {0} in Ihrer Mailbox {1}.
Der String INBOX ist, wie im vorangehenden Kapitel beschrieben, leicht einzusetzten,
komplizierter wird es mit dem ersten Parameter {0}. Mit der Anzahl der Nachrichten
in der Mailbox ändert sich der Text. Damit dieser zur Laufzeit eingesetzt werden kann,
muß die Anzahl der Nachrichten einem String zugeordnet werden. Wenn die Mailbox
mehr als eine Nachricht enthält, muß in den Text außerdem noch ein Integer
eingesetzt werden.
Schritt: Erstellen des ResourceBundles
ResourceBundle plural = ResourceBundle.getBundle("PluralBundle",
locale);
27
Internationalisierung und Lokalisierung
28
Das Default Property-File sähe wie folgt aus:
pattern = Sie haben {0} in Ihrer Mailbox {1}.
keine = keine neuen Nachrichten
eine = eine neue Nachricht
viele = {2} neue Nachrichten
mailbox = INBOX
Schritt: Formatierer erstellen
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(locale);
Schritt: ChoiceFormatter erstellen
double [] msgLimits = {0, 1, 2};
String [] msgStrings = {
plural.getString("keine"),
plural.getString("eine"),
plural.getString("viele")
};
ChoiceFormat auswahl = new ChoiceFormat(msgLimits, msgStrings);
Die ChoiceFormat-Klasse weist jedem Zahlenwert innerhalb von msgLimit (also 0, 1
und 2) den entsprechenden Wert der msgStrings zu. Bei Werten außerhalb des
Limits, wird dem Wert entweder der erste oder der letzte Wert der msgStrings
zugewiesen, je nachdem, ob die Zahl kleiner oder größer als das Limit ist. Aus diesem
Grund ist es sehr wichtig, daß die Zahlen im double-Array in aufsteigender
Reihenfolge sind. Die Werte in dem double Array sind nur zufälligerweise identisch
mit den Parametern im Template. Es besteht kein Zusammenhang!
Zuordnungen:
Zahl
Im Limit?
Nein, kleiner als das Limit
-1
0
1
2
3
Ja, 1. Wert
Ja, 2. Wert
Ja, 3. Wert
Nein, größer als das Limit
Zuordnung
keine neuen Nachrichten (1. Wert wird
zugewiesen)
keine neuen Nachrichten
eine neue Nachricht
2 neue Nachrichten
3 neue Nachrichten (letzter Wert wird
zugewiesen)
Alle größeren Zahlen bekommen ebenfalls den letzten Wert zugewisen.
Schritt: Das Template/Pattern anwenden und die Formate zuweisen
28
Internationalisierung
Fortgeschrittene Internationalisierung29
und Lokalisierung
29
String pattern = plural.getString("pattern");
formatter.applyPattern(pattern);
Format [] formate = {auswahl, null, NumberFormat.getInstance()};
formatter.setFormats(formate);
Hier erfolgt die Zuweisung der Formate ins Template. Dem ersten Parameter {0} wird
das ChoiceFormat-Objekt zugewiesen, der zweite Parameter {1} besteht nur aus
einem String und bekommt nichts zugewiesen, der dritte Parameter {2} enthält den
Wert und die Formatierung der Zahl, die angezeigt werden soll, wenn deren Wert <1
ist.
Schritt: Den Parametern Werte zuweisen und den Text formatieren
Wie im vorangegangenen Kapitel muß nun wieder ein Object-Array mit den entsprechenden Werten erstellt werden. Ganz zum Schluß wird das Array mit konkreten
Werten gefüllt und der Text formatiert.
Object [] werte = {null, plural.getString("mailbox"), null};
for (int i=-2; i<5; i++) {
werte[0] = new Integer(i);
werte[2] = new Integer(i);
String text = formatter.format(werte);
System.out.println(text);
}
29
Accessibility
30
4
Accessibility
Accessible: (leicht) zugänglich (Langenscheidts Millennium-Wörterbuch)
Accessible: easy to reach, enter or obtain (Longman Dictionary of Contemporary
English)
Accessibility: Erreichbarkeit, Zugänglichkeit (Pons Kompaktwörterbuch)
Suns Definition von Accessibility: Supporting People with Disabilities
Accessibility im Bezug auf Software bedeutet also, diese zugänglich / behindertengerecht zu gestalten.
Die Weltgesundheitsorganisation (WHO) schätzt, daß ca. 750 Millionen Menschen
weltweit schwerwiegend behindert sind. In Deutschland sind es etwa 6 Millionen
Menschen. Bisher unterstützen Computer diese große Bevölkerungsschicht kaum. In
amerikanische Behörden darf nur behindertengerechte Software eingesetzt werden, in
Europa gibt es ähnliche Verordnungen.
Am 17. Juli 2002 ist in Deutschland die "Verordnung zur Schaffung barrierefreier
Informationstechnik nach dem Behindertengleichstellungsgesetz" kurz "Barrierefreie
Informationstechnik-Verordnung – BITV" in Kraft getreten. Diese Verordnung gilt
für:
Internetauftritte und -angebote,
Internetauftritte und -angebote, die öffentlich zugänglich sind, und
mittels Informationstechnik realisierte graphische Programmoberflächen, die
öffentlich zugänglich sind, der Behörden der Bundesverwaltung.
Nach dieser Verordnung ist die Gestaltung von Angeboten der Informationstechnik
dazu bestimmt, behinderten Menschen im Sinne des § 3 des Behindertengleichstellungsgesetztes, denen ohne die Erfüllung zusätzlicher Bedingungen die Nutzung
der Informationstechnik nur eingeschränkt möglich ist, den Zugang dazu zu
eröffnen.
Dazu sind die Angebote der Informationstechnik so zu gestalten, dass sie die im
Anhang unter Priorität I aufgeführten Anforderungen und Bedingungen erfüllen und
zentrale Navigations- und Einstiegsangebote zusätzlich die im Anhang unter Priorität
II aufgeführten Anforderungen und Bedingungen berücksichtigen.
4.1
Assistive Technologien
Bevor man sich an die Programmierung behindertengerechter Software macht, ist es
sinnvoll, sich mit den verschiedenen Kategorien von Behinderungen zu befassen und
mögliche behindertengerechte Hardware-Technologien zu berücksichtigen.
Folgende Kategorien von Behinderungen sind für einen Programmierer von
Bedeutung:
30
Aufbereitung derAccessibility
Anwendung31
31
o
Visuell (Blindheit, Sehschwäche): Schwierigkeiten mit bildschirmbasierten
Inhalten.
o
Auditiv: Schwierigkeiten mit akustischen Inhalten
o
Physisch: Schwierigkeiten mit Standard-Eingabegeräten wie Tastatur oder
Maus
Es existieren diverse Ein- und Ausgabegeräte, die es einem behinderten Menschen
ermöglichen, Computer zu benutzen, vorrausgesetzt, die Software unterstützt diese
assistiven Technologien. Das sind z.B.:
o
Screen Reader (Bildschirmlesegeräte): Stellen GUI-Inhalte akustisch oder als
Braille dar.
o
Screen Magnifiers (Bildschirmvergrößerer): Ermöglichen eine Vergrößerung
des Bildschirminhalts
o
Speech Recognition (Spracherkennung): Ermöglicht es, Maus und Tastatur
über Sprache zu benutzen
o
Alternative Tastaturen: z.B. On-Screen-Tastaturen
o
Alternative Mäuse: z.B. Head Pointing Devices, die Mauseingabe mit dem Kopf
ermöglichen
Um Java-Software nun barrierefrei zu gestalten gibt es zwei mögliche Ansätze:
Graphische Benutzerschnittstellen von Java-Programmen werden von vornherein so
entwickelt, dass assistive Technologien direkt auf diese GUI zugreifen können. Dieser
Ansatz basiert auf der Java Accessibility API
Mit der Java Pluggable Look and Feel Architektur ist es möglich, nicht barrierefreie
Software nachträglich zu ersetzten oder zu erweitern, um somit unterschiedlichen
Systemanforderungen zu genügen.
In dieser Vorlesung ist nur der erste Ansatz relevant. Das Thema Look and Feel wird in
einem gesonderten Kapitel behandelt.
4.2
Aufbereitung der Anwendung
Java macht es einem recht leicht, zugängliche Software zu erstellen, denn Swing
liefert die Accessibility-Unterstützung schon mit, sie wurde direkt in die SwingKomponenten eingebaut. Schon ohne viel Aufwand ist Swing accessible:
Tooltips werden von assistiven Technologien benutzt, um die Komponente
zu beschreiben.
31
Accessibility
32
Mnemonics und accelerators ermöglichen eine volle Funktionalität ohne
Maus.
Label können mit Komponenten verbunden werden und so den Fokus auf die
angegebene Komponente übertragen, wenn das angezeigte Kürzel aktiviert
wird.
4.2.1
Die Java Accessibility API
Die Java Accessibility API ist ein Teil des JDK seit der Version 1.2. Allgemein kann die
Funktion der API wie folgt beschrieben werden:
Ein GUI-Element (z.B. eine Schaltfläche) wird durch die Implementierung des
Accessible-Interface als zugänglich deklariert. Das garantiert die Rückgabe eines
AccessibleContext-Objektes, sofern es angefragt wird.
Ein AccessibleContext-Objekt enthält die Basisinformationen, die notwendig
sind, um das GUI-Element für assistive Technologien zugänglich zu machen (z.B.
Name, Beschreibung, Zustand, usw.)
Darüber hinaus können zusätzliche Objekte bereitgestellt werden, die für das
entsprechende Element relevant sein können, z.B. AccessibleAction für
Schaltflächen oder AccessibleValue für Scrollbars oder Fortschrittsbalken.
Konkret besteht die Java Accessibility API aus einigen Klassen und Interfaces, von
denen die wichtigsten hier beschrieben werden:
Accessible: Dieses Interface muß vom Objekt implementiert werden, damit es als
barrierefrei erkannt wird. Alle Swing- und viele AWT-Komponenten implementieren
dieses Interface.
AccessibleContext: Enthält die Basisinformationen (Name, Beschreibung, Rolle
und Zustand des Objekts) für unterstützende Soft- und Hardware und bietet die
Möglichkeit, weitere speziellere Informationen abzufragen.
AccessibleAction: Sollte von jedem Objekt implementiert werde, das Aktionen
ausführen kann. Ein JRadioButton hat z.B. Als einzige Aktion "Klicken", ein
JTextField hat um die 50 Aktionen.
AccessibleComponent: Dieses Interface liefert Informationen über alle graphisch
präsentierten UI-Elemente, wie z.B. Vorder- und Hintergrundfarbe, Cursor,
Schriftarten, Position auf dem Bildschirm, Größe, usw.
AccessibleSelection: Spezielles Interface für Objekte, die „auswählbar“ sind.
(Menüs, Verzeichnisbäume, Listen). Damit können Informationen über bereits
ausgewählte Optionen erfragt oder Optionen gewählt werden.
32
Aufbereitung derAccessibility
Anwendung33
33
AccessibleText: Dieses Interface stellt Informationen für Textfelder und deren
Inhalt bereit. Dazu gehören z.B. der Index unter der Maus, das Wort und der Satz
unter der Maus, der Buchstabe vor und nach der Maus, usw.
AccessibleValue: Objekte, die einen Wert aus vielen auswählen (z.B. Schieberegler)
benötigen dieses Interface. Damit können sowohl die Minimal- als auch die
Maximalwerte abgefragt werden, sowie der aktuelle Wert gelesen oder gesetzt werden.
4.2.2
Die IBM Java Accessibility Checkliste
Die Java Accessibility Checkliste der Firma IBM zeigt detailliert, welche
Voraussetzungen zugängliche Software erfüllen muss. Die Liste ist im Netz unter
http://www-03.ibm.com/able/guidelines/java/accessjava.html erhältlich. Anhand
dieser Checkliste soll nun ein barrierefreies Programm erstellt werden.
IBM Java accessibility checklist - version 3.6
1
1.1
1.2
Keyboard access
Provide keyboard equivalents for all actions.
Do not interfere with keyboard accessibility features built into the
operating system.
2
2.1
Object information
Implement the Java Accessibility API by:
- using the Java Foundation Classes (JFC) / Swing components and/or
- following the guidelines for " Building Custom Components" when
extending the Java Foundation Classes and when implementing the Java
Accessibility API on custom components.
Set the focus.
Set the name on all components and include the description on icons and
graphics. If an image is used to identify programmatic elements, the
meaning of the image must be consistent throughout the application.
Associate labels with controls, objects, and icons.
When electronic forms are used, the form shall allow people using assistive
technology to access the information, field elements and functionality
required for completion and submission of the form, including all
directions and cues.
2.2
2.3
2.4
2.5
3
3.1
3.2
3.3
33
Sound and multimedia
Provide an option to display a visual cue for all audio alerts.
Provide accessible alternatives to significant audio and video.
Provide an option to adjust the volume.
Accessibility
34
4
4.1
4.2
4.3
4.4
4.5
5
5.1
5.2
6
6.1
Totals
4.2.3
Display
Use color as an enhancement, not as the only way to convey information or
indicate an action.
Support system settings for high contrast for all user interface controls and
client area content.
When color customization is supported, provide a variety of color
selections capable of producing a range of contrast levels.
Support system settings for size, font and color for all user interface
controls.
Provide an option to display animation in a non-animated presentation
mode.
Timing
Provide an option to adjust timed responses or allow the instruction to
persist.
Avoid the use of blinking text, objects, or other elements.
Verify accessibility
Test for accessibility using
available tools.
Summary of checklist
Comments
Number of Yes Responses ______
Number of No Responses ______
Number of Planned Responses ______
Number of N/A Responses ______
Quelle: IBM Java™ accessibility checklist
Testen auf Accessibility
Um zu testen, ob die entwickelte Anwendung auch accessible ist, gibt es mehrere
Möglichkeiten. In den seltensten Fällen hat man externe assistive Technologien, wie
z.B. ein Braille-Display zur Verfügung, aus diesem Grund muss man sich anders
behelfen.
Zum einen bietet Sun mehrere Tools an, die es einem erlauben, weiterführende
Informationen zu dem entwickelten Programm anzuzeigen. Ein Beispiel wäre das
Programm „Ferret“. Um es zu verwenden, müssen die Java Accessibility Utilities
(http://java.sun.com/javase/technologies/accessibility/downloads.jsp) heruntergeladen und sich die Dateien jaccess.jar und jaccess-examples.jar im
CLATHPATH befinden. Des Weiteren muss im Verzeichnis $JDKHOME/jre/lib/ eine
Datei accessibility.properties angelegt werden, in der die folgende Textzeile
steht:
assistive_technologies=Ferret
Nun wird beim Starten der Java-Anwendung automatisch das Programm „Ferret“
mitgestartet und gibt so die Möglichkeit, die gewünschten Informationen
abzurufen.
34
Aufbereitung derAccessibility
Anwendung35
35
4.2.4
Ein einfaches Swing-Programm
Begonnen wird mit einer einfachen Anwendung,
ohne jeden zusätzlichen Aufwand bezüglich der
Zugänglichkeit. Dabei handelt es sich lediglich um
eine Anordnung mehrerer Swing-Elemente ohne
wirkliche Funktion, die im GUI-Editor zusammengestellt wurde.
Mit Ferret wird nun dieses kleine Programm auf
zusätzliche Informationen überprüft und liefert –
nach Einstellung von „Track Mouse“ beim
Überfahren der Schaltfläche „JButton“ die
folgenden Informationen:
Woher stammen diese Informationen?
Swing liefert schon einiges an Accessibility-Unterstützung mit. Der Quelltext der
Swing-Komponenten hilft hier weiter. Wie erwartet implementiert der JButton das
Interface Accessible:
public class JButton extends AbstractButton implements Accessible
Dieses Interface verlangt, dass JButton die Methode getAccessibleContext()
implementiert. Diese sieht wie folgt aus:
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleJButton();
}
return accessibleContext;
}
35
Accessibility
36
In der JButton-Klasse befindet sich die innere AccessibleJButton-Klasse:
protected class AccessibleJButton extends
AccessibleAbstractButton {
public AccessibleRole getAccessibleRole() {
return AccessibleRole.PUSH_BUTTON;
}
}
Leider liefert diese innere Klasse selbst kaum Informationen, lediglich die Rolle des
Buttons wird zurückgegeben. Schaut man jedoch in der AccessibleRole-Klasse
nach, erhält man folgendes:
public static final AccessibleRole PUSH_BUTTON = new
AccessibleRole("pushbutton");
Wobei aus „pushbutton“ im Deutschen eine “Schaltfläche” wird s. Zeile 4 (Ferret).
Sucht man nun in der AccessibleAbstractButton-Klasse (von dieser erbt
AccessibleJButton), findet man weiter gehende Informationen. (Die
AccessibleAbstractButton-Klasse ist eine innere Klasse von AbstractButton.)
Die Klasse implementiert die nötigen Interfaces, um die restlichen vom Ferret
angezeigten Informationen zu erfragen. So liefert zum Beispiel die
getAccessibleName()-Methode den Namen der Schaltfläche (Zeile 2), in unserem
Fall den Text der Beschriftung.
public String getAccessibleName() {
if (accessibleName != null) {
return accessibleName;
} else {
if (AbstractButton.this.getText() == null) {
return super.getAccessibleName();
} else {
return AbstractButton.this.getText();
}
}
}
Leider reicht es nicht aus, für ein barrierefreies Programm lediglich Swing-Komponenten zu verwenden. Soll das Programm wirklich zugänglich sein, orientiert man
sich am besten an der IBM-Checkliste. Anhand dieser Checkliste soll nun auch das
obige kleine Swing-Programm richtig barrierefrei werden.
4.2.4.1 Tastatur-Bedienung
Teil 1 der Checkliste behandelt die Bedienung der Anwendung per Tastatur. Jede
mögliche Aktion soll auch per Tastatur durchgeführt werden können.
36
Aufbereitung derAccessibility
Anwendung37
37
Benutzer, die nicht die Möglichkeit haben, eine Maus zu bedienen, sind darauf
angewiesen, alle Funktionen des Programms über die Tastatur bedienen zu können.
Darüber hinaus kann eine gute Tastatur-Unterstützung die Produktivität deutlich
steigern.
Um Punkt 1.1 der IBM-Checkliste zu erfüllen ist es notwendig, alle Elemente der
Anwendung ohne Maus erreichen und benutzen zu können. Standardmäßig ist eine
grundsätzliche Erfüllung dieser Anforderung in Swing schon eingebaut,
Vorraussetzung ist allerdings, dass focusable der einzelnen Elemente auf true
gesetzt ist (Standard-Einstellung). Mit dieser Einstellung ist es möglich, mit der
Tabulatur-Taste zwischen den Schaltflächen hin- und herzuwechseln, mit der SpaceTaste lassen sich Schaltflächendrücken, bzw. aktivieren, die Menübar erreicht man
über F10 und verlässt sie entweder durch die Auswahl eines Menüpunktes oder mit
der Esc-Taste. Innerhalb von Listen, Comboboxen und Menüeinträgen bewegt man
sich mit den Cursor-Tasten. Des Weiteren ist es möglich, Text in Textfeldern zu
selektieren, die geschieht mit den Cursor-Tasten bei gedrückter Shift-Taste.
Hinweis: Um die Schaltflächen in einer vernünftigen Reihenfolge zu erreichen, ist es
notwendig, die Swing-Elemente in der Reihenfolge zu erzeugen, in der sie
angesprungen werden sollen, zuverlässiger kann die Reihenfolge auch über die
setNextFocusableComponent()-Methode gesetzt werden.
Ohne zusätzlichen Aufwand, erfüllen Swing-Anwendungen bereits Punkt 1.1 der
Checkliste. Darüber hinaus wäre es wünschenswert Shortcuts und Mnemonics
bereitzustellen. Um das zu erreichen muss allerdings Hand angelegt werden. Sinnvoll
ist es, Schaltflächen und Menüs mit Mnemonics zu versehen, besonders häufig
verwendete Funktionen, wie zum Beispiel die Druck- oder Speicherfunktion sollten
über Shortcuts erreichbar sein. Mit der
setMnemonic()-Methode lassen sich
Swing-Elementen Mnemonics hinzufügen.
Der Netbeans-GUI-Editor erlaubt es einem,
diese Mnemonics besonders einfach
hinzuzufügen.
Die Groß- bzw. Kleinschreibung spielt
keine Rolle, die Schaltflächen lassen sich
mit Alt- und der entsprechenden Taste
bedienen. Kommt das in der
setMnemonic()-Methode übergebene
Zeichen im Text der Schaltfläche vor, wird
das Zeichen unterstrichen dargestellt.
Comboboxen und Textfeldern können keine Mnemonics erhalten, es ist jedoch
möglich einem der entsprechenden Schaltfläche zugeordnetes Label ein solches
Mnemonic zuzuweisen:
comboLabel.setDisplayedMnemonic('o');
comboLabel.setLabelFor(textField);
37
Accessibility
38
Auch im Netbeans-GUI-Editor ist es möglich,
diese Einstellungen vorzunehmen (BindingTab).
Shortcuts, wie z.B. Strg-P für die
Druckfunktion werden auch als Keyboard
Accelerators bezeichnet. Sollen den
Menüeinträgen nun solche Shortcuts
zugewiesen werden, geschieht dasentweder
über die folgenden Befehle oder über die
entsprechende Einstellung im GUI-Editor:
jmi1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1,
InputEvent.CTRL_MASK));
jmi 2.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_2,
InputEvent.CTRL_MASK));
jmi 3.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_3,
InputEvent.CTRL_MASK));
Das Ergebnis der Bemühungen ist nun, dass sich die Menüeinträge mit Ctrl-1, Ctrl-2
und Ctrl-3 direkt ansprechen lassen und alle Schaltflächen über Mnemonics zu
erreichen sind.
Zuletzt sollte die Anwendung noch auf Tastaturtauglichkeit getestet werden, einfach
die Maus beiseite legen und ausprobieren, ob alles gut und schnell zu erreichen ist.
Klappt alles, ist Punkt 1.1 erfüllt!
Die meisten Betriebssysteme bieten von sich aus einige assistive Funktionen an. Nicht
jedem ist es beispielsweise möglich, gleichzeitig Strg-Alt-Entf zu drücken. Die
Einrastfunktion unter Windows erlaubt es, alle drei Tasten nacheinander zu drücken,
um so denselben Effekt wie gleichzeitiges Drücken zu ermöglichen. Solche und
ähnliche Funktionen, sollten durch die Java-Anwendung nicht überlagert werden.
Die unter Windows angebotenen Accessibility-Features finden sich in der folgenden
Tabelle (Quelle: IBM Accessibility Center):
38
Aufbereitung derAccessibility
Anwendung39
39
keyboard mappings
5 consecutive clicks of shift key
shift key held down for 8 seconds
6 consecutive clicks of control key
6 consecutive clicks of alt key
mobility access feature
on/off for stickykeys
on/off for slowkeys and repeatkeys
on/off for screen reader numeric keypad
future access use
Des Weiteren ist es wenig sinnvoll, gängige Shortcuts wie z.B. Strg-C für Kopieren mit
anderen Tastenkombinationen zu belegen, da dies zu maximaler Verwirrung führen
kann.
4.2.4.2 2. Objekt-Informationen
Teil 2.1 ist bereits abgehakt, da nichts anderes in diesem Skript vorgesehen ist. 
2.2 Fokus setzen
Wechselt man mit der Tastenkombination Alt-F6 zwischen verschiedenen JavaAnwendungen, oder unter Windows mit Alt-TAB zwischen Java- und Nicht-JavaAnwendungen hin- und her, kann es passieren, dass die Java-Anwendung keinen
Fokus mehr hat. Oftmals ist dies auch beim Start der Anwendung der Fall. Um das zu
ändern, ist es nötig einer Swing-Komponente den Default-Focus mit der
requestFocus()-Methode zuzuteilen.
Zum Testen sollte die Maus beiseite gelegt werden und zwischen verschiedenen
Anwendungen (Java und Nicht-Java) hin- und hergewechselt werden.
2.3 Grafiken und Icons beschreiben, Komponentennamen setzen
Personen, die blind sind, oder eine Sehschwäche haben, sind nicht in der Lage,
Grafiken zu verstehen, die für eine bestimmte Funktion wichtig sind. Auch können
Screen-Reader Grafiken logischerweise nicht „vorlesen“. Aus diesem Grund ist es
nötig, die Swing-Komponenten mit aussagekräftigen Namen und Beschreibungen zu
versehen, so dass anhand dieser Beschreibungen, die Funktion der entsprechenden
Schaltfläche erkannt werden kann. Selbstverständlich sollten Namen und
Beschreibungen nicht doppelt vergeben werden, wenn die Schaltflächen nicht
dieselbe Funktion aufweisen.
Die Schaltfläche in der Anwendung soll nun durch eine mit Icon, ohne Text ersetzt
werden. Es ist nun unter Umständen nicht mehr ersichtlich, welche Funktion diese
Schaltfläche hat. Als ergänzende Information wäre ein Tooltip nützlich, doch das
allein reicht nicht aus, um das Programm barrierefrei zu machen. Zusätzlich werden
noch Name und Beschreibung derSchaltfläche angegeben.
ImageIcon smiley = new javax.swing.ImageIcon("smiley.gif")
jButton1.setIcon(smiley);
jButton1.setToolTipText("Lächeln!");
jButton1.getAccessibleContext().setAccessibleName("LächelButton");
jButton1.getAccessibleContext().setAccessibleDescription("Hier
39
Accessibility
40
klicken, um zu lächeln!");
Mit diesen wenigen Zeilen Code, wird der Button mit einem Icon und einem Tooltip
versehen, zusätzlich bekommt er einen Namen und eine Beschreibung der Funktion.
Das Ergebnis sieht man hier:
Zwar ist die Schaltfläche nun umfassend beschrieben, jedoch die Grafik ist noch
unkommentiert. Auch dem Icon sollten beschreibende Informationen hinzugefügt
werden.
smiley.setDescription("Smiley-Icon");
Ferret liefert zu dem Icon nun die folgenden Informationen:
Checkpoint 2.3 ist nun erreicht, jedoch kann man an dem Programm noch etwas
verbessern. Gut wäre es, Komponenten, die logisch zusammengehören, zu gruppieren
und der Gruppe einen Namen zuzuweisen. Das erleichtert die Navigation sehr.
Es gibt mehrere Möglichkeiten, Swing-Komponenten zu einer Gruppe
zusammenzufassen. Eine wäre, die zusammengehörigen Elemente in eine eigene
JPanel zu legen und dieses zu benennen. Die 2. Alternative wäre ein JPanel mit einem
Rahmen (Titled Border).
Im Falle unseres Programms wäre eine Gruppierung der Checkboxen und
Radiobuttons denkbar.
jPanel1.setBorder(new TitledBorder("Checkboxen"));
jPanel1.add(jCheckBox1);
jPanel1.add(jCheckBox2);
Für die Radiobuttons funktioniert es analog.
40
Aufbereitung derAccessibility
Anwendung41
41
2.4 Beschriftungen zuweisen
Nicht nur für die Mnemonics ist es sinnvoll, den JLabels die dazugehörigen SwingKomponenten zuzuweisen, auch assistive Technologien wie beispielsweise
Screenreader benötigen diese Informationen. Sie sind sonst nicht in der Lage, die
Zuordnung vorzunehmen. Wie oben bereits angesprochen, ist dies mit der Methode
setLabelFor(JComponent comp) möglich.
Darüber hinaus sieht die IBM-Checkliste vor, dass Label und die dazugehörige
Komponente in beide Richtungen miteinander verbunden werden. Möglich macht
das die AccessibleRelation-Klasse.
AccessibleRelationSet arSet1 =
jLabel1.getAccessibleContext().getAccessibleRelationSet();
AccessibleRelation ar1 = new
AccessibleRelation(AccessibleRelation.LABEL_FOR,
jTextField1);
arSet1.add(ar1);
AccessibleRelationSet arSet2 = TextField1.getAccessibleContext()
.getAccessibleRelationSet();
AccessibleRelation ar2 = new
AccessibleRelation(AccessibleRelation.LABELED_BY, jLabel1);
arSet2.add(ar2);
4.2.4.3 3. Sound und Multimedia
3.1 Akkustische Signale zusätzlich visuell darstellen
Einige Benutzer sind nicht in der Lage, akustische Signale zu hören. Sei es, dass sie
gehörlos oder schwerhörig sind, oder aber keine Soundkarte besitzen, bzw. den Ton
abgestellt haben. Aus diesem Grund müssen akustische Signale auch visuell
dargestellt werden.
Werden in der Anwendung Audio-Signale verwendet, sollte es möglich sein, diese
vom Benutzer durch visuelle Signale ersetzen zu lassen, z.B. indem er im
Einstellungen-Dialog eine entsprechende Option auswählt. Mögliche visuelle Signale
41
Accessibility
42
wären Dialogfenster, z.B. JOptionPane- oder JDialog-Fenster. Darüber hinaus besteht
die Möglichkeit, bestriebsystemspezifische Ressourcen aufzurufen, die visuelle Signale
anbieten, wie z.B. das Blinken der aktiven Titelleiste. Für die letzte Möglichkeit ist das
„Java Native Interface (JNI)“ nötig, dessen Beschreibung hier den Rahmen sprengen
würde. Weiterführende Informationen zu JNI finden sich unter
http://java.sun.com/javase/6/docs/technotes/guides/jni/
Unser Beispiel soll nun so angepasst werden, dass der Benutzer mit den Radiobuttons
auswählen kann, ob er einen Sound hört, wenn er auf die Lächeln-Schaltfläche klickt,
oder aber nicht. Im zweiten Fall soll er ein Dialogfenster zu sehen bekommen.
Die Lächeln-Schaltfläche bekommt nun ein Event zugewiesen, das abhängig vom
Status der Radiobuttons einen Sound abspielt oder nicht. Die entsprechende Methode
könnte wie folgt aussehen:
private void smileActionPerformed(ActionEvent evt) {
if (smileRadioButton.isSelected()) {
try {
File f = new File("C:\\hihi.wav");
AudioClip clip = Applet.newAudioClip(f.toURI().toURL());
clip.play();
} catch (MalformedURLException mfu) {
}
} else {
JOptionPane.showMessageDialog(null, "Bitte Lächeln! :-)",
"Smile!",
JOptionPane.INFORMATION_MESSAGE);
}
}
3.2 Alternativen zu wichtigen Audio- und Videoinhalten bereitstellen
Für Audio- und Videoclips mit für die Anwendung wichtigen Inhalten müssen
Mitschriften zur Verfügung gestellt werden. Da es zurzeit noch keinen barrierefreien
Java Audio-Player gibt, müssen diese per Hand eingefügt werden. Die Mitschriften
42
Aufbereitung derAccessibility
Anwendung43
43
enthalten sowohl den kompletten gesprochenen Text des Clips als auch zusätzliche
„Status“-Informationen. Ein Beispiel:
[Telefonklingeln]
Mann: Gehst Du ans Telefon?
[Telefonklingeln]
[Telefonklingeln]
[Telefonklingeln]
Frau: Nein, der Anrufbeantworter soll drangehen!
[Im Hintergrund sieht man, wie am Anrufbeantworter ein Lämpchen zu blinken
beginnt]
3.3 Möglichkeit für die Regelung der Lautstärke anbieten
Das Java Media Framework bietet Möglichkeiten, einen Lautstärkeregler in die
Anwendung einzubauen. Darüberhinaus ist es oftmals schon ausreichend, wenn das
Betriebssystem ein solches Feature zur Verfügung stellt.
4.2.4.4 4. Anzeige
4.1 Farben nicht als einziges Mittel der Informationscodierung verwenden.
Der Einsatz von Farben kann für viele Anwendungen sinnvoll sein, jedoch muss man
beachten, dass nicht jeder in der Lage ist, Farben zu unterscheiden. Aus diesem Grund
sollte die Software die farbige Information noch auf andere Art- und Weise
hervorheben.
Elementen, die ausschließlich farbig sind (nicht zu dekorativen Zwecken), sollten
Textinformationen hinzugefügt werden. So sollte eine grüne Schaltfläche, die für eine
korrekte Eingabe steht, noch mit der entsprechenden Information versehen werden.
Es ist nicht ausreichend, farbige Texte in Kursiv- oder Fettschrift darzustellen, da dies
zwar jemandem hilft, der keine Farben unterscheiden kann, ein Screen Reader
hingegen verarbeitet diese Informationen nicht. Eine Mögliche Lösung wäre das
Voranstellen eines Zeichens, wie beispielsweise eines Sternchens.
Um die Anwendung zu testen, bietet es sich an, Screenshots zu erstellen und diese auf
einem Schwarz-Weiß-Drucker auszudrucken. Sind immer noch alle Informationen in
dem Ausdruck enthalten, dann ist Checkpunkt 4.1 erfüllt.
Die weitere Anwendung der IBM-Richtlinien würde den Rahmen der Vorlesung
sprengen. Aus diesem Grund sei Ihnen an dieser Stelle die IBM-Website ans Herz
gelegt: http://www-03.ibm.com/able/guidelines/java/accessjava.html
43
Drag and Drop
44
5
Drag and Drop
ToDo: Swing Hacks Kapitel 9
44
Clipboard
Aufbereitung
/ Zwischenablage
der Anwendung45
45
6
ToDo
45
Clipboard / Zwischenablage
Look-and-Feel
46
7
Look-and-Feel
Unter einem Look-and-Feel versteht man das Aussehen und das Verhalten der einzelnen Bedienelemente einer grafischen Benutzeroberfläche (GUI). Das Look-and-Feel ist
von Betriebssystem zu Betriebssystem unterschiedlich, jedes Betriebssystem hat seine
eigene Optik. Da Java auch für GUI-Anwendungen plattformübergreifend einsetzbar
sein soll, musste ein Weg gefunden werden, das Look-and-Feel der unterschiedlichen
Betriebsysteme in Java selbst darzustellen. Das Java Swing-Framework bemüht sich
dazu, die betriebssystemtypischen Komponenten so originalgetreu wie möglich
nachzubilden. Dies erfolgt nur durch Java2D-Zeichenoperationen und nicht wie beim
AWT-Framework durch Zurückgreifen auf die systemeigenen Bedienelemente.
7.1
Das Konzept des Pluggable-Look-and-Feel (PLAF)
Da das Swing-Framework auf allen von Java unterstützen Plattformen den gleichen
Code verwenden soll, es aber trotzdem dem Aussehen des jeweiligen Betriebssystems
angepasst erscheinen soll, war es notwendig, die optische Erscheinung der von Swing
gezeichneten Bedienelemente aus dem Kern des Swing-Framworks auszugliedern.
Diese Ausgliederung erfolgte in Form der PluggableLook-and-Feels, was dem Programmierer die Möglichkeit gibt, plattformübergreifend zu programmieren und auf seinem
eigenen System zu testen. Das Pluggable Look-and-Feel (PLAF) ist sogar zur Laufzeit
änderbar.
7.1.1
Das „alte“ Java Look-and-Feel (Metal Look-and-Feel)
Das erste Look-and-Feel, das entwickelt wurde, war das sogenannte Metal Look –andFeel (linke Abbildung). Inzwischen wurde es durch eine aufgehübschte Variante
ersetzt, das sogenannte Ocean Theme (rechts):
46
Das Konzept des Pluggable-Look-and-Feel
Look-and-Feel
(PLAF)47
47
Bis heute handelt es sich bei dem Metal-Look-and-Feel (Ocean Theme) um das
Standard Look-and-Feel, das auf jeder Plattform verfügbar ist und standardmäßig
verwendet wird..
7.1.2
Betriebsystemen nachempfundene Look-and-Feels
Für eine Hand voll Betriebssystele stellt Java Look-and-Feels bereit, die die
betriebssystemeigenen Komponenten nachbilden. In der Regel sind diese Look-andFeels nur auf den entsprechenden Plattformen verfügbar.
47
Look-and-Feel
48
7.1.3
Skinnable Look-and-Feel (Synth Look-and-Feel)
Möchte man auf vergleichsweise einfache und schnelle Art und Weise das Aussehen
einer Java-Anwendung verändern, läßt sich dies mit Hilfe des Synth Look-and-Feels
bewerkstelligen. Synth hat mit J2SE 5.0 Einzug in die Java-Welt erhalten und ist ein
sogenanntes „skinnable“ Look-and-Feel, mit Hilfe einer XML-Datei kann die JavaAnwendung mit einem neuen Erscheinungsbild versehen werden.
Im Rohzustand sieht Synth reichlich trist aus, erst mit Hilfe der XML-Datei wird die
Anwendung benutzbar (s. Kapitel 7.3).
7.1.4
Das „neue“ Java Look-and-Feel (Nimbus Look-and-Feel)
J2SE 6.0 Update 10 brachte ein erstes auf
Synth basierendes, eigenes Look-and-Feel,
Nimbus. Dieses Look-ans-Feel arbeitet mit
Vektorgrafiken und ist entsprechend
schnell und gut skalierbar. In Zukunft soll
Nimbus das Metal als Standard-Look-andFeel ablösen, ein Zeitpunkt steht jedoch
noch nicht fest. Aus Gründen der Abwärtskompatibilität hält Sun bislang noch an
Metal fest.
48
Wechsel des Look-and-Feels49
Look-and-Feel
49
7.2
Wechsel des Look-and-Feels
Über die Klasse UIManager kann das gewünschte Look-and-Feel gesetzt werden, dies
geschieht mit Hilfe der Methode setLookAndFeel(String className). Wird nun
das Look-and-Feel geändert, muss das den Swing-Komponenten mitgeteilt werden.
Dafür muss die SwingUtilities.updateComponentTreeUI(Component c)-Methode aufgerufen werden. Jetzt müssen nur noch die möglichen Exceptions abgefangen werden, und das Java-Programm erstrahlt in neuem Glanz.
try {
// Java LAF (Metal)
String plaf =
UIManager.getCrossPlatformLookAndFeelClassName();
//String plaf = "javax.swing.plaf.metal.MetalLookAndFeel";
// Zum Betriebssystem passendes LAF
//String plaf = UIManager.getSystemLookAndFeelClassName();
// LAFs per Hand setzen
//String plaf =
"com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
//String plaf =
"com.sun.java.swing.plaf.motif.MotifLookAndFeel";
//String plaf =
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
//String plaf =
"com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
//String plaf = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
//String plaf = "apple.laf.AquaLookAndFeel";
UIManager.setLookAndFeel(plaf);
} catch (UnsupportedLookAndFeelException e) {
//LAF darf mit diesem Betriebssystem nicht verwendet werden
System.err.println(e.toString());
} catch (ClassNotFoundException e) {
//Entsprechende Klasse ist nicht verfügbar
System.err.println(e.toString());
} catch (InstantiationException e) {
//LAF kann nicht instanziiert werden
System.err.println(e.toString());
} catch (IllegalAccessException e) {
//Auf die entsprechende Klasse kann nicht zugegriffen
werden
System.err.println(e.toString());
}
}
49
Look-and-Feel
50
Auf dieselbe Weise können selbstvertsändlich auch Look-and-Feels von Drittanbietern eingebunden werden, z.B. die JTattoo-Look-and-Feels:
7.3
Exkurs: Erstellen eines eigenen Skins
Um der eigenen Java-Anwendung auch ein eigenes Aussehen zu verleihen, ist die Erstellung eines Skins für Synth das Mittel der Wahl.
In diesem Exkurs wird von der bereits bekannten Look-and-Feel-Beispielanwendung
ausgehend ein eigener Skin erstellt. Hierzu ist es notwendig, das Synth Look-and-Feel
zu setzen sowie eine xml-Datei zu erstellen und anzugeben, die die Konfiguration für
das Aussehen enthalten soll. Entsprechend müssen auch Ausnahmen abgefangen
werden.
try {
String xmlFile = "beispielSkin.xml";
SynthLookAndFeel plaf = new SynthLookAndFeel();
plaf.load(LookAndFeelTest.class.getResourceAsStream(xmlFile),
LookAndFeelTest.class);
UIManager.setLookAndFeel(plaf);
} catch (UnsupportedLookAndFeelException e) {
//LAF darf mit diesem Betriebssystem nicht verwendet werden
System.err.println(e.toString());
} catch (ParseException e) {
//xml-Datei kann nicht verarbeitet werden
System.err.println(e.toString());
}
50
Exkurs: Erstellen eines Look-and-Feel
eigenen Skins51
51
Mit leerer xml-Datei kann Synth noch nicht verwendet werden, beim Ausführen des
Programms wird eine ParseException geworfen und auf das Metal Look-and-Feel zurückgegriffen. Doch eine minimale Änderung in der xml-Datei erzeugt das aus Kapitel
7.1.3 bekannte Bild:
<synth>
</synth>
Normalerweise würde nun von einem zuvor erstellten Entwurf ausgegangen, in
diesem Exkurs kommt es aber weniger auf ein Konzept an, sondern mehr darauf, die
verschiedenen Möglichkeiten zu demonstrieren. Am Ende dieses Eskurses steht also
ein Look-and-Feel, das wohl nicht für einen Designpreis nominiert werden wird. 
Im Grunde genommen besteht die Skin-xml-Datei aus aufeinanderfolgenden <style>Elementen, denen jeweils ein <bind />-Element folgt. Hierbei bezieht sich das einem
<style>-Element folgende <bind /> jeweils auf das vorausgegangene <style>-Element
und verknüpft die Style-Information mit der entsprechenden Komponente:
<synth>
<style id="default">
// Beschreibung von Farben, Schriftarten und Zuständen
</style>
<bind style="default" type="region" key=".*"/>
<style id="button">
// Beschreibung von Farben, Schriftarten und Zuständen
</style>
<bind style="button" type="region" key="Button"/>
</synth>
51
Look-and-Feel
52
In diesem Exkurs beschränken wir und auf eine Swing-Komponente pro <style>-Element, es ist jedoch auch möglich, mehrere Komponenten in einem <style>-Element
abzuhandeln und die xml-Möglichkeiten der Vererbung zu nutzen.
Die Parameter der bisher vorgestellen Elemente sind nur zum Teil selbsterklärend, aus
diesem Grunde soll hier kurz darauf eingegangen werden. Alle Elemente mit ihren
Parametern können zudem unter
http://java.sun.com/javase/6/docs/api/javax/swing/plaf/synth/docfiles/synthFileFormat.html nachgelesen werden.
Element:
<style></style>
Parameter:
id
Eindeutiger Name für das Stil-Element.
Element:
<bind />
Parameter:
style
Eindeutiger Name des vorangegangenen Stil-Elements, entspricht dem id-Parameter
des Stil-Elements.
Parameter:
type
Name der Region, aus dem die gewünschte Komponente stammt, normalerweise
„region“, wenn die Komponente aus
http://java.sun.com/javase/6/docs/api/javax/swing/plaf/synth/Region.html stammt.
Parameter:
key
Regulärer Ausdruck für die verwendete(n) Komponente(n). Üblicherweise wird dieser
Ausdruck aus
http://java.sun.com/javase/6/docs/api/javax/swing/plaf/synth/Region.html entnommen, wobei sich der Wert wir folgt ändert: Der Name der entsprechenden
Regions-Konstante wird in Kleinbuchstaben umgewandelt, der erste Buchstabe und
jeder einem Unterstrich folgende Buchstabe wird groß geschrieben, dann werden alle
Unterstriche entfernt. So wird also aus der Konstante ARROW_BUTTON der key-Wert:
ArrowButton.
Der default-Stil mit key = „.*“ gilt für alle Komponenten.
Zuerst soll nun die Standardschriftart für die Anwendung festgelegt werden, in unserem Fall Papyrus. Hierbei handelt es sich zwar nicht um eine sehr gut lesbare Schrift,
aber zumindest ist sofort erkennbar, ob die Änderung der xml-Datei Wirkung zeigt.
Das <font>-Element erlaubt noch einen weiteren Parameter, style, mit ihm kann die
Schrift fett (BOLD), kursiv (ITALIC) oder fett und kursiv (BOLD ITALIC) gesetzt
werden.
Im nächsten Schritt soll der Hintergrund aller Komponenten in einem beigen
Gelbton eingefärbt werden. Farben werden innerhalb eines <state>-Elementes
definiert, ohne weitere Angaben steht dieses für jeden beliebigen Zustand.
<synth>
<style id="default">
<font name="Papyrus" size="12"/>
52
Exkurs: Erstellen eines Look-and-Feel
eigenen Skins53
53
<state>
<color value="#fbdda7" type="BACKGROUND"/>
</state>
</style>
<bind style="default" type="region" key=".*"/>
</synth>
Als nächstes soll das Textfeld als solches erkennbar werden. Dazu wird zunächst die
Farbe des Hintergrundes aufgehellt und die Textfarbe gesetzt. Damit die allgemeinen
Farben vom Textfeld überschrieben werden, wird ein zusätzliches <opaque>-Element
verwendet:
<style id="textField">
<opaque value="true"/>
<state>
<color value="#fcf2e0" type="BACKGROUND"/>
<color value="#8d470c" type="TEXT_FOREGROUND"/>
</state>
</style>
<bind style="textField" type="region" key="TextField"/>
Welche Werte der Parameter type im <color />-Element annimt, steht im Dokument
http://java.sun.com/javase/6/docs/api/javax/swing/plaf/synth/ColorType.html. Nun
könnte man vermuten, dass mit type=“FOREGROUND“ die Rahmenfarbe des Textfeldes gesetzt wird, dies ist jedoch nicht der Fall. Um spezielle Bereiche bestimmter
Komponenten zu verändern benötigt man das <imagePainter />-Element. Wie der
Name vermuten lässt, funktioniert das mit zuvor erstellten Bildern. Da man nicht für
jede Textfeldgröße, bzw. die Größe einer anderen Komponente ein Bild bereitstellen
kann, kommt beim Synth Look-and-Feel das sogenannte „Image-Stretching“ zum
53
Look-and-Feel
54
Einsatz. Bei diesem Verfahren bleiben die Ecken eines Bildes innerhalb der
angegebenen Grenzen (Insets) erhalten, während der dazwischenliegende Bereich
dem Einsatzbereich entsprechend gedehnt wird. Bei der Dehnung handelt es sich in
der Tat um eine Verzerrung und nicht um eine Wiederholung der Pixel. Filigrane
Muster sind auf diese Weise nicht zu bewerkstelligen. In unserem Fall hat der Rahmen
einen Eckenbereich von jeweils 6 Pixeln, wie in den beiden (stark vergrößerten)
Grafiken zu sehen ist:
Das <imagePainter />- sowie das <insets>-Element werden dem <style>-Element des
Textfeldes untergeordnet. Die Reihenfolge der sourceInsets entspricht denen des
<insets />-Elementes (oben, links, unten, rechts).
<imagePainter method="textFieldBorder" path="border.png"
sourceInsets="6 6 6 6" paintCenter="false"/>
<insets top="3" left="3" bottom="3" right="3"/>
Nicht alle obigen Elemente und Parameter sind selbsterklärend, deshalb folgt eine
kurze Erklärung:
Element:
<imagePainter />
Parameter:
method
Funktionsname der javax.swing.plaf.synth.SynthPainter-Klasse
(http://java.sun.com/javase/6/docs/api/javax/swing/plaf/synth/SynthPainter.html)
beginnend mit einem Kleinbuchstaben und ohne vorangehendes „paint“. Für das
Textfeld stellt die Klasse zwei Zeichenmethoden zur Verfügung,
paintTextFieldBackground() und paintTextFieldBorder().
Parameter:
Pfad zur Bilddatei.
path
54
Exkurs: Erstellen eines Look-and-Feel
eigenen Skins55
55
Parameter:
sourceInsets
1. Parameter: Höhe der oberen Ecken
2. Parameter: Breite der linken Ecken
3. Parameter: Höhe der unteren Ecken
4. Parameter: Breite der rechten Ecken.
Parameter:
paintCenter
Gibt an, ob der Innenbereich, innerhalb der „insets“ gezeichnet werden soll (true),
oder ob der darunterliegende Hintergrund angezeigt werden soll (false).
Element:
<insets />
Im Grunde genommen handelt es sich hierbei um den Bereich, um den das Textfeld
vergrößert wird, damit Platz für den Eckenbereich geschaffen wird. Ein Wert von 0
bedeutet, dass sich das Textfeld in seiner Größe nicht ändert.
Führt man das Programm nun aus, sehen die Textfelder wie folgt aus:
Im rechten Bild ist deutlich zu erkennen, wie die Grafik (ursprünlich 3 einzelne Punkte) verzerrt wird. Folgende Bilder demonstrieren die Änderung der Textfeldgröße
durch das <insets />-Element:
55
Look-and-Feel
56
Dieselbe Technik läßt sich natürlich nicht nur bei Textfelden, sondern auch bei vielen
anderen Komponenten, bzw. Komponententeilen anwenden. Dabei sollte man nicht
nur an ähnlich geartete Elemente wie JButtons denken, auch JPanels und JTabPanes
können so mit einer Grafik versehen werden:
<style id="tabbedPaneContent">
<imagePainter method="tabbedPaneContentBackground"
path="OldPaper.jpg"
sourceInsets="80 70 80 70" paintCenter="trze"/>
<insets top="0" left="0" bottom="0" right="0"/>
</style>
Wie man sieht gilt der neue Hintergrund nicht für Listen, auch der Listen-Teil der
JComboBox wird in der ursprünglichen Hintergrundfarbe gezeichnet.
Auf ähnliche Weise wie bisher werden nun
weitere Komponenten mit einem neuen
Aussehen geschmückt:
Ab einem bestimmten Punkt sind einem
mit den bisher kennengelernten Möglichkeiten die Hände gebunden. Zusätzlich zu
den Möglichkeiten Schrift, Farbe, Hintergrund- und Rahmenbilder zu setzen, besitzen einige Komponenten spezifische
Eigenschaften, die über das <property />Element angesprochen werden können.
Alle möglichen Eigenschaften lassen sich
in folgendem Dokument nachschlagen:
56
Exkurs: Erstellen eines Look-and-Feel
eigenen Skins57
57
http://java.sun.com/javase/6/docs/api/javax/swing/plaf/synth/docfiles/componentProperties.html
Das <property />-Element funktioniert ähnlich wie ein Hashtable mit einer Schlüssel /
Wert-Paarung. Beim Schlüssel handelt es sich um die Eigenschaft der o.g. Liste, der
Wert ist dabei die id eines zuvor in einem <imageIcon />-Element festgelegten Bildpfades. Bei der Gelegenheit macht auch die Angabe eine aktiven und inaktiven Zustandes Sinn:
<style id="radioButton">
<imageIcon id="radio_off" path="radioOff.png"/>
<imageIcon id="radio_on" path="radioOn.png"/>
<property key="RadioButton.icon" value="radio_off"/>
<state value="SELECTED">
<property key="RadioButton.icon" value="radio_on"/>
</state>
</style>
<bind style="radioButton" type="region" key="RadioButton"/>
Die CheckBox funktioniert analog.
Bei der Gelegenheit könnte man den Button ebenfalls um einen Zustand erweitern, um dabei festzustellen, dass im gedrückten Zustand (warum auch
immer) die Textfarbe mit TEXT_FOREGROUND und nicht wie zuvor mit
FOREGROUND gesetzt wird.
<style id="button">
<opaque value="true"/>
<state>
<color value="#fbdda7" type="BACKGROUND"/>
<color value="#8d470c" type="FOREGROUND"/>
</state>
<state value="PRESSED">
<color value="#a6590f" type="BACKGROUND"/>
<color value="#fbdda7" type="TEXT_FOREGROUND"/>
</state>
<imagePainter method="buttonBorder" path="border.png"
sourceInsets="6 6 6 6" paintCenter="false"/>
<insets top="3" left="3" bottom="3" right="3"/>
</style>
<bind style="button" type="region" key="Button"/>
57
Look-and-Feel
58
Element:
<state />
Parameter:
id
Eindeutiger Name für das <state>-Element.
Parameter:
value
Mögliche Zustände sind: ENABLED (Standardeinstellung), MOUSE_OVER, PRESSED,
DISABLED, FOCUSED, SELECTED und DEFAULT.
Wie bei CheckBoxen und RadioButtons lassen sich die Höhe von Listeneinträgen,
Breite und Höhe des (noch) verzerrten Schieberegler-Anfassers, Abstände von Text zu
Tab uvm. definieren, indem man das <property />-Element verwendet . Handelt es
sich bei dem Wert der Eigenschaft nicht um eine zuvor definierte id muss als Parameter im <property />-Element noch der Typ angegeben werden. Außerdem ist zu beachten, dass das <property />-Element zum darüberliegenden <style>-Element passt. So
beziehen sich zwar die Abmessungen des Schieberegler-Anfassers auf ebendiesen
Anfasser, gehören abermit der Eigenschaft „Slider.thumbWith“ zur Region „Slider“
und nicht „SliderThumb“. Hier sollte man sich genau danach richten, was im
Properties-Dokument von Sun angegeben ist.
<style id="list">
<property key="List.cellHeight" type="integer" value="16" />
</style>
<bind style="list" type="region" key="List"/>
<style id="slider">
<property key="Slider.thumbWidth" type="integer" value="12" />
<property key="Slider.thumbHeight" type="integer" value="13"
/>
<property key="Slider.paintValue" type="boolean" value="false"
/>
</style>
<bind style="slider" type="region" key="Slider"/>
Angenommen, man möchte für die Liste der JComboBox andere Werte verwenden
(z.B. eine andere Zeilenhöhe angeben), dann kommt man auf o.g. Art und Weise
nicht zum Ziel. Hier reicht die Region „ComboBox“ nicht aus, um auf List.cellHeight
der ComboBox.list zuzugreifen. Die Region „ComboBox“ erlaubt zwar den Zugriff auf
ComboBox.list, aber weiter zu List.cellHeight kommt man nicht. Für diesen Fall sieht
der type-Parameter des<bind />-Elements einen weiteren Wert vor, „name“. Auf diese
Weise lässt sich „ComboBox.list“ direkt ansprechen und so die Höhe der Listeneinträge setzen:
58
Exkurs: Erstellen eines Look-and-Feel
eigenen Skins59
59
<style id="comBoBoxList">
<property key="List.cellHeight" type="integer" value="16" />
</style>
<bind style="comBoBoxList" type="name" key="ComboBox.list"/>
Mit dem bisher Gelernten lässt sich der Synth-Skin schon fast fertigstellen. Ein Wermutstropfen ist jedoch der schwarze Rahmen um die Liste der ComboBox. Sollte jemand zufällig auf eine Möglichkeit stoßen, diesen zu entfernen, bitte die Lösung an
karen.bensmann@fh-gelsenkirchen mailen. 
Von diesem kleinen Manko abgesehen ist das Skin schon fast fertig.
Zum Ende dieses Exkurses sollen die
Menüleiste und der Hintergrund der
Bereich hinter den Tabs etwas
verschönert werden. Um dies zu
erreichen sollen eigene Java-Klassen
verwendet werden. Im Fall der
Menüleiste kommt ein Farbverlauf zum
Einsatz. Zunächst wird von der Klasse
SynthPainter abgeleitet und dann die
paintMenuBarBackground-Methode
überschrieben. Innerhalb der Methode
wird ein einfacher Farbverlauf erzeugt.
Zugegebenermaßen passt er überhaupt
nicht zum restlichen Aussehen, soll aber
zu Anschauungszwecken trotzdem zum
Einsatz kommen.Diese eigene Klasse
wird dann in der xml-Datei aufgerufen,
und zwar mit Hilfe der <object />- und
<painter />-Elemente:
public class OldPaperPainter extends SynthPainter {
public void paintMenuBarBackground(SynthContext context,
Graphics g, int x, int y,
int w, int h) {
Graphics2D g2 = (Graphics2D)g;
g2.setPaint(new
GradientPaint((float)x, (float)y,
new Color(159, 81, 14),
(float)x, (float)(h/2),
new Color(251, 221, 167), true));
g2.fillRect(x, y, w, h);
g2.setPaint(null);
}
}
59
Look-and-Feel
60
<style id="menuBar">
<object id="gradientBack"
class="OldPaperPainter"/>
<painter method="menuBarBackground"
idref="gradientBack"/>
</style>
<bind style="menuBar" type="region"
key="MenuBar"/>
Element:
<object />
Parameter:
id
Eindeutiger Name für das <object />-Element.
Parameter:
Klassenname
class
Element:
<painter />
Parameter:
idref
Referenz auf das <object />-Element, das den Klassennamen enthält.
Parameter:
Methodenname
method
Der Hintergrund der TabPane soll mit einer Textur versehen werden. Zusätzlich werden diesmal in der xml-Datei Werte in die UIDefaults-Tabelle geschrieben werden
(Details zu dieser Tabelle gibt es im folgenden Kapitel). Diese Werte werden in der
neuen Methode dann ausgelesen und fließen in die Textur ein. Dieses Vorgehen hat
den Vorteil, dass ein nicht Programmieraffinier Designer die Werte einfach in die
xml-Datei schreiben kann.
Die Textur wird in Form einer sich wiederholenden Kachel erzeugt. Alle paar Zeilen
wird soll ein unregelmäßiges Muster erzeugt werden, das den Eindruck eines unregelmäßigen Papiers vermitteln soll. Farbwerte und Musterparameter werden aus der
UIDefaults-Tabelle ausgelesen:
@Override
public void paintTabbedPaneTabAreaBackground(SynthContext
context,
Graphics g, int x, int y,
nt w, int h) {
final UIDefaults uiDefaults = UIManager.getDefaults();
Color mainColor =
uiDefaults.getColor("SprinkledPaper.mainColor");
Color sprinkleColor =
uiDefaults.getColor("SprinkledPaper.sprinkleColor");
int width = uiDefaults.getInt("SprinkledPaper.width");
60
Exkurs: Erstellen eines Look-and-Feel
eigenen Skins61
61
int height = uiDefaults.getInt("SprinkledPaper.height");
int sprinklePercentage =
uiDefaults.getInt("SprinkledPaper.sprinklePercentage");
// Anzahl Zeilen, die eine gesprenkelte Zeile enthalten, != 0
int linesLoop = uiDefaults.getInt("SprinkledPaper.linesLoop");
if (linesLoop == 0) {
linesLoop = 1;
}
// Erste Zeile mit Sprenkeln
int firstSprinkle =
uiDefaults.getInt("SprinkledPaper.firstSprinkle");
Graphics2D g2 = (Graphics2D) g;
BufferedImage bi = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D big = bi.createGraphics();
big.setColor(mainColor);
big.fillRect(0, 0, width, height);
Random randomGenerator = new Random();
for (int i = 0; i < height; i++) {
if (i % linesLoop == firstSprinkle) {
for (int j = 0; j < width; j++) {
int randomInt = randomGenerator.nextInt(100);
if (randomInt < sprinklePercentage) {
big.setColor(sprinkleColor);
} else {
big.setColor(mainColor);
}
big.fillRect(j, i, 1, 1);
}
}
}
Rectangle r = new Rectangle(0, 0, width, height);
g2.setPaint(new TexturePaint(bi, r));
g2.fillRect(x, y, w, h);
g2.setPaint(null);
}
Die verschiedenen Parameter zum besseren Verständnis als Grafik:
61
Look-and-Feel
62
Setzen der Werte in die UIDefaults-Tabelle und Anwenden der
paintTabbedPaneTabAreaBackground-Methode in der xml-Datei:
<style id="uidefaults">
<object id="MainColor" class="java.awt.Color">
<int>232</int>
<int>188</int>
<int>113</int>
</object>
<defaultsProperty key="SprinkledPaper.mainColor" type="idref"
value="MainColor"/>
<object id="SprinkleColor" class="java.awt.Color">
<int>251</int>
<int>221</int>
<int>167</int>
</object>
<defaultsProperty key="SprinkledPaper.sprinkleColor"
type="idref" value="SprinkleColor"/>
<defaultsProperty key="SprinkledPaper.width" type="integer"
value="100"/>
<defaultsProperty key="SprinkledPaper.height" type="integer"
value="24"/>
<defaultsProperty key="SprinkledPaper.sprinklePercentage"
type="integer" value="70"/>
<defaultsProperty key="SprinkledPaper.linesLoop"
type="integer" value="3"/>
<defaultsProperty key="SprinkledPaper.firstSprinkle"
type="integer" value="1"/>
</style>
<style id="tabbedPaneTabArea">
<object id="texturedBack" class="OldPaperPainter"/>
<painter method="tabbedPaneTabAreaBackground"
idref="texturedBack"/>
</style>
<bind style="tabbedPaneTabArea" type="region"
key="TabbedPaneTabArea"/>
62
Exkurs: Erstellen eines Look-and-Feel
eigenen Skins63
63
Damit ist der Exkurs beendet. Natürlich ist der Skin noch weit davon fertig zu sein, bis
alle Komponenten inklusive alle Zustände implementiert sind, steht noch ein gutes
Stück Arbeit bevor. Alle Quellen und Grafiken liegen zum Download bereit.
63
Look-and-Feel
64
7.4
Exkurs: Schreiben eines eigenen Look-and-Feels
Ein eigenes Look-and-Feel zu programmieren, ist eine recht langwierige und zum Teil
frustrierende Angelegenheit. Bevor man sich dazu entschließt, ein eigenes Look-andFeel zu schreiben, sollte man sich sicher sein, dass die Erstellung eines Synth-Skins für
die Anforderungen nicht ausreicht.
Es gibt drei verschiedene Möglichkeiten, ein Look-and-Feel zu programmieren:
Man beginnt komplett bei Null, leitet von LookAndFeel sowie allen UIKomponenten ab, die in javax.swing.plaf definiert sind.
Man leitet von BasicLookAndFeel und seinen in javax.swing.plaf.basic
definierten UI-Komponenten ab.
Man leitet von einem bereits existierenden Look-and-Feel ab und ändert nur einzelne
Komponenten.
Es ist außerdem möglich, es mit einer Mischung von Punkt 2 und 3 zu versuchen,
einen Wettbewerb für besonders schönes Programmieren gewinnt man damit
allerdings nicht! („Been there, done that, got the T-Shirt.“)
Wie man ein Look-and-Feel programmiert,
lernt man am besten an einem Beispiel. Hier
soll das „Terminal Look-and-Feel“ umgesetzt
werden. Es ist ein recht einfaches Look-andFeel, aber zum Lernen durchaus geeignet,
wenn es auch aus ergonomischer Sicht
betrachtet nicht so der Weisheit letzter Schluss
ist.  (Die durchgestrichenen Komponenten
wurden nicht umgesetzt.
Man beginnt mit der Programmierung, indem
man die LookAndFeel-Klasse selbst erstellt. In
diesem Beispiel werden wir von der 2.
Möglichkeit Gebrauch machen und vom
BasicLookandFeel ableiten. Das schöne an
dieser Methode ist, dass das Basic Look-andFeel bereits alle Komponenten bereitstellt,
man also schrittweise die Komponenten
einfach austauschen kann. So ist direkt nach
dem Ableiten von BasicLookAndFeel und dem
Überschreiben der unten aufgeführten 5 abstrakten Methoden ein lauffähiges Lookand-Feel vorhanden.
import javax.swing.plaf.basic.BasicLookAndFeel;
public class TerminalLookAndFeel extends BasicLookAndFeel {
/** Creates a new instance of TerminalLookAndFeel */
public String getDescription() {
return "Terminal Look and Feel";
}
64
Exkurs: Schreiben eines eigenen Look-and-Feels65
Look-and-Feel
65
public String getID() {
return "Terminal";
}
public String getName() {
return "Terminal";
}
public boolean isNativeLookAndFeel() {
return false;
}
public boolean isSupportedLookAndFeel() {
return true;
}
}
Wird das Terminal Look-and-Feel nun in das Programm von oben eingebunden und
dieses gestartet, sieht man logischerweise das Fenster im Basic Look-and-Feel. Die
Icons für die Radiobuttons und die Checkboxen fehlen, da dem Terminal Look-andFeel die Speicherorte nicht bekannt sind.
Schritt für Schritt werden nun die Terminal-Komponenten eingebaut:
Der nächste noch recht einfache Schritt, ist die Festlegung der „defaults“. Die
BasicLookAndFeel.getDefaults()-Methode ruft in dieser Reihenfolge die
folgenden Methoden auf:
protected void initClassDefaults(UIDefaults table): In dieser Methode
steht, wo die Klassen für die einzelnen ComponenUIs zu finden sind, bzw. wie diese
heißen.
65
Look-and-Feel
66
protected void initSystemColorDefaults(UIDefaults table): Hier
werden die Standard-Farben für das Look-and-Feel festgelegt. Es ist sinnvoll, dies
auszulagern und nicht direkt in die ComponentUI-Methoden mit
reinzuprogrammieren, da so der Benutzer die Möglichkeit erhält die Farben zu
ändern.
protected void initComponentDefaults(UIDefaults table): Über die
Systemfarben hinaus werden hier die Farben für die einzelnen Komponenten, sowie
Schriftarten, Rahmen und Icons festgelegt.
Am einfachsten beginnt man mit den System-Farben, dort lassen sich schon sehr
schnell gute Erfolge erzielen, indem man in der TerminalLookAndFeel-Klasse einfach
die entsprechende Methode überschreibt. Die beiden anderen Methoden können bei
dieser Gelegenheit auch bereits vorbereitet werden:
protected void initClassDefaults(UIDefaults table) {
super.initClassDefaults(table);
Object [] classes = {
//Defaults
};
table.putDefaults(classes);
}
protected void initSystemColorDefaults(UIDefaults table) {
String [] colors = {
"control", "#000000",
"controlText", "#00ff00",
"controlHighlight", "#00ff00",
"controlLtHighlight", "#00ff00",
"scrollbar", "#000000",
"controlShadow", "#00ff00",
"controlDkShadow", "#00ff00",
};
loadSystemColors(table, colors, false);
}
protected void initComponentDefaults(UIDefaults table) {
super.initComponentDefaults(table);
Object [] defaults = {
// Defaults;
};
table.putDefaults(defaults);
}
Welcher Parameter dabei welche Änderung bewirkt, lässt sich am besten durch Testen
herausfinden, so wurde im linken Bild testweise controlHighlight in rot (#ff0000) und
controlLtHighlight in gelb (#ffff00) gefärbt, im rechten Bild controlShadow in rot und
controlDkShadow in gelb.
66
Exkurs: Schreiben eines eigenen Look-and-Feels67
Look-and-Feel
67
Vorläufig werden erstmal all diese Werte auf grün gesetzt, um die Strichstärke, bzw.
das Einsetzen der entsprechenden Highlight-/Shadow-Komponenten kümmert man
sich später in der UI-Klasse.
Als nächstes bietet es sich an, eine Icon-Factory für die Radiobuttons und die
Checkboxen zu schreiben. Man spart sich dadurch das Überschreiben der
entsprechenden UI-Klassen. Der Code für das Terminal Radiobutton-Icon könnte wie
folgt aussehen:
67
Look-and-Feel
68
public class TerminalIconFactory {
private static Icon radioButtonIcon;
public static Icon getRadioButtonIcon() {
if (radioButtonIcon==null) {
radioButtonIcon = new RadioButtonIcon();
}
return radioButtonIcon;
}
private static class RadioButtonIcon implements Icon,
UIResource, Serializable {
private static final int size = 12;
public int getIconHeight() {
return size;
}
public int getIconWidth() {
return size;
}
public void paintIcon(Component c, Graphics g, int x, int
y) {
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
g.setColor(UIManager.getColor("RadioButton.foreground"));
g.drawOval(x, y, size-1, size-1);
if (model.isSelected()) {
g.fillOval(x+3, y+3, size-6, size-6);
}
}
}
}
Beim Starten des Programms passiert erstmal noch nichts, da das Look-and-Feel noch
nichts von dem neuen Icon weiß. Deswegen muss diese Information, zusammen mit
der Farbe für den Radiobutton, dem defaults-Array mitgeteilt werden:
protected void initComponentDefaults(UIDefaults table) {
super.initComponentDefaults(table);
Object [] defaults = {
"RadioButton.icon",
TerminalIconFactory.getRadioButtonIcon(),
"RadioButton.foreground", table.get("controlHighlight"),
68
Exkurs: Schreiben eines eigenen Look-and-Feels69
Look-and-Feel
69
table.putDefaults(defaults);
}
Das Ergebnis:
Wem der Weltraumkeks-Look der Kreise
nicht gefällt, der hat die Möglichkeit,
das Graphics-Objekt in ein Graphics2DObjekt zu casten, und dort die
RenderingHints entsprechend
anzupassen. Alle draw-Methoden
müssen dann natürlich von dem neuen
Objekt gezeichnet werden.:
public void paintIcon(Component c, Graphics g, int x, int y) {
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(UIManager.getColor("RadioButton.foreground"));
g2d.drawOval(x, y, size-1, size-1);
if (model.isSelected()) {
g2d.fillOval(x+3, y+3, size-6, size-6);
}
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
Es ist sinnvoll, am Ende der paintIcon-Methode den ANTIALIAS-Wert wieder auf „off“
zu setzten, denn Anti-Aliasing geht sehr zu Lasten der Performance.
Analog wird mit der CheckBox verfahren, hier ist die Umwandlung in ein
Graphics2D-Objekt allerdings nicht notwendig, die Zeichenmethode könnte
beispielsweise so aussehen:
69
Look-and-Feel
70
public void paintIcon(Component c, Graphics g, int x, int y) {
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
int anfang = 0;
int ende = size-1;
int mitte = (size-1)/2;
g.setColor(UIManager.getColor("CheckBox.foreground"));
g.drawLine(anfang+x, mitte+y, mitte+x, ende+y);
g.drawLine(mitte+x, ende+y, ende+x, mitte+y);
g.drawLine(ende+x, mitte+y, mitte+x, anfang+y);
g.drawLine(mitte+x, anfang+y, anfang+x, mitte+y);
anfang = 3;
ende = size-4;
mitte = (size-1)/2;
if (model.isSelected()) {
g.drawLine(anfang+x, mitte+y, mitte+x, ende+y);
g.drawLine(anfang+x+1, mitte+y-1, mitte+x+1, ende+y-1);
g.drawLine(anfang+x+2, mitte+y-2, mitte+x+2, ende+y-2);
g.drawLine(anfang+x+3, mitte+y-3, mitte+x+3, ende+y-3);
}
}
Nun wird es langsam kompliziert. Das Erzeugen der UI-Klassen kann sehr frustrierend
sein. Insbesondere Comboboxen können sich zur reinsten Heimsuchung entwickeln.
Es lohnt sich in jedem Fall, den Java-Sourcecode zur Hand zu haben, um sich einen
70
Exkurs: Schreiben eines eigenen Look-and-Feels71
Look-and-Feel
71
Überblick zu verschaffen, was zu tun ist. Denn mit dem Überschreiben der Methoden
allein ist es nicht getan, man muss auch wissen, was in den Methoden vor sich geht.
In dem Fall der ButtonUI hilft ein Blick auf den SourceCode der MetalButtonUIKlasse. Das Motivierende an diesem Sourcecode ist außerdem, dass man schnell
feststellt, dass die Programmierer von Sun auch nur Menschen sind. ;)
Der Source Code befindet sich im Installationsverzeichnis des JDK in der Datei src.zip.
Die ersten paar Methoden erstellt man immer nach „Schema F“:
public class TerminalButtonUI extends BasicButtonUI {
private final static TerminalButtonUI terminalButtonUI = new
TerminalButtonUI();
public static ComponentUI createUI(JComponent c) {
return terminalButtonUI;
}
public void installDefaults(AbstractButton b) {
super.installDefaults(b);
}
public void uninstallDefaults(AbstractButton b) {
super.uninstallDefaults(b);
}
}
Nun kommt der unerfreuliche Teil. Schaut man sich die paintText-Methoden der
BasicButtonUI an, stellt man fest, dass nur eine der beiden Methoden benutzt und
überschrieben werden darf, und zwar die, die als 2. Parameter AbstractButton b und
nicht JComponent c erhält. Diese 2. Methode ruft wiederum die „unerwünschte“
Methode mit dem JComponen-Parameter auf. Diese Methode verwendet Farben, auf
die wir mit unserer initComponentDefaults-Tabelle keinen Zugriff haben und die und
den Text des deaktivierten Buttons nicht zeichnen würde.. Also schlagen wir zwei
Fliegen mit einer Klappe, indem wir die richtige (erwünschte) Methode überschreiben
und uns an der paintText-Methode von MetallButtonUI orientieren (Änderungen in
Kommentaren):
protected void paintText(Graphics g, AbstractButton b, Rectangle
textRect, String text) {
//AbstractButton b = (AbstractButton) c;
//Diese Typumwandlung brauchen wir nicht, b wird übergeben
ButtonModel model = b.getModel();
//FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
FontMetrics fm = SwingUtilities2.getFontMetrics(b, g);
int mnemIndex = b.getDisplayedMnemonicIndex();
/* Draw the Text */
if (model.isEnabled()) {
71
Look-and-Feel
72
/*** paint the text normally */
g.setColor(b.getForeground()); // Farbe von controlText
} else {
/*** paint the text disabled ***/
//g.setColor(getDisabledTextColor());
g.setColor(UIManager.getColor("Button.disabled"));
// Farbe aus initComponentDefaults (Button.disabled)
}
SwingUtilities2.drawStringUnderlineCharAt(/*c*/ b, g, text,
mnemIndex,
textRect.x, textRect.y + fm.getAscent());
}
Damit man beim Terminal Look-and-Feel erkennen kann, dass der Button gedrückt
ist, soll sich die Farbe in ein dunkles Grau ändern. Deswegen ist es notwendig, die
Hintergrundfarbe des gedrückten Buttons anzupassen. Zu diesem Zweck wird die
paintButtonPressed()-Methode überschrieben:
protected void paintButtonPressed(Graphics g, AbstractButton b) {
if ( b.isContentAreaFilled() ) {
Dimension size = b.getSize();
// g.setColor(getSelectColor()); (MetallButtonUI)
g.setColor(UIManager.getColor("Button.pressed"));
g.fillRect(0, 0, size.width, size.height);
}
}
Analog zu den Radiobuttons und Checkboxen, müssen die Button-eigenen Farben
auch in der TerminalLookAndFeel-Klasse eingetragen werden. Des Weiteren muss der
initClassDefaults-Methode (TerminalLookAndFeel-Klasse)mitgeteilt werden, wo sich
die ButtonUI-Klasse befindet.
protected void initComponentDefaults(UIDefaults table) {
super.initComponentDefaults(table);
Object[] defaults = {
"RadioButton.icon",
TerminalIconFactory.getRadioButtonIcon(),
"RadioButton.foreground", table.get("controlHighlight"),
"CheckBox.icon", TerminalIconFactory.getCheckBoxIcon(),
"CheckBox.foreground", table.get("controlHighlight"),
"Button.foreground", table.get("controlHighlight"),
"Button.background", table.get("control"),
"Button.pressed", new ColorUIResource(102, 102, 102),
"Button.disabled", new ColorUIResource(151, 151, 151),
};
table.putDefaults(defaults);
}
72
Exkurs: Schreiben eines eigenen Look-and-Feels73
Look-and-Feel
73
protected void initClassDefaults(UIDefaults table) {
super.initClassDefaults(table);
Object [] classes = {
"ButtonUI", "TerminalButtonUI",
};
table.putDefaults(classes);
}
Im gedrückten Zustand ist der Button nun dunkelgrau, die deaktivierte Schaltfläche
wird ebenfalls korrekt gezeichnet. Auch die Mnemonic funktioniert wie gewünscht,
sofern man eine definiert. Zugegebenermaßen hätte sie das auch vorher getan. 
Aus der TerminalButtonUI-Klasse heraus kann man auf den Rahmen des Buttons
keinen Einfluss nehmen, zumindest nicht, ohne die komplette, recht komplizierte
installDefaults-Methode zu überschreiben. In dieser Methode der BasicButtonUIKlasse wird das Look-and-Feel mit der Anbringung des Rahmens beauftragt. Der
Rahmen selbst wird in einer Border-Factory erstellt. Das soll nun der nächste Schritt
sein. Dazu leitet man die BasicBorders-Klasse ab und schaut sich den Quellcode
einmal bei Licht an. Was dabei rauskommen könnte sieht dann so aus:
public class TerminalBorders extends BasicBorders {
public static Border getButtonBorder() {
Border buttonBorder = new
BorderUIResource.CompoundBorderUIResource(
new TerminalBorders.ButtonBorder(), new MarginBorder());
return buttonBorder;
}
public static class ButtonBorder extends AbstractBorder
implements UIResource {
73
Look-and-Feel
74
public void paintBorder(Component c, Graphics g, int x, int
y, int width, int height) {
boolean isPressed = false;
boolean isDefault = false;
Color foreground =
UIManager.getColor("Button.foreground");
if (c instanceof AbstractButton) {
AbstractButton b = (AbstractButton)c;
ButtonModel model = b.getModel();
foreground=b.getForeground();
isPressed = model.isPressed() && model.isArmed();
if (c instanceof JButton) {
isDefault = ((JButton)c).isDefaultButton();
}
}
if (c.isEnabled()) {
g.setColor(foreground);
} else {
g.setColor(UIManager.getColor("Button.disabled"));
}
g.drawRect(x, y, width-1, height-1);
}
public Insets getBorderInsets(Component c)
{
return getBorderInsets(c, new Insets(0,0,0,0));
}
public Insets getBorderInsets(Component c, Insets insets) {
insets.top = 2;
insets.left = insets.bottom = insets.right = 3;
return insets;
}
}
}
Wie üblich müssen die defaults noch angepasst werden:
"Button.border", TerminalBorders.getButtonBorder(),
Die erste ComponenUI ist damit (fast, der Fokus fehlt noch) fertig. Hier sieht man den
bisherigen Stand mit einem aktiven (links) und einem inaktiven Button (rechts), für
den weniger aufmerksamen Leser sei hier angemerkt, dass nun nicht nur die
deaktivierte Schaltfläche einen grauen Rahmen erhalten hat, sondern sich darüber
hinaus die Rahmenbreite verringert hat.
74
Exkurs: Schreiben eines eigenen Look-and-Feels75
Look-and-Feel
75
Alle anderen Komponenten im Detail zu erklären, würde an dieser Stelle zu weit
führen. Der Quelltext des Terminal Look-and-Feel (allerdings nur mit einer Hand voll
weiteren Komponenten) liegt zum Download bereit.
75
Anhang
76
8
Anhang
8.1
Barrierefreie Informationstechnik-Verordnung (BITV)
Priorität I
Anforderung
1
Für jeden Audio- oder visuellen Inhalt sind geeignete äquivalente
Inhalte bereitzustellen, die den gleichen Zweck oder die gleiche
Funktion wie der originäre Inhalt erfüllen.
Bedingung
1.1
Für jedes Nicht-Text-Element ist ein äquivalenter Text
bereitzustellen. Dies gilt insbesondere für: Bilder, graphisch
dargestellten Text einschließlich Symbolen, Regionen von
Imagemaps, Animationen (z. B. animierte GIFs), Applets und
programmierte Objekte, Zeichnungen, die auf der Verwendung
von Zeichen und Symbolen des ASCII-Codes basieren (ASCIIZeichnungen), Frames, Scripts, Bilder, die als Punkte in Listen
verwendet werden, Platzhalter-Graphiken, graphische Buttons,
Töne (abgespielt mit oder ohne Einwirkung des Benutzers),
Audio-Dateien, die für sich allein stehen, Tonspuren von Videos
und Videos.
1.2
Für jede aktive Region einer serverseitigen Imagemap sind
redundante Texthyperlinks bereitzustellen
1.3
Für Multimedia-Präsentationen ist eine Audio-Beschreibung der
wichtigen Informationen der Videospur bereitzustellen.
1.4
Für jede zeitgesteuerte Multimedia-Präsentation (insbesondere
Film oder Animation) sind äquivalente Alternativen (z.B.
Untertitel oder Audiobeschreibungen der Videospur) mit der
Präsentation zu synchronisieren.
Anforderung
2
Texte und Graphiken müssen auch dann verständlich sein, wenn
sie ohne Farbe betrachtet werden.
Bedingung
2.1
Alle mit Farbe dargestellten Informationen müssen auch ohne
Farbe verfügbar sein, z.B. durch den Kontext oder die hierfür
vorgesehenen Elemente der verwendeten Markup-Sprache.
2.2
Bilder sind so zu gestalten, dass die Kombinationen aus
Vordergrund- und Hintergrundfarbe auf einem Schwarz-WeißBildschirm und bei der Betrachtung durch Menschen mit
Farbfehlsichtigkeiten ausreichend kontrastieren.
76
Barrierefreie Informationstechnik-VerordnungAnhang
(BITV)77
77
77
Anforderung
3
Markup-Sprachen (insbesondere HTML) und Stylesheets sind
entsprechend ihrer Spezifikationen und formalen Definitionen
zu verwenden.
Bedingung
3.1
Soweit eine angemessene Markup-Sprache existiert, ist diese
anstelle von Bildern zu verwenden, um Informationen
darzustellen.
3.2
Mittels Markup-Sprachen geschaffene Dokumente sind so zu
erstellen und zu deklarieren, dass sie gegen veröffentlichte
formale Grammatiken validieren.
3.3
Es sind Stylesheets zu verwenden, um die Text- und
Bildgestaltung sowie die Präsentation von mittels MarkupSprachen geschaffener Dokumente zu beeinflussen.
3.4
Es sind relative anstelle von absoluten Einheiten in den
Attributwerten der verwendeten Markup-Sprache und den
Stylesheet-Property-Werten zu verwenden.
3.5
Zur Darstellung der Struktur von mittels Markup-Sprachen
geschaffener Dokumente sind Überschriften-Elemente zu
verwenden.
3.6
Zur Darstellung von Listen und Listenelementen sind die hierfür
vorgesehenen Elemente der verwendeten Markup-Sprache zu
verwenden.
3.7
Zitate sind mittels der hierfür vorgesehenen Elemente der
verwendeten Markup-Sprache zu kennzeichnen.
Anforderung
4
Sprachliche Besonderheiten wie Wechsel der Sprache oder
Abkürzungen sind erkennbar zu machen.
Bedingung
4.1
Wechsel und Änderungen der vorherrschend verwendeten
natürlichen Sprache sind kenntlich zu machen.
Anforderung
5
Tabellen sind mittels der vorgesehenen Elemente der
verwendeten Markup-Sprache zu beschreiben und in der Regel
nur zur Darstellung tabellarischer Daten zu verwenden.
Bedingung
5.1
In Tabellen, die tabellarische Daten darstellen, sind die Zeilenund Spaltenüberschriften mittels der vorgesehenen Elemente der
verwendeten Markup-Sprache zu kennzeichnen.
5.2
Soweit Tabellen, die tabellarische Daten darstellen, zwei oder
mehr Ebenen von Zeilen- und Spaltenüberschriften aufweisen,
sind mittels der vorgesehenen Elemente der verwendeten
Markup-Sprache Datenzellen und Überschriftenzellen einander
zuzuordnen.
Anhang
78
5.3
Tabellen sind nicht für die Text- und Bildgestaltung zu
verwenden, soweit sie nicht auch in linearisierter Form
dargestellt werden können.
5.4
Soweit Tabellen zur Text- und Bildgestaltung genutzt werden,
sind keine der Strukturierung dienenden Elemente der
verwendeten Markup-Sprache zur visuellen Formatierung zu
verwenden.
Anforderung
6
Internetangebote müssen auch dann nutzbar sein, wenn der
verwendete Benutzeragent neuere Technologien nicht
unterstützt oder diese deaktiviert sind.
Bedingung
6.1
Es muss sichergestellt sein, dass mittels Markup-Sprachen
geschaffene Dokumente verwendbar sind, wenn die
zugeordneten Stylesheets deaktiviert sind.
6.2
Es muss sichergestellt sein, dass Äquivalente für dynamischen
Inhalt aktualisiert werden, wenn sich der dynamische Inhalt
ändert.
6.3
Es muss sichergestellt sein, dass mittels Markup-Sprachen
geschaffene Dokumente verwendbar sind, wenn Scripts, Applets
oder andere programmierte Objekte deaktiviert sind.
6.4
Es muss sichergestellt sein, dass die Eingabebehandlung von
Scripts, Applets oder anderen programmierten Objekten vom
Eingabegerät unabhängig ist.
6.5
Dynamische Inhalte müssen zugänglich sein. Insoweit dies nur
mit unverhältnismäßig hohem Aufwand zu realisieren ist, sind
gleichwertige alternative Angebote unter Verzicht auf
dynamische Inhalte bereitzustellen.
Anforderung
7
Zeitgesteuerte Änderungen des Inhalts müssen durch die
Nutzerin, den Nutzer kontrollierbar sein.
Bedingung
7.1
Bildschirmflackern ist zu vermeiden.
7.2
Blinkender Inhalt ist zu vermeiden.
7.3
Bewegung in mittels Markup-Sprachen geschaffener Dokumente
ist entweder zu vermeiden oder es sind Mechanismen
bereitzustellen, die der Nutzerin, dem Nutzer das Einfrieren der
Bewegung oder die Änderung des Inhalts ermöglichen.
7.4
Automatische periodische Aktualisierungen in mittels MarkupSprachen geschaffener Dokumente sind zu vermeiden.
78
Barrierefreie Informationstechnik-VerordnungAnhang
(BITV)79
79
79
7.5
Die Verwendung von Elementen der Markup-Sprache zur
automatischen Weiterleitung ist zu vermeiden. Insofern auf eine
automatische Weiterleitung nicht verzichtet werden kann, ist der
Server entsprechend zu konfigurieren.
Anforderung
8
Die direkte Zugänglichkeit der in Internetangeboten
eingebetteten Benutzerschnittstellen ist sicherzustellen.
Bedingung
8.1
Programmierte Elemente (insbesonder Scripts und Applets) sind
so zu gestalten, dass sie entweder direkt zugänglich oder
kompatibel mit assistiven Technologien sind.
Anforderung
9
Internetangebote sind so zu gestalten, dass Funktionen
unabhängig vom Eingabegerät oder Ausgabegerät nutzbar sind.
Bedingung
9.1
Es sind clientseitige Imagemaps bereitzustellen, es sei denn die
Regionen können mit den verfügbaren geometrischen Formen
nicht definiert werden.
9.2
Jedes über eine eigene Schnittstelle verfügende Element muss in
geräteunabhängiger Weise bedient werden können.
9.3
In Scripts sind logische anstelle von geräteabhängigen EventHandlern zu spezifizieren.
Anforderung
10
Die Verwendbarkeit von nicht mehr dem jeweils aktuellen Stand
der Technik entsprechenden assistiven Technologien und
Browsern ist sicherzustellen, so weit der hiermit verbundene
Aufwand nicht unverhältnismäßig ist.
Bedingung
10.1
Das Erscheinenlassen von Pop-Ups oder anderen Fenstern ist zu
vermeiden. Die Nutzerin, der Nutzer ist über Wechsel der
aktuellen Ansicht zu informieren.
10.2
Bei allen Formular-Kontrollelementen mit implizit zugeordneten
Beschriftungen ist dafür Sorge zu tragen, dass die Beschriftungen
korrekt positioniert sind.
Anforderung
11
Die zur Erstellung des Internetangebots verwendeten
Technologien sollen öffentlich zugänglich und vollständig
dokumentiert sein, wie z.B. die vom World Wide Web
Consortium entwickelten Technologien.
Bedingung
11.1
Es sind öffentlich zugängliche und vollständig dokumentierte
Technologien in ihrer jeweils aktuellen Version zu verwenden,
soweit dies für die Erfüllung der angestrebten Aufgabe
angemessen ist.
Anhang
80
11.2
Die Verwendung von Funktionen, die durch die Herausgabe
neuer Versionen überholt sind, ist zu vermeiden.
11.3
Soweit auch nach bestem Bemühen die Erstellung eines
barrierefreien Internetangebots nicht möglich ist, ist ein
alternatives, barrierefreies Angebot zur Verfügung zu stellen, dass
äquivalente Funktionalitäten und Informationen gleicher
Aktualität enthält, soweit es die technischen Möglichkeiten
zulassen. Bei Verwendung nicht barrierefreier Technologien sind
diese zu ersetzen, sobald aufgrund der technologischen
Entwicklung äquivalente, zugängliche Lösungen verfügbar und
einsetzbar sind.
Anforderung
12
Der Nutzerin, dem Nutzer sind Informationen zum Kontext und
zur Orientierung bereitzustellen.
Bedingung
12.1
Jeder Frame ist mit einem Titel zu versehen, um Navigation und
Identifikation zu ermöglichen.
12.2
Der Zweck von Frames und ihre Beziehung zueinander ist zu
beschreiben, soweit dies nicht aus den verwendeten Titeln
ersichtlich ist.
12.3
Große Informationsblöcke sind mittels Elementen der
verwendeten Markup-Sprache in leichter handhabbare Gruppen
zu unterteilen.
12.4
Beschriftungen sind genau ihren Kontrollelementen zuzuordnen.
Anforderung
13
Navigationsmechanismen sind übersichtlich und schlüssig zu
gestalten.
Bedingung
13.1
Das Ziel jedes Hyperlinks muss auf eindeutige Weise
identifizierbar sein.
13.2
Es sind Metadaten bereitzustellen, um semantische
Informationen zu Internetangeboten hinzuzufügen.
13.3
Es sind Informationen zur allgemeinen Anordnung und
Konzeption eines Internetangebots, z.B. mittels eines
Inhaltsverzeichnisses oder einer Sitemap, bereitzustellen.
13.4
Navigationsmechanismen müssen schlüssig und nachvollziehbar
eingesetzt werden.
Anforderung
14
Das allgemeine Verständnis der angebotenen Inhalte ist durch
angemessene Maßnahmen zu fördern.
Bedingung
14.1
Für jegliche Inhalte ist die klarste und einfachste Sprache zu
verwenden, die angemessen ist.
80
Barrierefreie Informationstechnik-VerordnungAnhang
(BITV)81
81
Priorität II
81
Anforderung
1
Für jeden Audio- oder visuellen Inhalt sind geeignete äquivalente
Inhalte bereitzustellen, die den gleichen Zweck oder die gleiche
Funktion wie der originäre Inhalt erfüllen.
Bedingung
1.5
Für jede aktive Region einer clientseitigen Imagemap sind
redundante Texthyperlinks bereitzustellen.
Anforderung
2
Texte und Graphiken müssen auch dann verständlich sein, wenn
sie ohne Farbe betrachtet werden.
Bedingung
2.3
Texte sind so zu gestalten, dass die Kombinationen aus
Vordergrund- und Hintergrundfarbe auf einem Schwarz-WeißBildschirm und bei der Betrachtung durch Menschen mit
Farbfehlsichtigkeiten ausreichend kontrastieren.
Anforderung
3
Markup-Sprachen (insbesondere HTML) und Stylesheets sind
entsprechend ihrer Spezifikationen und formalen Definitionen
zu verwenden.
Anforderung
4
Sprachliche Besonderheiten wie Wechsel der Sprache oder
Abkürzungen sind erkennbar zu machen.
Bedingung
4.2
Abkürzungen und Akronyme sind an der Stelle ihres ersten
Auftretens im Inhalt zu erläutern und durch die hierfür
vorgesehenen Elemente der verwendeten Markup-Sprache
kenntlich zu machen.
4.3
Die vorherrschend verwendete natürliche Sprache ist durch die
hierfür vorgesehenen Elemente der verwendeten MarkupSprache kenntlich zu machen.
Anforderung
5
Tabellen sind mittels der vorgesehenen Elemente der
verwendeten Markup-Sprache zu beschreiben und in der Regel
nur zur Darstellung tabellarischer Daten zu verwenden.
Bedingung
5.5
Für Tabellen sind unter Verwendung der hierfür vorgesehenen
Elemente der genutzten Markup-Sprache Zusammenfassungen
bereitzustellen.
5.6
Für Überschriftenzellen sind unter Verwendung der hierfür
vorgesehenen Elemente der genutzten Markup-Sprache
Abkürzungen bereitzustellen .
Anhang
82
Anforderung
6
Internetangebote müssen auch dann nutzbar sein, wenn der
verwendete Benutzeragent neuere Technologien nicht
unterstützt oder diese deaktiviert sind.
Anforderung
7
Zeitgesteuerte Änderungen des Inhalts müssen durch die
Nutzerin, den Nutzer kontrollierbar sein.
Anforderung
8
Die direkte Zugänglichkeit der in Internetangeboten
eingebetteten Benutzerschnittstellen ist sicherzustellen.
Anforderung
9
Internetangebote sind so zu gestalten, dass Funktionen
unabhängig vom Eingabegerät oder Ausgabegerät nutzbar sind.
Bedingung
9.4
Es ist eine mit der Tabulatortaste navigierbare, nachvollziehbare
und schlüssige Reihenfolge von Hyperlinks,
Formularkontrollelementen und Objekten festzulegen.
9.5
Es sind Tastaturkurzbefehle für Hyperlinks, die für das
Verständnis des Angebots von entscheidender Bedeutung sind
(einschließlich solcher in clientseitigen Imagemaps),
Formularkontrollelemente und Gruppen von
Formularkontrollelementen bereitzustellen.
Anforderung
10
Die Verwendbarkeit von nicht mehr dem jeweils aktuellen Stand
der Technik entsprechenden assistiven Technologien und
Browsern ist sicherzustellen, so weit der hiermit verbundene
Aufwand nicht unverhältnismäßig ist.
Bedingung
10.3
Für alle Tabellen, die Text in parallelen Spalten mit
Zeilenumbruch enthalten, ist alternativ linearer Text
bereitzustellen.
10.4
Leere Kontrollelemente in Eingabefeldern und Textbereichen
sind mit Platzhalterzeichen zu versehen.
10.5
Nebeneinanderliegende Hyperlinks sind durch von Leerzeichen
umgebene, druckbare Zeichen zu trennen.
Anforderung
11
Die zur Erstellung des Internetangebots verwendeten
Technologien sollen öffentlich zugänglich und vollständig
dokumentiert sein, wie z.B. die vom World Wide Web
Consortium entwickelten Technologien.
Bedingung
11.4
Der Nutzerin, dem Nutzer sind Informationen bereitzustellen,
die es ihnen erlauben, Dokumente entsprechend ihren Vorgaben
(z.B. Sprache) zu erhalten.
82
Barrierefreie Informationstechnik-VerordnungAnhang
(BITV)83
83
83
Anforderung
12
Der Nutzerin, dem Nutzer sind Informationen zum Kontext und
zur Orientierung bereitzustellen.
Anforderung
13
Navigationsmechanismen sind übersichtlich und schlüssig zu
gestalten.
Bedingung
13.5
Es sind Navigationsleisten bereitzustellen, um den verwendeten
Navigationsmechanismus hervorzuheben und einen Zugriff
darauf zu ermöglichen.
13.6
Inhaltlich verwandte oder zusammenhängende Hyperlinks sind
zu gruppieren. Die Gruppen sind eindeutig zu benennen und
müssen einen Mechanismus enthalten, der das Umgehen der
Gruppe ermöglicht.
13.7
Soweit Suchfunktionen angeboten werden, sind der Nutzerin,
dem Nutzer verschiedene Arten der Suche bereitzustellen.
13.8
Es sind aussagekräftige Informationen am Anfang von inhaltlich
zusammenhängenden Informationsblöcken (z.B. Absätzen,
Listen) bereitzustellen, die eine Differenzierung ermöglichen.
13.9
Soweit inhaltlich zusammenhängende Dokumente getrennt
angeboten werden, sind Zusammenstellungen dieser Dokumente
bereitzustellen.
13.10
Es sind Mechanismen zum Umgehen von ASCII-Zeichnungen
bereitzustellen.
Anforderung
14
Das allgemeine Verständnis der angebotenen Inhalte ist durch
angemessene Maßnahmen zu fördern.
Bedingung
14.2
Text ist mit graphischen oder Audio-Präsentationen zu ergänzen,
sofern dies das Verständnis der angebotenen Information fördert.
14.3
Der gewählte Präsentationsstil ist durchgängig beizubehalten.