使用JavaFX 8创建一个空安全的BooleanBinding

时间:2016-04-26 13:26:35

标签: java javafx binding javafx-8

我需要帮助创建null - 安全BooleanBinding。它必须是null - 安全,因为我不能为模型中的所有属性提供默认值(一个原因:模型包含枚举)。我的第一种方法如下:

    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)

但是这种方法不起作用,因为评估完整的表达式导致NullPointerException(但是当提供属性时它正确地更新了按钮)。现在我正在尝试使用JavaFX binding and null values中建议的Bindings类,但我无法使其正常工作。这是我目前的做法:

    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);

但这不起作用,我不明白为什么。似乎modelProperty()的属性绑定在这里不起作用!您能否向我解释如何将第一个工作解决方案(至少没有null)转换为正确的null安全解决方案?

编辑2016-04-26:建议的解决方案不起作用,因此我创建了一个简单的完整工作示例:

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();
    }
}

单元测试失败并显示上面的代码(使用方法notWorkingBinding()),但使用方法partialWorkingBinding()。我做错了什么?

2 个答案:

答案 0 :(得分:3)

您为isNotCreatedBinding设置了计算功能,但您没有设置绑定的依赖关系。您需要添加mision作为依赖项:

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

修改

您需要侦听statusProperty而不是missionProperty,当missionProperty()。get()== null时,它不会与createBooleanBinding一起使用。

但您可以使用When绑定:

(正如问题中已经提到的NullPointerException

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

或更低级别的解决方案:

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

答案 1 :(得分:2)

Tomas Mikula的ReactFX framework (v 2.0)内置了此功能:

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);
        }



    }
}