2015-06-29

Schöne neue Welt, Teil 2

In meinem vorherigen Post hatte ich geschrieben: Deshalb bietet es sich an, auch Positions- und Größenänderungen des Fensters zu überwachen, und die Werte im nicht maximierten Zustand zu sichern. Allerdings sieht JavaFX 8 meines Wissens nach keine Möglichkeit vor, sich mittels setOn…() über Änderungen der Fenstergröße oder –position informieren zu lassen. Meine Intention war deshalb, für die Properties x, y, width und height jeweils einen ChangeListener zu registrieren. Diese hätten zunächst geprüft, ob das Fenster maximiert wurde. Ist dies nicht der Fall, wären die jeweiligen Eigenschaften in die Preferences geschrieben worden.

Nun ist klar, dass das Anklicken des Volle Größe-Widgets zu Änderungen an allen vier Eigenschaften führen muss. Unerwartet ist für mich aber gewesen, dass die gemeldeten Koordinaten nicht zur maximized-Property passen. Hierzu ein Screenshot. Die Ausgaben sind unmittelbar nach dem Anklicken des Fensterwidgets entstanden. Die ersten beiden Ausgaben beziehen sich auf die ursprüngliche Breite und Höhe des Fensters. Die letzten beiden hingegen (x- und y-Position) entsprechen dem maximierten Zustand.

Bildschirmausgaben in der NetBeans-Console

Warum ist dies unbefriedigend? Da es keine Möglichkeit gibt, alle vier Angaben (Position und Größe des Fensters) in einem Rutsch zu ermitteln, tröpfeln die Änderungen in nicht vorhersehbarer Reihenfolge ein. Für das Speichern in den Benutzereinstellungen wäre es aber wichtig, alle Werte zum selben Zeitpunkt ermitteln zu können. Ich glaube, dass so etwas in das Framework gehört. Bis es soweit ist, können Sie sich aber mit einem Workaround behelfen, den ich im nächsten Post vorstellen werde.

Wie gehen Sie mit dem Problem um? Schreiben Sie mir…

2015-06-28

Schöne neue Welt

Kann sich noch jemand an den JSR 296 erinnern? Er spezifiziert ein Swing Application Framework, das die reichlich vorhandenen Bausteine von Swing insbesondere für den unerfahrenen Entwickler sinnvoll zusammenfasst. Leider wurde der Java Specification Request nie in eine Java-Version übernommen. Gut, mittlerweile ist Swing ebenfalls obsolet. Jedenfalls war ein recht nützliches Feature das sich Kümmern um Fensterzustände. Wird die Anwendung beendet, speichert das Framework Größe und Position in einer .properties-Datei. Warum man in der Referenzimplementierung seinerzeit nicht die Preferences API genutzt hat, verstehe ich übrigens bis heute nicht. Anyway. Die spannende Frage ist nun: wie sieht das unter JavaFX aus? Unterstützt der Swing-Nachfolger den Entwickler mit fertigen Hilfsklassen? Die nicht überraschende Antwort ist: im Prinzip ja. Eigentlich ist alles vorhanden. Wie schon bei Swing muss man sich den entsprechenden Code aber selbst zusammenhacken.

Man findet im Netz einige Posts zu dem Thema. Meistens wird vorgeschlagen, mit stage.setOnCloseRequest() einen EventHandler zu setzen, der Position und Größe der main stage ausliest und mit der Preference API persistiert. Beim (erneuten) Start der Anwendung werden die gespeicherten Werte ausgelesen und vor dem Aufruf von stage.show() gesetzt. Soweit, so gut. Was ich bislang nirgendwo gelesen habe: man sollte zusätzlich abspeichern, ob das Fenster beim Beenden maximiert war, und diesen Zustand beim Start wiederherstellen. Andernfalls erscheint das Fenster nämlich nur “irgendwie groß”, weil zwar Breite und Höhe auf volle Größe gesetzt werden, aber das korrespondierende Widget nicht den richtigen Zustand hat. Noch ein Caveat: ist das Fenster maximiert, dürfen Position und Größe nicht übernommen werden, damit beim erneuten Anklicken die ursprüngliche Größe wiederhergestellt werden kann. Also:

  • beim Beenden immer abspeichern, ob das Fenster maximiert ist (stage.isMaximized())
  • nur wenn das Fenster nicht maximiert ist, Position und Größe sichern

Beim Start der Anwendung…

  • immer setMaximized() mit dem gespeicherten Wert setzen; Standardwert ist false
  • Position und Größe auslesen und, sofern vorhanden, setzen

Es gibt einen Sonderfall, den meine Lösung nicht berücksichtigt. Verschiebt man ein nicht maximiertes Fenster oder ändert seine Größe, maximiert es danach, und beendet dann die Anwendung, so erscheint das Fenster nicht an der erwarteten Position und in der erhofften Größe, wenn man nach einem Neustart die Maximierung aufhebt. Deshalb bietet es sich an, auch Positions- und Größenänderungen des Fensters zu überwachen, und die Werte im nicht maximierten Zustand zu sichern.

Ein Tipp zum Schluss: Stages können noch einige weitere Zustände haben, die man unter Umständen persistieren und wiederherstellen möchte…

2015-06-20

Hätten Sie’s gewusst? Project ThreeTen

Das Java-Ökosystem ist so riesig, dass man eigentlich gar nicht alles kennen kann… Zumindest ging mir das so mit ThreeTen. Ziel dieses Projekts war die Implementierung von JSR 310 Date and Time API in das JDK 8. Dies ist seit geraumer Zeit abgeschlossen; das Projekt wurde beendet. Die Weiterentwicklung findet in den bewährten OpenJDK-Prozessen statt. Spannend ist aber, dass es einen Backport für Java SE 6 und 7 gibt. Dieser lässt sich dank Maven leicht in eigene Projekte integrieren:

<dependency>
  <groupid>org.threeten</groupid>
  <artifactid>threetenbp</artifactid>
  <version>1.2</version>
</dependency>

Ich habe mich gefragt, ob sich die neuen Zeit- und Datumsfunktionen auch unter Android nutzen lassen. Und siehe da – es funktioniert. Sie können in Android Studio entweder Maven Central als Repository hinzufügen, oder einfach die .jar-Datei händisch herunterladen und in den libs-Ordner kopieren. In diesem Fall müssen Sie mit Add As Library (Rechtsklick auf die Datei) dafür sorgen, dass Android Studio sie in das Buildfile übernimmt. Probieren Sie danach einfach das folgende Codeschnipsel aus:

private void time() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now.toString());
    LocalDate bd = LocalDate.of(1970, Month.AUGUST, 29);
    System.out.println(bd.toString());
}

Bei der Übernahme von Codebesipielen für JSR 310 müssen Sie nur eine Kleinigkeit beachten: der Paketname des Backports ist org.threeten.bp.

2015-06-06

Clip4Moni Downloads

Clip4Moni-LogoClip4Moni ist open source, kann deshalb jederzeit aus Bitbucket geklont werden. Aber vielleicht möchten Sie das Progrämmchen ja einfach nur ausprobieren. Da ist klonen und selber bauen vielleicht etwas mühselig. Deshalb stelle ich seit heute zwei fertige Archive bereit. Für Windows gibt es ein gezipptes .msi-File. Die  wenigen Dateien landen nach der Installation in AppData\Roaming\Clip4Moni. Der Installer erzeugt zwei Verknüpfungen, eine unter Programme\Clip4Moni, sowie eine unter Programme\Autostart. Für den Mac habe ich ein App-Bundle geschnürt. Nach dem Entpacken einfach in den Programme-Ordner verschieben. Da Clip4Moni keine Java Runtime an Board hat, muss unter beiden Betriebssystemen die JRE vorher installiert werden. Es ist mindestens Java 7 erforderlich.

Nach dem Start befindet sich ein Clip4Moni-Icon unter Windows im Infobereich der Taskleiste, unter Mac OS X im rechten Bereich der Menüleiste. Auf dem Mac wird das Menü durch Anklicken mit der linken Maustaste geöffnet, unter Windows mit der rechten (das ist unter Java so vorgesehen!).

Das Clip4Moni-Popupmenü

Um einen neuen Eintrag anzulegen, klicken Sie auf Neuer Eintrag…. Daraufhin öffnet sich der gleichnamige Dialog. Sofern sich zu diesem Zeitpunkt Text auf dem Klemmbrett befindet, wird dieser als Inhalt übernommen. Beschreibung erscheint nach dem Schließen des Dialogs mit OK im Clip4Moni-Menü. Probieren Sie es aus und tippen Testeintrag in das Beschreibungsfeld. Tipp: klicken Sie doch einmal mit der rechten Maustaste, wenn sich der Mauszeiger über dem Feld Inhalt befindet.

Der Dialog Neuer Eintrag

Nun sieht das Menü so aus:

Clip4Moni-Menü nach dem Hinzufügen eines Eintrags

Möchten Sie in einer Anwendung ein Textschnipsel einfügen, klicken Sie den korrespondierenden Eintrag im Clip4Moni-Menü an, und wählen danach in der Zielanwendung Paste bzw. Einfügen. Das Menü Klemmbrett listet Operationen auf, die Sie mit dem Text auf dem Klemmbrett ausführen können.

Das Klemmbrett-Menü

Recht praktisch ist Inhalt anzeigen….

Der Dialog Inhalt anzeigen

Auch für das Bearbeiten bestehender Einträge gibt es einen Dialog. Er ist im Folgenden zu sehen:

Der Dialog Einträge bearbeiten

Clip4Moni gibt es schon recht lange, seit 2008. Wie der Name andeutet, war das Programm ursprünglich eine Spezialanfertigung für meine Frau. Da Moni möglichst kleine Programme mag, habe ich es immer schlank und schlicht gehalten. Deshalb gibt es zum Beispiel keine Tastenkürzel und keine Validierung. Gelegentlich bastle ich etwas daran, so dass das eine oder andere vielleicht doch noch hinzugefügt wird. Auf der anderen Seite… die Dateigröße der Archive spricht für sich, oder? Smiley

2015-06-05

Liebling ich habe die GUI geschrumpft

Zugegeben, es ist nicht mehr besonders hip, über AWT oder Swing zu bloggen. Auf der anderen Seite gibt es noch so viele Programme, die man gerne weiter benutzen möchte. Bei mir ist das unter anderem meine kleine Textschnipselverwaltung Clip4Moni. Unglücklicherweise macht(e) diese ungewollt auf ganz modernen Geräten mit high dpi-Anzeige keine besonders gute Figur. Das (bekannte) Problem ist, dass AWT und Swing alle Komponenten zu klein rendern. Das mag insbesondere bei AWT überraschen, weil native Komponenten doch eigentlich wissen sollten, wie sie sich korrekt darstellen müssen.

Screenshot von einem high dpi-Gerät

Mein Surface Pro 3 löst bei 12 Zoll Bildschirmdiagonale üppige 2160 x 1440 Pixel auf. Das sind 216 dpi. Auf dem Screenshot ist schön zu sehen, wie groß eine 9-Punkt Segoe UI bei einer Skalierung von 200% eigentlich sein müsste. Und wie lächerlich klein dagegen das AWT-Menü ist. Die Frage ist nun: kann man eine lesbarere Darstellung erzwingen, indem man dem AWT befiehlt, einen größeren Zeichensatz für Menüs zu verwenden? Die Größe von Fonts wird in Punkt angegeben; ein Punkt entspricht einem Zweiundsiebzigstel Zoll. Für die Segoe UI 9 Punkt wären dies bei 200% dann 18 Punkt. Welchen Zeichensatz AWT und Swing für Menüs verwenden, kann man auf zweierlei Weise abfragen: Font f = UIManager.getFont("MenuItem.font") oder Font f = (Font) Toolkit.getDefaultToolkit().getDesktopProperty("win.menu.font"). Letzteres liefert aber nur unter Windows ein Ergebnis.

Skurril ist, dass beide Abfragen auf meinem Rechner Segoe UI 18 Punkt liefern. Heißt das, dass der Workaround mit dem größeren Font nicht funktioniert? Aus Verzweiflung habe ich den gelieferten Zeichensatz mit setFont() für alle Einträge des Clip4Moni-Menüs gesetzt. Das Ergebnis ist überraschend:

Screenshot eines korrigierten Menüs

Sieht so aus, wie es soll.  Es reicht also, für jedes Menüelement explizit den Font zu setzen, den Java als Standard liefert. Noch sind in Clip4Moni die Swing-Dialoge zu klein, aber das müsste sich mit ähnlichen Workarounds ebenfalls beheben lassen.