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.