2016-04-26 2 views
1

Ich brauche Hilfe beim Erstellen eines null -safe . Es muss null -safe sein, da ich keine Standardwerte für alle Attribute im Modell bereitstellen kann (ein Grund: das Modell enthält Aufzählungen). Mein erster Ansatz war wie folgt:Erstellen Sie eine null-sichere BooleanBinding mit JavaFX 8

executeButtonDisabled.bind(missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.CREATED))); 
    final BooleanBinding isNotExecutingBinding = missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.EXECUTING)); 
    completeButtonDisabled.bind(isNotExecutingBinding); 
    cancelButtonDisabled.bind(isNotExecutingBinding) 

Aber dieser Ansatz nicht funktioniert, weil der vollständige Ausdruck ausgewertet wird, welche in einem NullPointerException führt (aber es korrekt aktualisiert die Tasten, wenn eine Eigenschaft zur Verfügung gestellt). Jetzt versuche ich, die Bindings Klasse zu verwenden, wie in JavaFX binding and null values vorgeschlagen, aber ich kann es nicht arbeiten lassen. Hier ist mein derzeitiger Ansatz:

final BooleanBinding isNotCreatedBinding = Bindings.createBooleanBinding(
      () -> mission.isNull().getValue() 
        ? true 
        : missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.CREATED).getValue()); 
    final BooleanBinding isNotExecutingBinding = Bindings.createBooleanBinding(
      () -> mission.isNull().getValue() 
        ? true 
        : missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.EXECUTING).getValue()); 

    executeButtonDisabled.bind(isNotCreatedBinding); 
    completeButtonDisabled.bind(isNotExecutingBinding); 
    cancelButtonDisabled.bind(isNotExecutingBinding); 

Aber das funktioniert nicht und ich verstehe nicht warum. Es scheint, dass die Eigenschaft verbindlich für modelProperty() hier nicht funktioniert! Kannst du mir erklären, wie man die erste Arbeitslösung (zumindest ohne null) in eine richtige null -sichere Lösung umwandelt?

bearbeiten 2016.04.26: Die vorgeschlagene Lösung daher nicht erstellt voll funktionierendes Beispiel eine einfache I funktioniert:

Mission.java:

package de.florianwolters.example.javafx.bindings; 

import javafx.beans.property.ObjectProperty; 
import javafx.beans.property.SimpleObjectProperty; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 

public class Mission { 

    enum Status { 
     CREATED, 
     EXECUTING, 
     COMPLETED, 
     CANCELED; 
    } 

    private final StringProperty shortName = new SimpleStringProperty(); 

    private final ObjectProperty<Status> status = new SimpleObjectProperty<>(); 

    public Mission(final String shortName) { 
     this.setShortName(shortName); 
     this.setStatus(Status.CREATED); 
    } 

    public String getShortName() { 
     return shortNameProperty().get(); 
    } 

    public void setShortName(final String shortName) { 
     shortNameProperty().set(shortName); 
    } 

    public StringProperty shortNameProperty() { 
     return shortName; 
    } 

    public Status getStatus() { 
     return statusProperty().get(); 
    } 

    public void setStatus(final Status status) { 
     statusProperty().set(status); 
    } 

    public ObjectProperty<Status> statusProperty() { 
     return status; 
    } 
} 

MissionDetailsViewModel.java:

package de.florianwolters.example.javafx.bindings; 

import javafx.beans.binding.Bindings; 
import javafx.beans.binding.BooleanBinding; 
import javafx.beans.property.ObjectProperty; 
import javafx.beans.property.ReadOnlyBooleanProperty; 
import javafx.beans.property.ReadOnlyBooleanWrapper; 
import javafx.beans.property.SimpleObjectProperty; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

public final class MissionDetailsViewModel { 

    /** 
    * The logger used for logging in the `MissionDetailsViewModel` class. 
    */ 
    private static final Logger LOGGER = LoggerFactory.getLogger(
     MissionDetailsViewModel.class); 

    private ObjectProperty<Mission> mission = new SimpleObjectProperty<>(); 

    private final ReadOnlyBooleanWrapper executeButtonDisabled = new ReadOnlyBooleanWrapper(true); 

    private final ReadOnlyBooleanWrapper completeButtonDisabled = new ReadOnlyBooleanWrapper(true); 

    private final ReadOnlyBooleanWrapper cancelButtonDisabled = new ReadOnlyBooleanWrapper(true); 

    /** 
    * Constructs a `MissionDetailsViewModel`. 
    */ 
    public MissionDetailsViewModel(final ObjectProperty<Mission> mission) { 
     this.mission.bind(mission); 
//  partialWorkingBinding(); 
     notWorkingBinding(); 
    } 

    private void notWorkingBinding() { 
     final BooleanBinding isNotCreatedBinding = Bindings.createBooleanBinding(
      () -> missionProperty().isNull().get() 
       ? true 
       : missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.CREATED).get(), 
      missionProperty()); 

     final BooleanBinding isNotExecutingBinding = Bindings.createBooleanBinding(
      () -> mission.isNull().get() 
       ? true 
       : missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.EXECUTING).get(), 
      missionProperty()); 

     executeButtonDisabled.bind(isNotCreatedBinding); 
     completeButtonDisabled.bind(isNotExecutingBinding); 
     cancelButtonDisabled.bind(isNotExecutingBinding); 
    } 

    private void partialWorkingBinding() { 
     executeButtonDisabled.bind(missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.CREATED))); 
     final BooleanBinding isNotExecutingBinding = missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.EXECUTING)); 
     completeButtonDisabled.bind(isNotExecutingBinding); 
     cancelButtonDisabled.bind(isNotExecutingBinding); 
    } 

    public boolean isExecuteButtonDisabled() { 
     return executeButtonDisabledProperty().get(); 
    } 

    public ReadOnlyBooleanProperty executeButtonDisabledProperty() { 
     return executeButtonDisabled; 
    } 

    public boolean isCompleteButtonDisabled() { 
     return completeButtonDisabledProperty().get(); 
    } 

    public ReadOnlyBooleanProperty completeButtonDisabledProperty() { 
     return completeButtonDisabled; 
    } 

    public boolean isCancelButtonDisabled() { 
     return cancelButtonDisabledProperty().get(); 
    } 

    public ReadOnlyBooleanProperty cancelButtonDisabledProperty() { 
     return cancelButtonDisabled; 
    } 

    public Mission getMission() { 
     return missionProperty().get(); 
    } 

    public void setMission(final Mission mission) { 
     missionProperty().set(mission); 
    } 

    public ObjectProperty<Mission> missionProperty() { 
     return mission; 
    } 
} 

MissionDetailsViewModelTest.java:

package de.florianwolters.example.javafx.bindings; 

import static eu.lestard.assertj.javafx.api.Assertions.assertThat; 
import javafx.beans.property.SimpleObjectProperty; 

import org.junit.Before; 
import org.junit.Test; 

public final class MissionDetailsViewModelTest { 
    private Mission mission; 
    private MissionDetailsViewModel viewModel; 

    @Before 
    public void setUp() { 
     mission = new Mission("My Short Name"); 
    viewModel = new MissionDetailsViewModel(new SimpleObjectProperty<Mission>(mission)); 
    } 

    @Test 
    public void testInitialValues() { 
     assertThat(viewModel.executeButtonDisabledProperty()).isFalse(); 
     assertThat(viewModel.completeButtonDisabledProperty()).isTrue(); 
     assertThat(viewModel.cancelButtonDisabledProperty()).isTrue(); 
    } 

    @Test 
    public void testMissionStatusSetToExecuting() { 
     mission.setStatus(Mission.Status.EXECUTING); 
     assertThat(viewModel.executeButtonDisabledProperty()).isTrue(); 
     assertThat(viewModel.completeButtonDisabledProperty()).isFalse(); 
     assertThat(viewModel.cancelButtonDisabledProperty()).isFalse(); 
    } 

    @Test 
    public void testMissionStatusSetToCompleted() { 
     mission.setStatus(Mission.Status.COMPLETED); 
     assertThat(viewModel.executeButtonDisabledProperty()).isTrue(); 
     assertThat(viewModel.completeButtonDisabledProperty()).isTrue(); 
     assertThat(viewModel.cancelButtonDisabledProperty()).isTrue(); 
    } 

    @Test 
    public void testMissionStatusSetToCanceled() { 
     mission.setStatus(Mission.Status.CANCELED); 
     assertThat(viewModel.executeButtonDisabledProperty()).isTrue(); 
     assertThat(viewModel.completeButtonDisabledProperty()).isTrue(); 
     assertThat(viewModel.cancelButtonDisabledProperty()).isTrue(); 
    } 
} 

Der Komponententest schlägt mit dem obigen Code fehl (die Methode notWorkingBinding() wird verwendet), arbeitet jedoch mit der Methode partialWorkingBinding(). Was mache ich falsch?

Antwort

3

Sie haben die Berechnungsfunktion für isNotCreatedBinding eingerichtet, aber Sie haben die Abhängigkeiten für die Bindung nicht festgelegt. Sie müssen mision als Abhängigkeit hinzuzufügen.

Bindings.createBooleanBinding(
     () -> mission.isNull().getValue() 
       ? true 
       : missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.CREATED).getValue(), mission); 

EDIT

Sie müssen die statusProperty statt missionProperty hören, die mit createBooleanBinding wird nicht funktionieren, wenn missionProperty() get() == null .

Aber Sie können verwenden eine When Bindung:

(verursacht eine NullPointerException wie bereits in der Frage erwähnt)

BooleanBinding isNotCreatedBinding = new When(mission.isNotNull()).then(mission.get().statusProperty().isNotEqualTo(Mission.Status.CREATED)).otherwise(false); 

oder eine Low-Level-Lösung:

missionProperty().addListener((ov, m, m1) -> { 
      if (m1 != null) { 
       executeButtonDisabled.bind(m1.statusProperty().isNotEqualTo(Mission.Status.CREATED)); 
      }else { 
       executeButtonDisabled.unbind(); 
       executeButtonDisabled.set(false); 
      } 
     }); 
+0

Danke, aber das funktioniert nicht, beziehen Sie sich auf meine aktualisierte Frage.Ich habe ein vollständiges Beispiel mit einem Komponententest bereitgestellt. –

+0

Ich habe meine Anser – jns

+0

aktualisiert Danke, aber ich bekomme immer noch eine 'NullPointerException' mit der' When' Bindung. Das macht mich verrückt, ich denke, das ist ein sehr häufiger Anwendungsfall. Denken Sie an eine Auswahl in einer Hauptansicht (z. B. "ListView"), die anfänglich auf "null" gesetzt ist. Ich binde die "Mission" in der Detailansicht an die Auswahl in der Masteransicht, aber ich kann es nicht funktionieren lassen ... –

2

Tomas Mikulas ReactFX framework (v 2.0) hat diese Funktionalität eingebaut:

import org.reactfx.value.Val; 

import javafx.beans.property.BooleanProperty; 
import javafx.beans.property.ObjectProperty; 
import javafx.beans.property.SimpleBooleanProperty; 
import javafx.beans.property.SimpleObjectProperty; 

public class NestedBindingTest { 
    public static void main(String[] args) { 
     BooleanProperty disable = new SimpleBooleanProperty(); 
     disable.addListener((obs, wasDisabled, isNowDisabled) -> 
      System.out.println("disable: "+wasDisabled+" -> "+isNowDisabled)); 

     ObjectProperty<Item> item = new SimpleObjectProperty<>(); 

     Val<Item.Status> status = Val.flatMap(item, Item::statusProperty); 
     disable.bind(status.map(s -> s == Item.Status.PENDING).orElseConst(true)); 

     Item i = new Item(); 
     System.out.println("Setting item"); 
     item.set(i); 

     System.out.println("Setting item status to PENDING"); 
     i.setStatus(Item.Status.PENDING); 

     System.out.println("Setting item status to READY"); 
     i.setStatus(Item.Status.READY); 

     System.out.println("Setting item to null"); 
     item.set(null); 
    } 

    public static class Item { 
     public enum Status {PENDING, READY} 

     private final ObjectProperty<Status> status = new SimpleObjectProperty<>(); 

     public final ObjectProperty<Status> statusProperty() { 
      return this.status; 
     } 


     public final NestedBindingTest.Item.Status getStatus() { 
      return this.statusProperty().get(); 
     } 


     public final void setStatus(final NestedBindingTest.Item.Status status) { 
      this.statusProperty().set(status); 
     } 



    } 
}