Intechcore auf dem 25. Deut­scher EDV-Gerichtstag 2016. Gemeinsam mit unseren Partnern im Bereich Software für die Justiz haben wir die zukünftige Lösung forumSTAR-Text Moder­ni­sierung präsentiert.

In diesem Beitrag möchten wir einige Vorteile der JavaFX-Bibliothek erläutern, die besonders im Bezug auf das Userin­terface eine Rolle spielt.
Als einer der wichtigen Vorteile der JavaFX Bibliothek gilt die Möglichkeit der Nutzung von Styles (CSS), die uns aus dem WWW bekannt sind.
Dadurch verliert sich die Abhän­gigkeit zu der Bibliothek Look & Feel. Man kann jedoch selbst das Aussehen der Appli­kation bestimmen, was auch ziemlich flexibel und schön möglich ist. Die Gestaltung kann dynamisch erfolgen, eine Animation beinhalten oder auch 3D Grafik.
Durch die Benutzung der Style-Konzeption ist es nun möglich, die Appli­ka­tionen mit so genannten „Skins“ zu erstellen, durch die das Aussehen der Appli­kation komplett von der Business­logik losgelöst wird und mehr Indivi­du­altät erhält. Solche indivi­du­ellen Skins können sogar separat von einem Designer erstellt werden.

Erstellen wir ein einfaches Beispiel eines Dialog­fensters mit einem Button:

public class JavaFXDialog1 extends Application {
    @Override
    public void start(Stage stage) {
        final VBox vbox = new VBox();
        final Button button = new Button("test");
        vbox.getChildren().addAll(button);
        final Scene scene = new Scene(vbox, 150, 100);
        stage.setScene(scene);
        stage.show();
    }
  
    public static void main(String[] args) {
        launch(args);
    }

avafx and css styles 1

Styles kann man unter­schiedlich anwenden:
1) Unmit­telbar im Code, um z.B. die Schrift­farbe in dem Button zu ändern:

button.setStyle("-fx-text-fill: red");

avafx and css styles 2

2) Mit Hilfe einer CSS-Datei, auf die die Klasse Scene ausge­richtet werden soll:
Dafür wird eine Datei mit der Erwei­terung .css erstellt und unter dem Projekt­ver­zeichnis abgelegt, z.B. /css/styles.css.
Inhalt der Datei:

.button {
    -fx-text-fill: blue;
}

Dabei ist es sehr wichtig, die Entwick­lungs­um­gebung so einzu­richten, dass sie diese CSS-Dateien beim Bilden der Appli­kation auch mitkopiert.
Unter IntelliJ IDEA wird es beispiels­weise folgen­der­maßen gemacht:

avafx and css styles 3

Nun ist alles fertig, um die Style-Datei einzubinden:

scene.getStylesheets().add((getClass().getResource("/css/styles.css")).toExternalForm());

Wir starten das Projekt und bekommen folgendes Dialogfenster:
avafx and css styles 4

Instruktion .button in der CSS-Datei sagt aus, dass nun alle Knöpfe eine blaue Schrift­farbe haben werden:

final Button button1 = new Button("button1");
final Button button2 = new Button("button2");
vbox.getChildren().addAll(button1, button2);

avafx and css styles 5

Und was, wenn das nicht das ist, was wir brauchen? Was, wenn wir einen konkreten Knopf definieren wollen?
3) Abhilfe schafft die userbe­zogene Definition des Knopf-Styles:
In styles.css schreiben wir:

.button1 {
    -fx-text-fill: green;
}

Und im Code:

button1.getStyleClass().add("button1");

Das Dialog­fenster sieht so aus:
avafx and css styles 6

Nun haben alle Knöpfe, die mit der Style-Klasse verbunden sind, grüne Schrift­farbe, wobei die Methode add() uns dabei Hinweise gibt, dass wir mehrere von solchen Styles hinzu­fügen können, wodurch verschiedene Element­ei­gen­schaften erweitert, vorde­fi­niert oder überladen werden.
4) User-Style kann man auch durch den so genannten ID definieren:

In der styles.css schreiben wir:

#button2 {
    -fx-text-fill: yellow;
}

Und im Code:

button2.setId("button2");

Als Ergebnis bekommen wir folgendes Dialogfenster:

avafx and css styles 7

D.h. alle Elemente mit der gleichen ID sehen gleich aus.

Was kann man sonst noch Inter­es­santes mit den Styles machen?

Styles können auch die so genannten Verhal­ten­st­rigger bearbeiten, die aus der XAML Welt kommen.
Wenn wir bei dem Beispiel mit dem Knopf bleiben, gehören zu diesen Triggern solche Ereig­nisse der GUI wie Fokus, Selektion, Mousedown, Mouseover usw. also alles, was man nicht im Dialog­fenster haben möchte. Es beinhaltet nur den Buisiness­login bei den kunden­sei­tigen Änderungs­wün­schen und es wird nur die CSS-Datei geändert und nicht die Logik.
So kann man z.B. mit der folgenden CSS-Definition die Farbe des Knopfes ändern, wenn der User mit der Maus darüber fährt.

.button:hover {
    -fx-background-color: orange;
}

So sieht das Dialog­fenster bei Mouseover aus:

avafx and css styles 8

Wie bereits oben erwähnt, führt das zum gleichen Verhalten von allen Button-Klassen. Mit diesem Code:

.button1:hover {
    -fx-background-color: orange;
}

werden die Trigger nur für die Elemente angewendet, die auf die Klasse «button1» verweisen.

Diese Vorge­hens­weise kann man bei vielen Trigger anwenden, bei Knöpfen gibt es z.B. auch noch foused, selected oder presset.

Leider kann das nicht direkt im Code genutzt werden:

button.setStyle(":hover -fx-text-fill: red");

Vielleicht wird diese Möglichkeit in der Zukunft durch JavaFX Developer realisiert.

Wozu brauchen wir das alles? Im Internet kann man noch eine Reihe an Beispielen finden, die deutlicher sind als dieses. Das Ziel des Beitrags war nicht, diese zu kopieren. Uns geht es darum, dass die Konzeption von Syles und Trigger  auch für den eigenen Bedarf erweitert werden kann, das ist für uns von Interesse.

Als Beispiel können wir folgende Ausgangs­si­tuation betrachten:
wir wollen mit JavaFX eine visuelle Kompo­nente umsetzen, die für die Auswahl der Größe einer im Text einge­fügten Tabelle dienen soll. Das Aussehen der Kompo­nente, das Farbschema, die Größe usw., sollen dabei kein Bestandteil der Business­logik der Kompo­nente sein, sondern über eine externe CSS-Datei konfi­gu­rierbar sein.

Das sollte ungefähr so aussehen:

avafx and css styles 9

Der User geht mit dem Mauszeiger über die Tabelle und sieht seine ausge­wählten Elemente, in dem Fall eine Tabelle mit 7 x 8 Zellen. Beim Klick auf die Kompo­nente sollte die Auswahl an das Programm übermittelt werden, um eine entspre­chende Tabelle einzufügen.
Sicherlich kann man auf die Auswahl über den Code reagieren, aber was wenn der eine Kunde eine bestimmte Farbe bevorzugt, der andere aber ganz andere Farben bevorzugt? Möglich ist auch, dass das Farbschema durch ein «Skin» oder sonst wie vorde­fi­niert wird – was dann?

Hier wird nur eine Kompo­nente für die Zellen­se­lektion benötigt, um deren Aussehen es an dieser Stelle aber nicht gehen soll.

Hier können wir auf das voran­ge­gangene Beispiel mit dem Trigger «hover» für den Button anknüpfen. Der wirkt aller­dings für jede Zelle, wenn man mit dem Mauszeiger über sie fährt. Die Zellen, die der Mauszeiger verlassen hat, entsprechen dem Trigger nicht mehr und fallen somit aus der Auswahl. Wie können wir den gesamten Bereich ausge­wählt behalten?

Für die Lösung dieser Aufgabe wird ein eigener Trigger erstellt, der auf eine bestimmte Eigen­schaft des Objektes reagiert, z.B. «bin im Diapason der Selektion» oder inRange, die wir folgen­der­maßen in der CSS-Datei definieren können:

.MyCell:inRange {
    -fx-border-width: 0.5;
    -fx-border-color: #ffffff;
    -fx-background-color: lightskyblue
}

An der Stelle muss man aber sagen, dass dies ist keine einfache Aufgabe ist. Zudem ist die Lösung für die JavaFX Versionen 1.7 und 1.8 gänzlich unter­schiedlich. Für die Lösung in der Version 1.7. sind wir auf die Nutzung einer Menge der depre­cated-Methoden angewiesen.

Für den Anfang schauen wir uns die Kompo­nente an:

public class JavaFXDialog2  extends Application {
    @Override
    public void start(Stage stage) {
        final VBox vbox = new VBox();
        final GridPaneEx table = new GridPaneEx();
        table.init(10, 10);
        final Label label = new Label();
        label.setMaxWidth(Double.MAX_VALUE);
        label.setAlignment(Pos.CENTER);
        label.setTextAlignment(TextAlignment.CENTER);
        label.setStyle("-fx-padding: 3 0 5 0");
        label.textProperty().bind(table.text);
        vbox.getChildren().addAll(label, table);
        final Scene scene = new Scene(vbox, 350, 300);
        scene.getStylesheets().add((getClass().getResource("/css/styles.css")).toExternalForm());
        scene.setFill(null);
        stage.setScene(scene);
        stage.show();
    }
 
 
    public static void main(String[] args) {
        launch(args);
    }
 
 
    private void fireCreateTable(final int cols, final int rows){
        System.out.println("cols = " + cols + ", rows = " + rows);
    }
 
 
    protected class GridPaneEx extends GridPane {
  
        public final StringProperty text = new SimpleStringProperty("cancel");
        private int cols;
        private int rows;
  
        public GridPaneEx(){
            this.setOnMouseExited(new EventHandler() {
                @Override
                public void handle(MouseEvent mouseEvent) {
                    text.setValue("cancel");
                    deselectAll();
                }
            });
        }
  
        public void init(final int cols, final int rows){
            getChildren().clear();
            this.cols = cols;
            this.rows = rows;
            for (int col = 0; col < cols; col++){
                for (int row = 0; row < rows; row++){
                    final Button rect = new Button();
                    rect.setMinSize(30, 10);
                    add(rect, col, row);
                    final int selectedCol = col;
                    final int selectedRow = row;
                    rect.setOnMouseMoved(new EventHandler() {
                        @Override
                        public void handle(MouseEvent mouseEvent) {
                            selectRange(selectedCol, selectedRow);
                            text.setValue((selectedCol + 1) + " x " + (selectedRow + 1));
                        }
                    });
                    rect.setOnAction(new EventHandler() {
                        @Override
                        public void handle(ActionEvent actionEvent) {
                            fireCreateTable(selectedCol + 1, selectedRow + 1);
                            deselectAll();
                        }
                    });
                }
            }
            deselectAll();
        }
  
        private Node getNodeFromGridPane(int col, int row) {
            for (Node node : getChildren()) {
                if (GridPane.getColumnIndex(node) == col && GridPane.getRowIndex(node) == row) {
                    return node;
                }
            }
            return null;
        }
  
        private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            if (select){
                node.setStyle("-fx-border-width: 0.5; -fx-border-color: #ffffff; -fx-background-color: lightskyblue");
            } else {
                node.setStyle("-fx-border-width: 0.5; -fx-border-color: #000000; -fx-background-color: #ffffff");
            }
        }
  
        public void deselectAll(){
            for (int col = 0; col < cols; col++){
                for (int row = 0; row < rows; row++){
                    selectCell(col, row, false);
                }
            }
        }
        private void selectRange(int selectedCol, int selectedRow){
            deselectAll();
            for (int col = 0; col <= selectedCol; col++){
                for (int row = 0; row <= selectedRow; row++){
                    selectCell(col, row, true);
                }
            }
        }
    }
}

Besonders inter­essant ist die Methode selectCell, in der die Zellen­färbung unmit­telbar im Code reali­siert wird:

Für normale Zellen:

node.setStyle("-fx-border-width: 0.5; -fx-border-color: #000000; -fx-background-color: #ffffff");

Für Zellen im ausge­wählten Bereich:

node.setStyle("-fx-border-width: 0.5; -fx-border-color: #ffffff; -fx-background-color: lightskyblue");

Weil es in der Aufga­ben­be­schreibung steht, dass klare Farben­zu­weisung unmöglich ist, versuchen wir die mit Hilfe eines eigenen Styles in styles.css zu definieren:

#MyCellNormal {
    -fx-border-width: 0.5;
    -fx-border-color: #000000;
    -fx-background-color: #ffffff;
}
 
 
#MyCellInRange {
    -fx-border-width: 0.5;
    -fx-border-color: #ffffff;
    -fx-background-color: lightskyblue
}

Und in der Methode selectCell:

private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            if (select){
                                node.setId("MyCellNormal");
            } else {
                                node.setId("MyCellInRange");
            }
        }

Schon besser, nicht war? D.h. wenn der Kunde mit der Farbwahl unzufrieden sein sollte, kann man sie direkt in der Datei styles.css ändern. Die Logik der Kompo­nente bleibt dabei unverändert.

Aller­dings gibt es eine noch elegantere Lösung der Aufgabe: Platzieren wir in styles.css noch zusätzlich 2 Sytles:

.MyCell {
    -fx-border-width: 0.5;
    -fx-border-color: #000000;
    -fx-background-color: #ffffff;
}
 
 
.MyCell:inRange {
    -fx-border-width: 0.5;
    -fx-border-color: #ffffff;
    -fx-background-color: lightskyblue
}

Das bedeutet, dass die Zellen sich auf den Style «MyCell» orien­tieren und wenn der Trigger «inRange» greift, analog zu «hover» oder «presset», ändert sich die Farbe entsprechend.
Aber wie bringen wir der Zelle bei, den Trigger zu starten?
Da wir in unserem Beispiel für die Zellen, Button-Elemente nutzen, ist es erfor­derlich ihr Verhalten in der so genannten Pseudo-Klasse neu zu definieren. In JavaFX 1.7 wird das so gemacht:

protected static class RangeButton extends Button {
        public RangeButton(){
            getStyleClass().add("MyCell");
        }
 
 
        private BooleanProperty inRange = new BooleanPropertyBase() {
 
 
            @Override
            protected void invalidated() {
                impl_pseudoClassStateChanged("inRange");
            }
 
 
            @Override
            public Object getBean() {
                return RangeButton.this;
            }
 
 
            @Override
            public String getName() {
                return "inRange";
            }
        };
 
 
        public boolean isInRange() {
            return inRange.get();
        }
 
 
        public void setInRange(boolean value) {
            inRange.set(value);
        }
 
 
        private static final long IN_RANGE_PSEUDOCLASS_STATE = StyleManager.getInstance().getPseudoclassMask("inRange");
 
 
        @Override
        public long impl_getPseudoClassState() {
            long mask = super.impl_getPseudoClassState();
            if (isInRange()) mask |= IN_RANGE_PSEUDOCLASS_STATE;
            return mask;
        }
    }

Wie wir sehen, sind alle Methoden «…Pseudo­Class…» – deprecated.
Jetzt nutzen wir statt Button unseren RangeButton. Und die Methode selectCell sieht nun so aus:

private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            ((RangeButton)node).setInRange(select);
        }

D.h. die Änderung der Feldei­gen­schaft «InRange» führt dazu, dass der Style-Trigger greift und die Farbe der ausge­wählten Zellen sich entspre­chend ändert.
Das ist genau das, was wir brauchen!
In JavaFX 1.7 funktio­niert das. In JavaFX 1.8 ist das leider verboten. Der Code ist nicht mehr kompi­lierbar sobald JVM 1.8 hinzu­ge­schaltet wird.
Was bietet uns dann die neue Version an der Stelle?
Wie bereits erwartet, wurden die depre­cated Methoden entfernt und die Archi­tektur verein­facht. Jetzt reicht es daher aus, wenn wir folgendes tun:

protected static class RangeButton extends Button {        protected final PseudoClass pcInRange = PseudoClass.getPseudoClass("inRange");
 
 
        public RangeButton(){
            getStyleClass().add("MyCell");
        }
 
 
        protected final BooleanProperty inRange = new BooleanPropertyBase() {
 
 
            @Override
            protected void invalidated() {
                pseudoClassStateChanged(pcInRange, getValue());
            }
 
 
            @Override
            public Object getBean() {
                return RangeButton.this;
            }
 
 
            @Override
            public String getName() {
                return "inRange";
            }
        };
 
 
        public boolean isInRange() {
            return inRange.get();
        }
 
 
        public void setInRange(boolean value) {
            inRange.set(value);
        }
    }

Alles funktio­niert wie bisher.
Man kann den Code noch etwas vereinfachen…:

protected static class RangeButton extends Button {
        protected final BooleanProperty inRange;
        public RangeButton(){
            getStyleClass().add("MyCell");
            final PseudoClass pcInRange = PseudoClass.getPseudoClass("inRange");
            inRange = new SimpleBooleanProperty();
            inRange.addListener(new ChangeListener() {
                @Override
                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                    pseudoClassStateChanged(pcInRange, newValue);
                }
            });
        }

…und die Methode entspre­chend ändern:

private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            ((RangeButton)node).inRange.setValue(select);
        }

Zusam­men­fassung: Mit Hilfe der beschrie­benen Lösung auf JavaFX ist es möglich, spezielle userbe­zogene Eigen­schaften der Kompo­nenten zu erstellen und diese mit den Styles zu verknüpfen,. Das ist besonders bequem, wenn die Anfor­derung besteht, die Business­logik komplett von der GUI zu abstrahieren.

10. November

Im August 2009 haben wir in der Ausschreibung des Projektes „Pflege und Weiter­ent­wicklung Textsystem_RP“ des Minis­te­riums der Justiz Rheinland-Pfalz teilge­nommen. Im November 2009 haben wir Zuschlag erteilt bekommen.

Die Intechcore GmbH mit Sitz in München wurde 2008 gegründet. Seit der Gründung begleiten wir diverse Projekte der Justiz in verschie­denen Bundesländern.

Unsere Kompe­tenzen:

Intechcore ist auf die Optimierung, Wartung und Neuent­wicklung von Software und Software­sys­temen, sowie auf Beratungen zur Quali­täts­si­cherung von Software­sys­temen spezialisiert.

Auf dem 22. EDV-Gerichtstag 2013 präsen­tiert Intechcore folgende Projekte:

  • Pflege und Weiter­ent­wicklung von forumSTAR-Text. Hier handelt es sich um ein Textver­ar­bei­tungs­system für die Justiz
  • Pflege und Weiter­ent­wicklung von web.sta- Zwischen­schicht. Es geht um einen Teil der Fachan­wendung web.sta, die als DMS (Dokumen­ten­ma­nage­ment­system) funktio­niert und zusammen mit forumSTAR-Text die Abarbeitung von XML-Vorlagen und XML-Dokumente ermöglicht
  • Entwicklung und Pflege von XML-Vorlagen für die staats­an­walt­schaft­liche Textver­ar­beitung des Landes Rheinland-Pfalz

Hier geht es zur Übersicht:

http://www.edvgt.de/pages/22.-deutscher-edv-gerichtstag/begleitausstellung-und-unternehmenportraets.php

Achtung, seit Januar 2016 haben wir neue Adresse:

Intechcore GmbH
Keltenring 17
82041 Oberha­ching bei München

Wir freuen uns, am 23. EDV-Gerichtstag im September 2014 in Saarbrücken teilzunehmen.

30. November

In einem unsere Projekte setzten wir Scala und JavaFX ein. Bei der Quali­täts­si­cherung wird zusätzlich scalastyle verwendet.

17. September

Unsere Firma hat vom 12.09.2012 bis 14.09.2012 am EDV-Gerichtstag 2012 in Saarbrücken teilgenommen.

Sehr geehrte Damen und Herren,

in nächster Zeit suchen wir Sie, für das Entwerfen und Verfassen von Texten in der deutschen Sprache für drei Seiten unserer Homepage (www.intechcore.de : „Home“ (ca. 80 Wörter), „Über uns“ (ca. 120Wörter) und „Service“ (ca.550 Wörter)).

Für die Erhöhung des Bekannt­heits­grades und des Firmen­images haben wir uns im Rahmen der Entwick­lungs­stra­tegie entschieden, unsere vorhan­denen deutschen Texte durch neue zu ersetzen. Die neuen Texte sollen dazu dienen, den Kunden und anderen Inter­es­senten wichtige Infor­ma­tionen über das Unter­nehmen und unsere Leistungen zur Verfügung zu stellen. Das wird für den Image­aufbau der Firma ein wichtiger Beitrag sein. Die Texte müssen auch unter Einbe­haltung von SEO-Methoden geschrieben werden, also mit Rücksicht auf die Wirkung der Texte in den Suchma­schinen. Das heißt, die Benutzung der zutref­fenden Keywords muss den Suchma­schinen erleichtern, unsere Webseite besser zu aufzu­finden. Die Texte müssen auch komplett selbst verfasst sein. Vor der Bezahlung werden die Texte daher auf ihre Authen­ti­zität hin geprüft.

Bei Interesse Ihrer­seits senden Sie uns bitte in den nächsten Tagen ein unver­bind­liches Kosten­an­gebot dieses Auftrages bzw. Ihre Schätzung der Kostenhöhe und Zahlungs­kon­di­tionen, mögliches Anfangs­datum und ungefähre Dauer des Auftrages, damit wir eine Entscheidung treffen könnten. Uns würden auch Beispiele oder Hinweise auf Ihre bereits geschriebene Inhalte sehr interessieren.

Kontakt:
Andrej Petrenko
Intechcore GmbH
www.intechcore.de
a.petrenko@intechcore.de
info@intechcore.de

04. Dezember

Softwareentwickler(w/m) – Java SE/EE Vollzeit für eine Festan­stellung in München gesucht. Wir brauchen Verstärkung für unser kompe­tentes Teams bei der Erstellung von Konzep­tionen, Software­ent­wicklung und Support von Software­sys­temen im Rahmen komplexer Systemumgebungen.

Was wird erwartet:

  • Weiter­ent­wicklung
  • Fehler­be­hebung
  • Erstellung von techni­schen Spezifikationen

Ihr Profil:

  • Hochschul­ab­schluss in Infor­matik, Mathe­matik oder vergleich­barer Abschluss
  • Mindestens dreijährige Erfahrung in der Software­ent­wicklung mit Java-Projekten
  • Hervor­ra­gende Kennt­nisse in Java/J2EE, Servlets, JSP und XML/XSL sowie Java-Frame­works für Weban­wen­dungen und Web-Services. Insbe­sonders Swing Kenntnisse.
  • Eigen­ver­ant­wort­liches, kunden- und lösungs­ori­en­tiertes Denken
  • Teamfä­higkeit, Flexi­bi­lität und Organisationsstärke
  • kommu­ni­kative Fähigkeiten
  • Eigen­mo­ti­vation

Was kommt von uns

  • Eine inter­es­sante und abwechs­lungs­reiche Tätigkeit
  • Die Mitarbeit in inter­dis­zi­pli­nären Teams
  • Intensive Einar­beitung, inklusive interner Schulungen

Wir freuen uns auf Ihre Bewer­bungen mit frühstem Einstiegs­termin und Gehalts­vor­stellung per E‑Mail an jobs@intechcore.de an Hr. Smeltsov oder auf Ihre ausführ­lichen Bewer­bungs­un­ter­lagen an folgende Anschrift:

Intechcore GmbH
z.H. Hr. Smeltsov
Wamslerstr. 2
81829 München