JavaFX: DataBinding mit FXML

VerbindungDer letzte Beitrag meiner Blog-Serie “JavaFX mit FXML programmieren” umfasst das Thema DataBinding. Als DataBinding bezeichnet man die Verbindung zweier Variablen. Dabei kann die Verbindung unidirektional (in eine Richtung) oder bidirektional (in beider Richtungen) sein.
Allgemein werden DataBindings typischerweise im Viewmodel verwendet und in Form von Property-Variablen realisiert. Adam Bien hat dafür ein sehr kurzes und einfaches Tutorial geschrieben:

 

Adam Bien Ansatz

Wer Adam Bien kennt, weis dass er nicht um den heißen Brei rumerzählt. Taten statt Worte;-)
Hier ist seine Erklärung von DataBinding in Form eines JUnit Tests:

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;

public class StringDataBindingTest {
    
    @Test
    public void bindUnidirectionally(){
        String expected = "DukeFX";

        StringProperty from = new SimpleStringProperty();
        StringProperty to = new SimpleStringProperty();

        to.bind(from);

        from.set(expected);

        String actual = to.get();

        assertThat(actual,is(expected));
    }
} 

Was haben wir gelernt? Wenn man zwei Property-Variablen mit der Methode “.bind()” oder “.bindBidirectional()” verbindet, verweisen beide Variablen auf den gleichen Inhalt. Vergleichbar ist das Verhalten mit Pointern in C++:

int  expected = 5;
int* from, to;

from = &expected;
to   = &expected;

Der Unterschied zwischen “.bind()” und “.bindBidirectional()” ist, dass bei “x.bind(y)” eine “java.lang.RuntimeException: A bound value cannot be set.” Exception fliegt, wenn “x” geändert wird.

 

Was gibt es für Properties?

Neben unidirektional und bidirektional, muss man auch die richtige Property einsetzen. Properties sind eigentlich Interfaces, die nicht erzeugt werden können. Hierfür gibt es die Simple*-Klassen:

  • SimpleBooleanProperty
  • SimpleDoubleProperty
  • SimpleFloatProperty
  • SimpleIntegerProperty
  • SimpleListProperty
  • SimpleLongProperty
  • SimpleMapProperty
  • SimpleObjectProperty
  • SimpleSetProperty
  • SimpleStringProperty

 

Das Programm

Ihr habt die Theorie überstanden! Nun geht es in die Praxis – mein Beispiel-Programm besteht aus 3 Dateien:

  • Die Start-Klasse: zum initialisieren der JavaFX-Anwendung
  • Die Controller-Klasse: Handling der DataBindings
  • und die FXML-View: Definition der View

Am wichtigsten ist die Controller-Klasse!!!

 

Start-Klasse

Die Klasse

package de.axxg.fxml;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;


public class Start extends Application {

		public static void main(String[] args) {
		        launch(args);
		    }
		     
		    @Override
		    public void start(Stage primaryStage) throws IOException {
		    	// +++++++++++++++++++++++++++++++++++++++++++++
		    	// Layout
		    	// +++++++++++++++++++++++++++++++++++++++++++++
		    	
		    	// FXML-Datei laden!
		    	Parent root = FXMLLoader.load(getClass().getResource("maske.fxml"));
		        
				// Szene
				Scene scene = new Scene(root);
				
		    	// +++++++++++++++++++++++++++++++++++++++++++++
		    	// Stage konfigurieren
		    	// +++++++++++++++++++++++++++++++++++++++++++++

		    	// Titel setzen
				primaryStage.setTitle("AxxG - FXML Beispiel");
				
				// Szene setzen
				primaryStage.setScene(scene);
				primaryStage.sizeToScene();
				
				// Stage anzeigen
				primaryStage.show();
			}
	}

 

Die FXML-View

Die View

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>


<GridPane fx:id="grid" alignment="CENTER" hgap="10.0" vgap="10.0" xmlns:fx="http://javafx.com/fxml" fx:controller="de.axxg.fxml.Controller">
  <children>
    <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Hallo AxxG-Leser" GridPane.columnIndex="0" GridPane.rowIndex="0">
      <font>
        <Font name="Tahoma" size="20.0" />
      </font>
    </Text>
    <Label text="Vorname:" GridPane.columnIndex="0" GridPane.rowIndex="1" />
    <Label text="Nachname:" GridPane.columnIndex="0" GridPane.rowIndex="2" />
    <TextField fx:id="vornameTF" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
    <TextField fx:id="nachnameTF" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
    <HBox GridPane.columnIndex="1" GridPane.rowIndex="4">
      <children>
        <Button fx:id="addBtn" mnemonicParsing="false" text="eintragen" onAction="#go"/>
        <Button fx:id="okBtn" mnemonicParsing="false" text="Alle anzeigen" onMouseEntered="#enter" />
      </children>
    </HBox>
  </children>
  <padding>
    <Insets bottom="25.0" left="25.0" right="25.0" top="25.0" />
  </padding>
</GridPane>

 

Controller-Klasse

In Zeile 15 initialisiere ich die StringProperty mit dem Wert “Alexander”. Dieser Wert wird dann in der Oberfläche angezeigt.
In Zeile 23 bzw. 28 definiere ich die DataBindings. Drückt man jetzt auf einen der Buttons wird jeweils einer der Werte geändert und beide ausgegeben.

package de.axxg.fxml;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;

public class Controller {

	@FXML
	private TextField vornameTF;

	private StringProperty vorname = new SimpleStringProperty("Alexander");
	
	
	// Diese Methode wird nach dem Inject aufgerufen!
	public void initialize(){	
		// nur vorname kann vornameTF aktuallisieren
		// ansonsten kommt eine Exception
		// java.lang.RuntimeException: A bound value cannot be set.
		vornameTF.textProperty().bind(vorname);
		
		// oder
		
		// Beide Seiten aktuallisieren sich
		vornameTF.textProperty().bindBidirectional(vorname);
	}
	
	@FXML
	protected void go(ActionEvent event) {
		vorname.setValue("Peter");
		
		System.out.println("Stringprop.: " + vorname.getValue());
		System.out.println("Textfeld: " + vornameTF.getText());
	}

	@FXML
	protected void enter(MouseEvent event) {
		vornameTF.setText("Die Maus ist über den Button!");
		
		System.out.println("Stringprop.: " + vorname.getValue());
		System.out.println("Textfeld: " + vornameTF.getText());
	}
}

Die Ausgabe könnte so aussehen:

Stringprop.: Peter
Textfeld: Peter
Stringprop.: Die Maus ist über den Button!
Textfeld: Die Maus ist über den Button!
Stringprop.: Die Maus ist über den Button!
Textfeld: Die Maus ist über den Button!
Stringprop.: Peter
Textfeld: Peter

 

Download

Für Faule gibt es hier ein fertiges Projekt…

Java Version JDK 1.7.0_11 Inhalt gepacktes Projekt
JavaFX Version 2.2.4 Größe 4.018 KB (~4 MB)
IDE Eclipse IDE Version 3.8.1 Endung *.zip
Preis kostenlos Lizenz Creative Commons Lizenzvertrag

JavaFX DataBinding FXML

 

Blog-Serie

Dieser Beitrag ist ein Teil meiner Serie zu “JavaFX mit FXML programmieren“. In dieser Serie berichte, wie man in die Entwicklung mit FXML einsteigen und effektiv arbeiten kann. Weitere Beiträge sind:

 

Die Quellen und weiterführende Links

 

Copyright © 2013 AxxG – Alexander Gräsel



Kommentar verfassen