JavaFX Validation

Ich wurde vor ein Paar Tagen darauf gebracht das JavaFx noch keine echte Validierung in der Oberfläche hat und das hat micht jetzt genervt das es nichts gibt.

Das Stichword Bean Validation ist dann gefallen.

Gesagt getan etwas analysiert und los gehts.

Meine Idee war das ich in meinem Oberflächenobjekt soetwas schreibe:

[codesyntax lang=“java“]package eu.thecreator.fxvalidation;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.beans.property.SimpleStringProperty;
import javax.validation.constraints.NotNull;

/**
*
* @author andre
*/
public class ToValidate {
@NotNull
private SimpleStringProperty zeichenkette = new SimpleStringProperty();[/codesyntax]

Das ist ja ganz schön aber da fehlt etwas. Die Verbindung zur UI also ist die erste Idee nicht viel Wert 🙁

Die 2. Idee ist im Controller es an die Elemente zu schreiben also so:

[codesyntax lang=“java“]package eu.thecreator.fxvalidation;

import javafx.scene.control.TextField;
import javax.validation.constraints.NotNull;

/**
*
* @author andre
*/
public class SampleController implements Initializable {

@NotNull
@FXML
TextField textField;[/codesyntax]

Das sah schon 1* gut aus da ich ja die UI Validieren möchte.

Jetzt kam der Knifflige Teil.

Reflection 🙂 der Controller musste Analysiert werden wo überall eine Annontation zu finden war. Das war leicht da alle Annontationen folgendes haben „javax.validation.Constraint“ somit von den Annontations die ich finde schauen ob diese selber Annontations haben und schon findet man alle.

Anschließend brauchen wir aber noch das „javafx.beans.property.Property“ das wir ja Validieren möchten. Hier habe ich einfach eine Map<Class,String> erstellt welche die Informationen beinhaltet.

[codesyntax lang=“java“]
private final static Map<Class<? extends Control>, String> controllHelper = new HashMap<>();

static {
//Das ist etwa Magie da wir bei den einzelnen UIs wissen müssen wogegen wir prüfen müssen
controllHelper.put(TextInputControl.class, „textProperty“);
}

/**
* Wir müssen immer an das korrekte Property des Controls unseren Listener
* hängen.
*
* @param control Control dessen Property wir suchen
* @return das Property des Controls das für die Validierung wichtig ist
*/
private Property installListenerOnControl(Control control) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Property p = null;
Class<? extends Control> current = control.getClass();
String txtProperty = null;
while (!current.isAssignableFrom(Object.class)) {
txtProperty = controllHelper.get(current);
if (txtProperty != null) {
break;
}
current = (Class<? extends Control>) current.getSuperclass();
}
if (txtProperty != null) {
Method callmethod = current.getDeclaredMethod(txtProperty, new Class[]{});
p = (Property) callmethod.invoke(control, new Object[0]);
}
i

f (p != null) {
p.addListener(this);
}
return p;
}[/codesyntax]

Jetzt musste man nur noch folgendes aufrufen:

[codesyntax lang=“java“] Set<ConstraintViolation<? extends Object>> validationMessages =
(Set<ConstraintViolation<? extends Object>>) validator.validateValue(validateHelper.controller.getClass(),
validateHelper.field.getName(), value, new Class[0]);
Somit hat man alle Fehler und kann sie Darstellen.

Hmm darstellen hier hatte ich nur gefunden wie man den Rahmen Rot macht. Hier mal mein Code:
[codesyntax lang=“java“]
private static class ValidateHelper {
protected final StringProperty validationMessage = new SimpleStringProperty();

private void addIconManager() {
try {
Image image = new Image(getClass().getResourceAsStream(„error.gif“));
ImageView iv = new ImageView(image);
iv.setScaleX(0.5);
iv.setScaleY(0.5);
iv.setScaleZ(0.5);

final Label l = new Label(null, iv);
//Nicht Managed da wir uns selber positionieren
l.setManaged(false);
//Am Anfang noch nicht sichtbar
l.setVisible(false);
Parent p = fxElement.getParent();
//Element immer gleich positionieren
l.layoutXProperty().bind(fxElement.layoutXProperty().add(fxElement.prefWidthProperty()).add(image.getWidth() / 2));
l.layoutYProperty().bind(fxElement.layoutYProperty());

l.setAlignment(Pos.CENTER_RIGHT);

//Tooltip anlegen und binden
l.setTooltip(new Tooltip());
l.getTooltip().textProperty().bind(validationMessage);
//Ausblenden wenn nicht mehr sichtbar
validationMessage.addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> ov, String t, String t1) {
l.setVisible(t1 != null && t1.length() > 0);
l.getTooltip().hide();
}
});
//Leider per Reflection in den Parent das Label indizieren
Method tmp = Parent.class.getDeclaredMethod(„getChildren“, new Class[0]);
boolean accessible = tmp.isAccessible();
tmp.setAccessible(true);
ObservableList<Node> list = (ObservableList<Node>) tmp.invoke(p, new Object[0]);
list.add(l);
tmp.setAccessible(accessible);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
throw new RuntimeException(ex);
}
}
}
[/codesyntax]
Damit ist man sehr Weit und so sah es dann aus:

Validator

Es gibt natürlich viele Annontations und gleich mein 2. Test scheiterte „@Email“ warum hat mich Stunden gekostet.

Hintergrund ist der das die Validatoren auf „CharSequence“ Funktionieren und das ist nicht von Object abgeleitet.

 

[codesyntax lang="java"]
public class EmailValidator implements ConstraintValidator<Email, CharSequence>{}
//Wird zu
public class EmailValidator implements ConstraintValidator<Email, Object> {}
[/codesyntax]

Das liegt daran da in der Validate Methode der Controller übergeben werden muss damit die Annontationen überprüft werden können. Leider ist der Code an der Stelle von Hibernate für die Validatoren sehr Blöd da man an dieser Stelle nicht eingreifen kann.
Und so sieht es jetzt aus:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert