Skinning: Painters, Button Shaper und Watermarking
Betrachtet man sich eine der zuvor vorgestellten LookAndFeel-Klassen im Sourcecode, wird man feststellen, dass sie lediglich die abstrakte Oberklasse org.jvnet.substance.SubstanceLookAndFeel erweitern und dieser im Konstruktor eine Implementierung der (ebenfalls abstrakten) Klasse org.jvnet.substance.api.SubstanceSkin übergeben. So wird beispielsweise in SubstanceOfficeBlue2007LookAndFeel eine Instanz von OfficeBlue2007Skin verwendet. Die eigentliche LAF-Logik befindet sich also in der Skin-Implementierung. Ein Skin besteht intern aus mehreren Farbschemata (intern als Implementierung des Interfaces SubstanceColorScheme realisiert) und einer Reihe sogenannter Painter (Gradient-, Decorator-, Highlight- und Border-Painter), welche das Zeichnen einzelner Bereiche der Oberfläche übernehmen. So kommt zum Beispiel der Gradient Painter immer dann zum Einsatz, wenn es darum geht, den inneren Bereich diverser Swing-Komponenten (Buttons, Checkboxes, Tables und so weiter) zu zeichnen. Decorator Painter sind für das Zeichnen spezieller Bereiche (Decoration Areas) innerhalb von Frames und Dialogen zuständig. Decoration Areas, um die sich also ein Decorator Painter kümmert, sind Bereiche wie die Titelzeile eines Fensters, die Menüzeile, den Toolbar-Bereich und noch einige mehr. Wichtig in einem Skin sind auch die Border Painter, denn diese stellen das einheitliche Zeichnen der äußeren Konturen diverser Swing-Komponenten wie Buttons, Check Boxes, Radio Buttons, Progress Bars, Tabs und Scrollbars sicher.Mit Hilfe von Button Shapern kann man in einem Skin optional festlegen, ob ein Button eckig oder abgerundet gezeichnet werden soll. In der Default-Einstellung verwendet Substance die Klasse ClassicButtonShaper und zeichnet eckige Kanten. Will man dagegen, dass abgerundete Buttons gezeichnet werden, verwendet man stattdessen eine Instanz von StandardButtonShaper. In der Klasse MistAquaSkin, die den von Mac OS X bekannten Aqua-Look imitiert, wird beispielsweise der StandardButtonShaper zum Zeichnen der abgerundeten Buttons verwendet.
Unter "Watermarking" versteht man bei Substance die Möglichkeit, Hintergründe in einer Swing-Anwendung mit Mustern oder Bildern zu versehen. Bereits mitgeliefert wird mit dem SubstanceStripeWatermark ein Wasserzeichen, welches horizontale Linien in den Hintergrund zeichnet. Mit SubstancePlanktonWatermark ist eine weitere beispielhafte Implementierung des SubstanceWatermark-Interfaces enthalten, das ein planktonartiges Muster in den Hintergrund zaubert. Von besonderem Interesse dürfte in diesem Zusammenhang auch die Klasse SubstanceImageWatermark sein, mit der man in der Lage ist, Hintergrundbilder in eine Anwendung zu setzen. In Abbildung 2 sieht man das auf Swing basierende, bekannte Werkzeug Apache TCPMon mit einem modifizierten Office2007BlueSkin und dem JAXenter-Logo als Hintergrundbild. Die Implementierung des modifizierten Skins in Listing 3 zeigt, wie einfach es geht. Die Klasse MySkin erbt von Office2007BlueSkin, stellt den Button Shaper um, damit runde Buttons gezeichnet werden, und setzt schließlich das Wasserzeichen mit einer zuvor konfigurierten Instanz von SubstanceImageWatermark. Dabei nimmt SubstanceImageWatermark über seinen Konstruktur den Pfad zum Bild entgegen und bietet mit Hilfe der Methode setKind() eine Möglichkeit, die Anordnung des Bildes zu steuern. Die einzelnen Einstellmöglichkeiten finden sich im ImageWatermarkKind-Enum. Die Deckkraft des Hintergrundbildes stellt man über die Methode setOpacity() ein. Der Wert steht in der Defaulteinstellung auf 0.2f.
Im Zusammenhang mit Watermarks ist es außerdem wichtig zu wissen, dass in bestimmten Swing-Komponenten (Textfelder, Table, Tree) das Watermarking per Default deaktiviert ist. Will man beispielsweise das Wasserzeichen auch auf einem Textfeld darstellen, so muss dieses über eine entsprechende Client Property explizit eingeschaltet werden (siehe in Abbildung 2 das Textfeld "Target Port"). Gesetzt wird die Client Property wie folgt:
textTcpPort.setClientProperty(SubstanceLookAndFeel.WATERMARK_VISIBLE, Boolean.true)
Auch ist es derzeit nicht möglich, mehr als ein Watermark in einer Maske zu verwenden.
Im separat erhältlichen Substance Extras Pack [5] finden sich noch viele weitere Farbschemata, Skins und Watermark-Implementierungen. Auch sind hier einige zusätzliche Button Shaper enthalten, die das Erstellen von teilweise sehr ausgefallenen Buttons ermöglichen (zum Beispiel einen Button in Form eines Dinosauriers) und demonstrieren, wie vielfältig die Möglichkeiten mit Substance sind.
Listing 3:
package net.teufel.substance;import org.jvnet.substance.api.SubstanceConstants.ImageWatermarkKind;import org.jvnet.substance.shaper.StandardButtonShaper;import org.jvnet.substance.skin.OfficeBlue2007Skin;import org.jvnet.substance.watermark.SubstanceImageWatermark;public class MySkin extends OfficeBlue2007Skin {public MySkin() {super();SubstanceImageWatermark watermark= new SubstanceImageWatermark("c:\\jaxenter_watermark.jpg");watermark.setKind(ImageWatermarkKind.APP_TILE);watermark.setOpacity(0.4f);this.buttonShaper = new StandardButtonShaper();this.watermark = watermark;}}
Skin über API-Aufruf aktivieren
Um den modifizierten Skin nun zu aktivieren, könnte man eine Klasse MyLookAndFeel schreiben, die von BasicLookAndFeel erbt und MySkin verwendet (Listing 4). Diese neue LookAndFeel-Klasse könnte man dann auf einem der bereits beschriebenen Wege aktivieren.
Listing 4:
package net.teufel.substance;import org.jvnet.substance.SubstanceLookAndFeel;public class MyLookAndFeel extends SubstanceLookAndFeel {public MyLookAndFeel() {super(new MySkin());}}
Es geht jedoch noch einfacher. Mit API-Aufrufen ist man nämlich in der Lage, einen Skin direkt anzusteuern und hat so die Möglichkeit, gänzlich auf LAF-Klassen zu verzichten. Die API-Aufrufe werden von der Klasse SubstanceLookAndFeel gekapselt. Ruft man auf dieser Klasse die statische Methode setSkin() auf und übergibt entweder eine Instanz des zu aktivierenden Skins bzw. den Klassennamen des Skins (als String), dann erzeugt Substance im Hintergrund ein entsprechendes LAF und startet dieses über den UIManager. Es ist wichtig zu verstehen, dass dieses Verfahren auch dann funktioniert, wenn man vorher keine der LookAndFeel-Klassen aus Substance bemüht hat. Das Entwicklerteam von Substance empfiehlt, diese Methode zum Aktivieren von LAFs bzw. Skins immer den Vorrang zu geben, weil setSkin() insbesondere auch dafür sorgt, dass beim Umstellen alle Fenster auf höchster Ebene neu gezeichnet werden (mittels SwingUtitlities.updateComponentTreeUI).














