Swing-Tips #

Bei der Arbeit mit Swing stößt man fast ständig an Besonderheiten und Eingenschaften, deren genaue Funktion man sich erst erarbeiten muss. Um solche Dinge für Andere Menschen und für mich zu erhalten und um vielleicht selber von den Tipps anderer Javaner zu profitieren, habe ich diese Seite begonnen, um sie hier zu erklären.

Application Framework #

Wer stärker bemuttert werden will als Swing alleine das vermag, kann mit einem Framework oder einer sog. Plattform viele Basis-Aufgaben einer Applikation wie z.B. die Erstellung von Fenstern, Menüs, aber auch nicht-Swing-Problemen, an eine Bibliothek abgeben. Hierzu gibt es den Artikel JavaApplicationFramework.

JTable wie ein Spreadsheet benutzen #

Der eigentliche Sinn einer editierbaren JTable ist meiner Meinung nach, dem Benutzer halbwegs das Gefühl zu geben, das er beim editieren eines Spreadsheets (also in MS Excel oder OpenOffice.org Calc) hat. Leider ist das "Feeling" etwas anders, was meiner Meinung nach unnötig die Benutzer verstört. Ideen hierzu finden sich auf der Seite JTableAlsSpreadsheet.

Zuordnung von Tastenfunktionen #

In einer Tabelle hatte ich damit gekämpft, daß man mit TAB nicht in die nächste Tabellenzelle kam. Der Fehler lag ganz woanders, aber ich habe etwas über die Zuordnung von Tasten zu Aktionen gelernt. Wer also wissen will, wie besondere Events wie z.B. Cursortasten, TAB, oder auch Mausaktionen zugeordnet werden, sollte einfach im ganz normalen JavaTutorial)fündig nachlesen. Dort ist sehr gut erklärt, was es mit InputMap und ActionMap auf sich hat.

Actions #

Das Action-Interface ist eigentlich eine recht gute Grundidee. Eine von einem Programm ausführbare Funktion wird abstrahiert als Action verpackt und kann dann z.B. einem Button und/oder einem Menü zugeordnet werden. Leider reicht die Implementation der Standard-Klassen vorne und hinten nicht aus, um komfortabel zu arbeiten. Auf der Seite JavaActions werden einige Aspekte von Actions diskutiert.

Zelle in einer Tabelle aktiveren #

Wer durch ein Programm (also nicht durch anklicken) eine Zelle aktivieren will, muss sich genau überlegen, was er denn nun will. Es gibt die Selektion der Tabelle und das Feld, auf dem der TAB-Focus liegt und dann kann es noch einen geöffneten Editor (mit und ohne Tastatur-Focus) geben. All dies sind komplett unterschiedliche Dinge. Das Ergebnis, das man am ehesten erwartet (das dem Klick mit der Maus am ehesten entspricht), gibt meines Erachtens nach folgender Code:

  table.requestFocusInWindow();
  table.editCellAt(2, 2);
  table.changeSelection(2, 2, false, false);
mit den folgenden Befehlen wird die Selektion verändert:
  table.getSelectionModel().setLeadSelectionIndex(2);
  table.getColumnModel().getSelectionModel().setLeadSelectionIndex(2);
  table.getSelectionModel().setAnchorSelectionIndex(2);
  table.getColumnModel().getSelectionModel().setAnchorSelectionIndex(2);
evtl. ist das hier dann auch noch hilfreich:
  table.scrollRectToVisible(table.getCellRect(2, 2, true));

richtig grosse Listen anzeigen #

Das ListModel kann prinzipiell auch mit richtig grossen Listen umgehen. Dabei gibt es allerdings ein paar Tricks. Zum einen sollte man mit setPrototypeCellValue() einen Wert mit maximaler Ausgabegröße setzen, um zu verhindern, daß beim Erstellen des Widgets alle Elemente testweise gezeichnet werden, um das Größte zu finden.

Ein paar zusätzliche Schwierigkeiten ergeben sich, wenn man eine richtig grosse Liste in einer ComboBox verwenden will, insbesondere wenn die Zugriff auf die Elemente langsam ist (z.B. aus einer Datenbank). Hier werden an mehreren Stellen Schleifen verwendet, um relativ simple Aufgaben zu lösen. Um das zu umgehen, müssen ein paar Methoden überladen und ein bisschen gehackt werden. Das würde allerdings hier den Rahmen sprengen. Wer eine Datenbank-taugliche Lösung sucht, kann mich fragen, ich habe hierfür eine abgeleitete Variante der ComboBox, die ich mit tausenden von Einträgen benutze. -- ThomasBayen

JComboBox mit Autocompletion #

Für richtig grosse Listen ist es dann auch sinnvoll, diese ordentlich durchsuchen zu können. Dazu bedarf es einer Autovervollständigung (wie z.B. im Firefox URL-Feld). Ich habe etwas gegoogelt und Ansätze hierzu unter folgenden Links gefunden:

Swinglabs enthält Projekte von Sun, die potentiell in das nächste Java aufgenommen werden könnten. Das bedeutet, die dortige Lösung ist ganz gut dokumentiert, in eine ordentliche API zerlegt und integriert sich sauber mit Swing. Das ist im Prinzip die sauberste Lösung, die ich bisher gefunden habe. Leider unterstützt sie kein Filtern der Liste.

Der obengenannte Artikel von Thomas Bierhance scheint sowas wie die Mutter aller Autocompletion-Lösungen zu sein. Alle scheinen mehr oder weniger bei ihm abgeschrieben zu haben. Dieser Artikel ist sehr gut geschrieben und erklärt Schritt für Schritt, wie man sowas macht. Schön zu lesen, wenn man einfach mal einem, der sich in Swing auskennt, über die Schulter schauen möchte. Im übrigen hat er sich am Ende des Artikels auch meine Gedanken über grosse Listen und die Performance-Probleme gemacht. Er hast den gleichen Bug gefunden wie ich und ihn auch per Reflection gelöst. Was mich dabei auf die Palme bringt, ist, daß dieser Artikel seit 2004 im Netz steht, offensichtlich sogar irgendjemand aus Suns SwingX-Team ihn wahrgenommen und daran herumgebastelt hat und Sun/Oracle sowas bis heute nicht ins JDK bekommen hat. Also durfte ich mal wieder das Rad neu erfinden. -- ThomasBayen

noch zu lösende Probleme #

Leider unterstützen viele Lösungen das Filtern nicht, deshalb hier nochmal zur Erklärung: Wenn ich den Anfang eines Begriffes eingebe, mächte ich, daß in der Liste darunter nur noch die Einträge stehen, die zu meinem Anfang passen (wie im Firefox). Die Swinglabs-Lösung z.B. lässt die Liste wie sie ist, geht aber automatisch zum ersten Eintrag, der passt. Ob noch weitere passen könnten, sehe ich nicht (wenn die nicht direkt untereinander stehen).

Neben dem Filtern sind meine Anforderungen noch: Der Umgang mit richtig grossen Listen (also eine API, in die ich Datenbankzugriffe reinschreiben kann) sowie die Möglichkeit, die Trefferfunktion selber bestimmen zu können (also nicht nur Suche nach dem Anfang des Strings, sondern eine eigene Funktion z.B. für reguläre Ausdrücke etc).

evtl. ist das Buch 'Swing Hacks' ein guter Hinweis. JavaBooklist.

Optimierung des Zeichnens unter Swing #

Wer wissen will, wie in Swing was warum in welcher Methode gezeichnet wird und wer mehr über paint(), repaint(), update(), doubleBuffered, opaque und solche Sachen erfahren möchte, als er jemals wissen wollte, sollte diesen Artikel lesen:

http://java.sun.com/products/jfc/tsc/articles/painting/

Wer wissen will, was sein Programm so treibt, kann dazu http://freedesktop.org/wiki/Software/Xephyr benutzen. Das ist ein Xnest-ähnlicher X-Server, der in einem Fenster arbeitet. Er hat einen Debug-Mode, der einem anzeigt, wann was gezeichnet wird. Das hilft dabei, mehrfache und unnötige refresh-Durchläufe zu finden.

disablen (ausgrauen) eines ganzen JPanel #

Wenn man ein komplexes Widget aus mehreren Unterwidgets aufgebaut hat (z.B. ein Eingabeformular), will man dieses in manchen Fällen schon mal ausser Betrieb setzen. Mit einem einzelnen Eingabefeld wie einem JTextField macht man das durch einen Aufruf von setEnabled(boolean). Man setzt damit aber immer nur das aktuell angesprochene Widget ausser Kraft und nicht seine Tochterwidgets.

Dieses Problem ist recht schwierig zu lösen, weil Sun es scheinbar "vergessen" hat. Im groben gibt es zwei Methoden:

  • rekursives dis-/enablen aller Tochterwidgets. Dabei ist zu beachten, daß es auch Widgets geben kann, die aus anderen Gründen bereits disabled sind. Diese muss man sich merken, um die richtigen später wieder zu enablen. In den meisten Fällen sollte das eine ordentliche Lösung ergeben. Haben sich die Tochterwidgets in der Zwischenzeit jedoch verändert, gibt das völliges Chaos und ist nicht praktikabel. Ein Beispiel für eine solche Lösung findet sich unter http://tips4java.wordpress.com/2009/08/02/disabled-panel/
  • Man erzeugt eine Art Glasspane, die man über das normale Widget legt und die es "ausgraut". Ausserdem muss man noch die Tastatur-Lsitener abschalten sowie das Widget und seine Töchter aus dem Focus-Traversal herausnehmen.
    • Eine solche Lösung geht mit dem JXLayer. Dieser stellt eine zusätzliche Schicht zwischen dem Benutzer und dem Widget dar, mit der man alles mögliche machen kann, unter anderem auch das Widget ausgrauen und deaktivieren. Interessanterweise ist das Ding so toll, daß ich noch nicht mal ein simples ausgrauen hinbekommen habe. :-) Stattdessen gibt es einen Blur-Effekt, den ich aus einer Bildbearbeitungs-Bibliothek nehmen musste.
    • Unter http://tips4java.wordpress.com/2009/08/02/disabled-panel/ gibt es hierzu auch eine etwas bodenständigere Lösung, die aus drei Java-Klassen besteht, die gut erklärt sind und deren Funktionsweise man schnell verstehen kann. Je nach Look&Feel ergibt sich eine etwas seltsame Farbe beim ausgrauen, die man im Quelltext aber einfach anpassen kann (statt der Suche im L&F einfach "Color(255,255,255)" nehmen).

Hänger beim Debugging in einer JComboBox #

Falls man, z.B. mit Eclipse Code debuggen will, der innerhalb der Render-Routine einer JComboBox läuft, kann das zu Problemen führen. Das gilt z.B. für die Implementierung eines eigenen JComboBoxModel oder für diverse Listener, die dort aufgerufen werden könnten. Zum Beginn des Zeichnens der Cmobobox sperrt Swing scheinbar aus irgendeinem Grund den X-Server. Hält der Debugger das Programm nun an der flaschen Stelle an, so hängt der ganze X-Server. :-(

Kurzfristige Abhilfe schafft hier das Einloggen auf der Textkonsole und ein schnelles "killall java", da der X-Server nicht wirklich abgestürzt, sondern eben nur gesperrt ist. Aber damit ist das Debugging-Problem ja immer noch nicht gelöst.

Lösung 1 #

Wer also solchen Code wirklich debuggen muss, kann es mit einem zweiten X-Server so wie ich machen: Ich habe mit

  Xephyr :1

einen eigenen "X-Server im Fenster" für mein Programm gestartet (sollte mit "Xnest" auch gehen). Dann bin ich in die Eclipse Startkonfiguration gegangen und habe dort im "Environment" den Wert "DISPLAY" auf ":1" gesetzt. Nun läuft mein Programm innerhalb des Xephyr-Fensters und hängt auch nur diesen auf. Eclipse (im normalen X-Server) ist aber weiterhin noch bedienbar und kann wunderbar zum Debuggen benutzt werden.

Übrigens bin ich auf dieses Problem schon vor einiger Zeit gestossen. Damals hatte ich mir notiert, daß ein Aufruf von "setLightWeightPopupEnabled(false);" auf der JComboBox auch hilft. Tat es jetzt aber nicht mehr. Vielleicht hängt das von den Umständen ab und ist im Zweifelsfall auch einen Versuch wert.

Unter Umständen kann man mit "export DISPLAY=:1; metacity &" auch den Gnome Fenstermanager im Xephyr-Server starten, bevor man sein Java-Programm startet. Das erlaubt eine bessere Bedienung der Java-Fenster. -- ThomasBayen

Lösung 2 #

Nach einigem Googlen habe ich herausgefunden, daß ich nicht der einzige bin, der das Problem hat (http://bugs.sun.com/view_bug.do?bug_id=6517045 und vor allem http://bugs.sun.com/view_bug.do?bug_id=6714678). Dort war dann auch ein rettender Hinweis: In aktuellen Java-Versionen kann man mit

  -Dsun.awt.disablegrab=true

als VM-Parameter das Problem auch umgehen. (Ich frage mich zwar, wenn jemand einen Schalter dafür eingebaut hat, warum der nicht immer eingeschaltet ist, aber was solls...)

Wizards programmieren #

Ein jeder hat wohl schon mal einen Wizard bedient, z.B. in der Forma als einen Installer. Da kommt natürlich die Frage auf, wie man sowas selber programmieren kann. Es geht dabei zumeist um eine Abfolge von Seiten, in denen man Eingaben tätigen kann. Diese werden ggf. überprüft und abhängig von den Eingaben kommt man auf andere Seiten oder nicht. Am Ende drückt man "Finish" und dann spätestens geschieht irgendetwas. Ich habe hier mal eine Sammlung von Links zum Thema aufgeführt:

sonstige Seiten zum Thema #

In diesem Wiki sind die folgenden Seiten mit dem Schlagwort Swing markiert:

Tags:  Java, Swing

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-22) was last changed on 03-Jan-2011 16:54 by ThomasBayen