|
Let's swing again!
Neue JFC Features in der
Java 2 Standard Edition, Version 1.3
Kai Tödter
Die Java Foundation Classes (JFC) beziehungsweise das darin enthaltene Swing-Set
sind ein fester Bestandteil der aktuellen Java 2 Standard Edition, Version 1.3.
Nach einigen Bug-Fix-Releases sind die meisten der Kinderkrankheiten geheilt.
Im neuen "Kestrel"-Release kommen eine Fülle von neuen Features und einige
kleinere Änderungen hinzu, die in diesem Artikel ausführlich vorgestellt
werden.
Einführung
Wer die Entwicklung der JFC bzw. Swing verfolgt hat, wird festgestellt haben,
dass die angebotenen Features immer ausgereifter wurden und inzwischen kaum
noch Wünsche offen sind. Sicherlich können die JFC nicht alle Anforderungen
abdecken, die meisten professionellen Software-Pakete werden aber mit der Funktionalität
des aktuellen Standes gut leben können. Was die Performance und die Effizienz
der Komponenten angeht, kann allerdings noch einiges getan werden. Die Version
1.3 der J2SE geht einen großen Schritt in diese Richtung und kann einiges
halten, was auf der JavaOne '99 [JavaOne] versprochen wurde. Dieser Artikel
geht ausführlich auf die wichtigsten neuen Features ein.
JTree
Im JTree gibt es einigen Neuerungen, z.B. einige neue Properties, für die
es die üblichen Getter- und Setter-Methoden gibt. Man kann jetzt mit der
Methode setToggleClickCount( int ) die Anzahl der Maus-Klicks festlegen, die
zum Expandieren/Kollabieren eines Baumknotens benötigt werden. Mit den
Methoden setLeadSelectionPath( TreePath ) und setAnchorSelectionPath( Treepath
) hat man jetzt mehr Kontrolle über Selektionen innerhalb von Bäumen.
Der Anchor Path ist derjenige TreePath, mit dem eine Selektion beginnt und er
wird als Bezugspunkt für alle Erweiterungen einer Selektion genommen. Der
Lead Path wiederum ist der Path, welcher zuletzt zu einer Selektion hinzugefügt
wurde. In Abbildung 1 ist z.B. "John Coltrane" der Anchor Path (mit erstem Mausklick
selektiert) und "I Thought About You" der Lead Path (mit zweitem Mausklick +
Shift selektiert). Mit der Methode setExpandsSelectedPaths( boolean ) ist es
nun möglich, festzulegen, ob ein Teilbaum expandiert werden soll, wenn
er programmtechnisch selektiert wird. Defaultmässig ist diese Property
auf true gesetzt. Weiterhin kann man mit der Methode removeDescendantTogglePath(
Enumeration ) festlegen, ob sich die aktuelle Selektion ändern soll, wenn
ein Baumknoten expandiert oder kollabiert wird. Eine kleine aber feine Änderung
gibt es beim DefaultTreeCellRenderer: Das Feld hasFocus ist jetzt protected.
Das macht es für eine Unterklasse, die nur die paint()-Methode überladen
will, möglich, auf alle releventen Properties direkt zuzugreifen. Früher
war das Feld privat und man musste den umständlichen Weg über getTreeCellRendererComponent
gehen. Die letzt kleine Änderung betrifft die Methode insureUniqueness()
im DefaultTreeSelektionModel. Diese ist jetzt obsolet, bleibt aber wegen Rückwärtskompatibilität
im API enthalten.
Abbildung 1: Baum mit Selektion
JTable
Bei den Tabellen gibt es einige Performance- und Layoutverbesserungen. Die Header
Renderer der Spalten sind nun Teil des Tabellen Headers (Klasse JTableHeader).
Da dadurch ähnliche Header Renderer pro Tabelle verwaltet werden, wird
der Footprint kleiner und die Performance deutlich verbessert. Alle Methoden
von JTableHeader mit "UpdateTableInRealTime" im Namen sind nun deprecated und
man kann jetzt einen DefaultRenderer des Typs TableCellRenderer setzen. Eine
weitere lang ersehnte Neuerung der JFC-Tabellen, nämlich dass verschieden
Zeilen unterschiedliche Höhen haben können, ist endlich realisiert
worden (Siehe Abbildung 2). Dafür steht die Methode setRowHeight( int row,
int height ) zur Verfügung. Um die Performance von Tabellen-Spalten, welche
Zeilen mit variabler Höhe beinhalten, in zukünftigen Versionen zu
verbessern, könnte die neue Utility-Klasse SizeSequence benutzt werden.
Das Erstellen von nichtstandard Editor-Komponenten wird durch die neue Klasse
AbstractCellEditor sehr erleichtert. Die existierende Klasse DefaultCellEditor
ist nun von AbstractCellEditor abgeleitet.
Abbildung 2: Tabelle mit unterschiedlichen Zeilenhöhen
JSpiltPane
Wenn sich die Größe einer JSplitPane ändert, wird nur die Größe
der rechten/unteren Komponente vergrößert. Das gibt den Effekt, als
ob die linke/obere Komponente unverändert bleibt. Man kann allerdings über
die Methode setResizeWeight( double ) eine Gewichtung einstellen, wie die Größen
der Komponenten nach der Größenänderung der JSplitPane aufgeteilt
werden sollen. Um einige Layout-Probleme mit der Umrandung der JSpliPane-Komponenten
und des Deviders zu umgehen, kann man jetzt die Umrandung der Klasse BasicSplitPaneDevider
mit der Methode setBorder( Border ) explizit setzen.
JFileChooser
Der JFileChooser besitzt eine neue Property isAcceptAllFileFilter, mit der man
den AcceptAll File-Filter (*.*) kontrollieren kann. Dieser File Filter ist jetzt
defaultmässig in der Filter-Combobox eingestellt. Eine weitere Neuerung
ist, dass man jetzt die OK- und Cancel-Buttons entfernen kann. Das macht immer
dann sehr viel Sinn, wenn der File Chooser in eigene Komponenten integrieren
werden soll. Mit der Methode setControlButtonsAreShown( boolean ) kann die Sichtbarkeit
dieser Buttons kontrolliert werden.
Buttons
Eine JCheckBox wird in Tabellen oder Listen oft als CellRenderer benutzt. Sowie
das Java- wie auch das Windows-Look&Feel spezifizieren, dass CheckBoxen
mit "2D Flat Border" und nicht mit "3D Border" gerendert werden sollen, wenn
sie als CellRenderer benutzt werden. Mit der Methode setBorderPaintedFlat( boolean
) von JCheckBox kann man dieses Aussehen jetzt leicht einstellen. Abbildung
3 zeigt normale und flache CheckBoxen in den 3 gängigen Look & Feels.
Während man beim Windows und beim Motif Look & Feel ein deutlich flacheres
Aussehen feststellt, ändert sich beim Java Look & Feel nichts. Das
mag entweder daran liegen, dass das Java Look & Feel ja sowieso schon recht
"Flat" ist. Vielleicht liegt es aber auch nur an meiner JDK 1.3 Beta Version.
Mal sehen, wie es im Final Release ausschaut. Um einfacher an die Information
über Gruppenzugehörigkeit zu gelangen, besitzt das DefaultButtonModel
jetzt die Methode getGroup().

Abbildung 3: "Flat" CheckBoxen
JTabbedPane
Die JTabbedPane hat eine neue (indexed) Property toolTipTextAt. Mit der Methode
setToolTipTextAt( int index, String toolTipText ) kann man jetzt den Tooltiptext
für jedes "Tab" der JTabbedPane setzen.
Text
Im Text-Paket gibt es einige kleinere und größere Änderungen.
Z.B. wurde ein Schreibfehler korrigiert: HTMLEditorKit.insertAtBoundry wird
zu HTMLEditorKit.insertAtBoundary. Um die Behandlung von DokumentEvents zu vereinfachen,
ist die Default-Funktionalität von CompositeView in die Klasse View gewandert.
Unterklassenbildung wird dadurch sehr vereinfacht, sowie das Management der
DokumentEvents mit den folgenden protected Methoden:
- boolean updateChildren( DokumentEvent.ElementChange, DokumentEvent e, ViewFactory
vf )
- void forwardUpdate( DokumentEvent.ElementChange, DokumentEvent e, Shape
a, ViewFactory vf )
- void forwardUpdateToView( View v, DokumentEvent e, Shape a, ViewFactory
vf )
- void updateLayout( DokumentEvent.ElementChange, DokumentEvent e, Shape
a )
Weiterhin sind auch die public Methoden zur Verwaltung der Kinder eines Views
von CompositeView zu View gewandert:
- void removeAll()
- void remove( int i)
- void insert( int offset, View v )
- void replace( int offset, int length, View[] views )
Die Klasse BoxView hat ein Argument axis, an welches Unterklassen bisher nicht
herangekommen sind. Dieses Argument hat eventuell auch Auswirkungen auf das Layout.
Deswegen sind die folgenden public Methoden zur Klasse javax.swing.text.BoxView
hinzugekommen:
- int getAxis()
- void set Axis( int axis )
- void layout Changed( int axis )
Die Klasse ParagraphView ist nun eine Unterklasse von FlowView, wodurch Dinge
wie das textuelle Umfließen einer Form und andere Arten von "Flows"
vereinfacht werden. Im ParagraphView werden jetzt die Paragraph-spezifischen
Flow-Strategien, wie Einrückung der ersten Zeile, behandelt. Die Klasse
javax.swing.text.View hat deswegen auch die neue Methode getViewIndex( int pos,
Position.Bias b ). Im HTML-Bereich hat sich einiges getan. Z.B. gab es in der
Klasse javax.swing.text.html.FormView zwei public static final Strings SUBMIT
und RESET. Diese sind jetzt deprecated und die entsprechenden Werte können
über die UIManager-Properties FormView.submitButtonText und FormView.resetButtonText
geändert werden. Bei der Serialisierung im HTML-Paket sind auch kleinere
Erweiterungen vorgenommen worden. Die Klasse javax.swing.text.html.parser.ParserDelegator
implementiert jetzt das Serializable-Interface. Die Klasse AbstractWriter wird
als Basis-Klasse benutzt, wenn man ein Text-Dokument in spezifischer Form ausgeben
will. Z.B. ist HTMLWriter von AbstractWriter abgeleitet, um HTML auszugeben.
Das Problem bestand darin, dass viele Methoden private waren, wodurch die Unterklassenbildung
ungemein erschwert wurde. Die folgenden API-Änderungen in AbstractWriter
machen die Unterklassenbildung und somit auch den HTMLWriter deutlich einfacher:
- public int getStartOffset()
- public int getEndOffset()
- protected Writer getWriter()
- protected int getLineLength()
- protected void setCurrentLineLength( int length )
- protected int getCurrentLineLength()
- protected boolean isLineEmpty()
- protected void setCanWrapLines( boolean newValue )
- protected boolean getCanWrapLines()
- public void setLineSeparator( String value )
- public String getLineSeparator()
- protected int getIndentLevel()
- protected void writeLineSeparator() throws IOException
- protected void write( char[] chars, int startIndex, int length ) throws
IOException
- protected void output( char[] content, int start, int length ) throws IOException
Das HTML-Paket unterstützt Cascading Style Sheets (CSS 1). Die folgenden
Methoden erlauben dem Entwickler, verschiedene Style Sheets miteinander zu verknüpfen:
- public synchronized void addStyleSheet( StyleSheet ss )
- public synchronized void removeStyleSheet( StyleSheet ss )
- public StyleSheet[] getStyleSheets()
Es gibt noch einige weitere Änderungen, wie die neuen Klassen ZoneView
und AsyncBoxView. Detaillierte Informationen findet man in der J2SE-Dokumentation
unter jdk1.3\docs\guide\swing\TextChanges.html.
Scrolling
Interaktives Scrollen ist vielleicht der wichtigste Faktor im subjektiven Eindruck
der gesamten Swing-Performance. In der neuen Version 1.3 wird in der Klasse
JViewPort eine neue Default-Optimierung, genannt BLIT_SCROLL_MODE, eingeführt,
welche Scroll-Bereiche per Bit-Blit kopiert. Der protected Member backingStore
ist nun deprecated.

Abbildung 4: SwingSet2 Demo: Scrollen mit BLIT_SCROLL_MODE
Drucken
In früheren Versionen wurde die print-Methode von JComponent nicht
überladen. Drucken war also nichts anderes als eine Display-Ausgabe in
einen Double-Buffer. Jetzt gibt es extra Methoden für das Drucken von Komponenten:
- protected void printComponent( Graphics g )
- protected void printChildren( Graphics g )
- protected void printBorder( Graphics g )
Eingabe-Verifikation
Es gibt einen neue Klasse javax.swing.InputVerifier, die man dazu verwenden
kann, den Inhalt einer Komponente, z.B. eines Textfeldes, zu validieren. Jetzt
kann einfach ein eigener InputVerifier mit der Methode setInputVerifier() (von
JComponent) gesetzt werden. Bevor der Focus auf eine andere Komponente gesetzt
werden kann, wird die Methode shouldYieldFocus() des InputVerifiers aufgerufen.
Der Focus wird nur transferiert, wenn diese Methode true zurückliefert.
Internationalisierung
Um die nächste visuell repräsentierte Modell-Position
herauszubekommen, auf welcher ein Caret plaziert werden kann, bietet die Klasse
GlyphView.GlyphPainter jetzt die Methode
- public int getNextVisualPositionFrom( GlyphView v, int pos, Position.Bias
b, Shape a, int direction, Position.Bias[] biasRet ) throws BadLocationException
Borders
Es war früher nicht möglich, mit Hilfe der BorderFactory eine "raised
etched Border" zu erzeugen. Um konsistent zu bleiben, wurden deshalb die
folgenden Methoden zur BoderFactory hinzugefügt:
- public static Border createEtchedBorder( int type )
- public static Border createEtchedBorder( int type, Color highlight, Color
shadow )
Um LineBoders mit abgerundeten Ecken zu erzeugen, gibt es jetzt einen neuen
Konstruktor:
- public LineBorder( Color color, int thickness, boolean roundedCorners )
Um eine Inkonsistenz zu beseitigen, ist die Klasse TableHeaderBorder vom Paket
plaf.metal.MetalUtils in das Paket javax.swing.plaf.metal.MetalBorders gewandert.
Actions
Actions werden in Swing benutzt, um an einer Stelle Funktionalität
zu implementieren, die von verschiedenen Komponenten, wie z.B. Menüs, Toolbars
usw. benutzt werden kann. Ändert die Action ihren Status, spiegelt sich
das auch meist in der visuellen Repräsentation der entsprechenden Komponente
wieder, die die Action benutzt: Menü-Items und Toolbar-Icons werden z.B.
disabled dargestellt, wenn die Action disabled wird. Um das Erzeugen von Komponenten,
welche auf Actions basieren, zu erleichtern, wurden die drei Container JToolBar,
JMenu und JPopupMenu mit den Methoden add( Action a ) angereichert, die jeweils
das entsprechende Control (JButton, JMenuItem, JMenuItem) aus der Action erzeugen
und zurückliefern. Daraus entstehen eine Reihe von Problemen, z.B. die
Unverträglichkeit mit GUI-Buildern, da die Container.add()-Methode überladen
wird und die zu enge Bindung der Konfigurationsmöglichekeiten an die Container-Klasse.
Deswegen gibt es ein neues API in der Klasse AbstractButton, welche wiederum
die Basisklasse für JButton, JMenuItem etc. ist:
- public void setAction( Action a )
- public Action getAction()
- protected void configurePropertiesFromAction( Action a )
- protected PropertyChangeListener createActionPropertyChangeListener()
Weiterhin haben folgende Klassen jetzt einen Konstruktor, der eine Action als
Parameter verlangt:
- JButton
- JCheckBox
- JRadioButton
- JToggleButton
- JMenuItem
- JMenu
- JCheckBoxMenuItem
- JRadioButtonMenuItem
Die Containerklassen JToolBar, JPopupMenu und JMenu haben jeweils eine neue
Methode:
- JToolBar: protected JButton createActionComponent( Action a )
- JPopupMenu: protected JMenuItem createActionComponent( Action a )
- JMenu: protected JMenuItem createActionComponent( Action a )
Zu guter letzt gibt es noch zwei neue Konstanten
- public static final String ACCELERATOR_KEY
- public static final String MNEMONIC_KEY
und eine neue Methode public Object[] getKeys(), um dem Entwickler die Möglichkeit
zu geben, herauszufinden, welchen Tasten für eine Action gesetzt worden
sind.
JToolbar
Um ungedockten Toolbars jetzt einen Titel zu geben, hat die Klasse JToolBar
zwei neue Konstruktoren:
- public JToolBar( String name )
- public JToolBar( String name , int orientation )

Abbildung 5: Ungedockte ToolBar mit Titel
Menüs
Bei den Menüs hat sich recht wenig geändert. Die Methode getPopupMenuOrigin()
von JMenu und die Methoden installComponents( JMenuItem menuItem ) und uninstallComponents(
JMenuItem menuItem ) sind jetzt protected. Weiterhin gibt es jetzt in den Klassen
JPopupMenu und javax.swing.plaf.PopupMenuUI die public Methode isPopupTrigger(
MouseEvent e ), die einen boolean zurückliefert.
JFrame
Endlich gibt es die Konstante EXIT_ON_CLOSE, die man in der Methode setDefaultCloseOperation(
int operation ) verwenden kann.
JInternalFrame
Die Klasse JInternalFrame hat einige Erweiterungen bekommen. Hinzugekommen ist
die Methode setLayer( int ), mit Hilfe derer man die Ebene setzten kann, in
der sich der interne Frame befindet. Intern wurde ein Memory Leak gefixed: Alle Listener der BasicInternalFrameTitlePane
werden jetzt entfernt, wenn sie nicht mehr gebraucht werden. Weiterhin hat sich
das Verhalten der Default Schließ-Operation geändert, nämlich
von HIDE_ON_CLOSE auf DISPOSE_ON_CLOSE. Auch die Konstante EXIT_ON_CLOSE wurde
definiert. Die Methode doDefaultCloseAction() ist jetzt public. Um das Rechteck
zu erhalten, dass das interne Frame in seiner normalen Größe einnimmt,
gibt es jetzt die Methode getNormalBounds(). Diese Größe kann man
natürlich mit setNormalBounds() auch setzen. Zwei weitere Methoden von
JInternal Frame sind getFocusOwner(), welches im aktiven Zustand des internen
Frames dasjenige Kind zurückliefert, welches gerade den Focus besitzt.
Die Methode restoreSubcomponentFocus() wird vom UI benutzt, um bei Aktivierung
des internen Frames den Focus vor der Deaktivierung wieder herzustellen. Zu
guter letzt hat der InternalFrameEvent die Methode getInternalFrame() bekommen.

Abbildung 6: Interne Frames im neuen SwingSet2 Demo
Listeners
Um endlich herausfinden zu können, welche Listener von verschiedenen
Klassen benutzt werden, wurde die Methode getListeners() zu folgenden Klassen
hinzugefügt:
- javax.swing.Timer javax.swing.AbstractListModel
- javax.swing.DefaultBoundedRangeModel
- javax.swing.DefaultButtonModel
- javax.swing.DefaultListSelectionModel
- javax.swing.DefaultSingleSelectionModel
- javax.swing.table.AbstractTableModel
- javax.swing.table.DefaultTableColumnModel
- javax.swing.tree.DefaultTreeModel
- javax.swing.tree.DefaultTreeSelectionModel
- javax.swing.text.AbstractDocument
- javax.swing.text.DefaultCaret
- javax.swing.event.EventListenerList
Performance
Einer der Hautgründe für Swings langsame Startup-Performance war,
dass der UIManager für jede Komponente, die ein UI-Delegate benutzt, das entsprechende Look & Feel lädt. Dafür mussten die Default-Tabellen
geladen werden, welche die UI-Defaults für die entsprechende Komponente enthielten.
Er wurden auch einige falsche Annahmen über die Kosten von Objekt-Instantiierungen
und für das Laden von Klassen getroffen. In einem neuen Verfahren wird jetzt
die Klasse UIDefaultProxy benutzt, was z.B. in einem "Hello Word"-Beispiel
dazu führt, dass das Laden von ca. 90 Klassen vermieden wird, nachdem nur
eine einzige Klasse geladen wurde! Die Scrolling-Performance im Allgemeinen wurde
auch wesentlich verbessert (Siehe auch Abschnitt Scrolling). Das Verschieben von
internen Frames hat sich durch den neuen Drag-Mode LIVE_DRAG_MODE auch sichtbar
verschnellert. Der neue Drag-Mode wird folgendermaßen gesetzt: desktop.setDragMode(
JDesktopPane.LIVE_DRAG_MODE ). Insgesamt scheint es so, als ob sich die gesamte
Swing-Performance verbessert hat.
Fazit
Im neuen Kestrel-Realease der Java 2 Standard Edition, Version 1.3, hat
sich einiges getan. Endlich sind viele der Kinderkrankheiten geheilt, die meisten
der Anforderungen an ein modernes GUI erfüllt und auch die Performance
macht deutliche Fortschritte. Ich bin sehr gespannt, in welche weiteren Verbesserungen
die den nächsten Versionen der Java Foundation Classes zu finden sein werden.
Literatur und Links
[Swing] Swing, http://java.sun.com/products/jdk/awt/swing
[Tö98] K. Tödter, Professionelle grafische Benutzungsoberflächen
mit den Java Foundation Classes,
in: JavaSpektrum, Januar/Februar 1998
[JavaOne] M. C. Franz & K. Tödter, Rückblick auf die JavaOne,
in: JavaSpektrum, September/Oktober 1999
|