【问题标题】:How to make JavaFX Property Listener to fire an event even if the oldVaule and newValue are the same?即使 oldVaule 和 newValue 相同,如何使 JavaFX 属性侦听器触发事件​​?
【发布时间】:2017-04-13 13:18:41
【问题描述】:

让我们考虑一个示例代码:

SimpleIntegerProperty simpleIntegerProperty = new SimpleIntegerProperty(0);
simpleIntegerProperty.addListener((observable, oldValue, newValue) -> {
  // execution code when the event is fired.
});

当我使用setValue() 方法设置新值时,如果 oldValue 和 newValue 相同,则不会触发该事件。仅当它们不同时。

一个例子:

  • 我有一个ListView<Element> 与一个包含一些“元素”的ObservableList<Element> 绑定。
  • 我可以在应用程序的不同位置添加更多元素。
  • 有一个“开始”按钮可以启动一个程序 - 它 遍历列表并对每个元素执行一些操作。
  • Procedure 是一个不同的类。它对元素做了一些事情,还包含SimpleIntegerPorperty - currentlyChosenElementIndex 以指示当前选择的元素的索引。

在处理当前元素时,我希望 ListView 显示这一点。现在,在此过程中,GUI 被阻止并在ListView 上选择当前元素,同时继续进行。程序结束后,应用程序将currentlyChosenElementIndex 重置为零,这是我遇到问题的索引。过程开始时,第一个元素未被选中,因为应用程序 setValue() 与以前相同。

有什么办法可以改变吗?

【问题讨论】:

  • A ChangeListener 照它所说的去做:它监听变化。如果值未更改,则不会收到通知。您想要在它实际上没有改变值时做出响应的事实表明您的设计在某种程度上是不正确的。您能举例说明为什么需要这样做吗?
  • 如果您可以将 SimpleIntegerProperty 更改为另一种类型,请查看我的答案;)
  • 这听起来就像您在某种意义上不正确地使用了currentlyChosenElementIndex。如果这表示当前选择的索引,那么如果它等于0,则表示当前选择了索引0 处的元素。如果当前选择了 nothing,则应将该值设置为一个特殊值,指示“当前未选择任何内容”(通常使用-1)。因此,这似乎应该通过将其初始化为 -1 来解决(并确保您的代码处理这种特殊情况,如有必要)。还是我完全误解了这个问题?
  • 哦,原来存在负数。谢谢,当然它解决了我的问题。我不知道为什么我认为我只能将其设置为 ListView 大小内的数字。
  • 在答案中添加了一个示例。

标签: java javafx


【解决方案1】:

你不能通过简单地使用SimpleIntegerProperty 类来做到这一点。但是您可以扩展类并添加所需的功能。像这样创建一个类

public class NotifySetIntegerProperty extends SimpleIntegerProperty {
    private OnSetValueListener valueListener;

    public NotifySetIntegerProperty(int initialValue) {
        super(initialValue);
    }

    @Override
    public void set(int newValue) {
        super.set(newValue);
        if(valueListener!= null) {
            valueListener.onValueSet(newValue);
        }
    }

    public void setValueListener(OnSetValueListener valueListener) {
        this.valueListener = valueListener;
    }

    public interface OnSetValueListener {
        void onValueSet(int value);
    }
}

然后就可以使用了,当setValue或者set方法被调用时会得到通知

NotifySetIntegerProperty property = new NotifySetIntegerProperty(0);
property.setValueListener(new NotifySetIntegerProperty.OnSetValueListener() {
    @Override
    public void onValueSet(int value) {
        System.out.println(value);
    }
});
property.setValue(1);
property.setValue(0);

会输出

1
0

【讨论】:

    【解决方案2】:

    如果您的ProcedurecurrentlyChosenElementIndex 表示当前正在处理的元素的索引,那么当当前正在处理没有元素 时,使其等于0 基本上会使您的应用程序处于不一致的状态。表示索引的通常约定是使用-1 表示“无值”。所以我认为将currentlyChosenElementIndex初始化为-1,并在程序完成后将其重置为-1会更有意义。 (这也将与未选择任何内容时选择模型的选择索引一致。)

    这确实意味着您在使用该值时必须小心,以避免任何ArrayIndexOutOfBoundsExceptions - 即您必须检查特殊值并单独处理它。

    这是一个 SSCCE:

    import java.util.List;
    
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.beans.property.ReadOnlyIntegerProperty;
    import javafx.beans.property.ReadOnlyIntegerWrapper;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.ListView;
    import javafx.scene.control.TextArea;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class ProcessListElements extends Application {
    
        private int count = 0 ;
    
        @Override
        public void start(Stage primaryStage) {
            ListView<String> listView = new ListView<>();
            for (int i = 0 ; i < 10 ; i++) addElement(listView.getItems());
    
            Procedure procedure = new Procedure();
    
            Button startProcessButton = new Button("Start Process");
            Button addItemButton = new Button("Add item");
            Button deleteItemButton = new Button("Delete item");
    
            TextArea log = new TextArea();
    
            startProcessButton.setOnAction(e -> {
                log.clear();
                listView.requestFocus();
                new Thread(() -> procedure.process(listView.getItems())).start();
            });
    
            addItemButton.setOnAction(e -> addElement(listView.getItems()));
            deleteItemButton.setOnAction(e -> listView.getItems().remove(listView.getSelectionModel().getSelectedIndex()));
            deleteItemButton.disableProperty().bind(listView.getSelectionModel().selectedItemProperty().isNull());
    
            HBox controls = new HBox(5, startProcessButton, addItemButton, deleteItemButton);
            controls.setAlignment(Pos.CENTER);
            controls.setPadding(new Insets(5));
    
    
            BorderPane root = new BorderPane(listView, null, log, controls, null);
    
            procedure.currentlyChosenElementIndexProperty().addListener((obs, oldIndex, newIndex) -> {
                Platform.runLater(() -> 
                    listView.getSelectionModel().clearAndSelect(newIndex.intValue()));
            });
    
            procedure.currentlyChosenElementIndexProperty().addListener((obs, oldIndex, newIndex) -> {
                Platform.runLater(() -> {
                    controls.setDisable(newIndex.intValue() != Procedure.NO_ELEMENT);
                });
            });
    
            procedure.currentlyChosenElementIndexProperty().addListener((obs, oldIndex, newIndex) -> {
                if (oldIndex.intValue() != Procedure.NO_ELEMENT) {
                    log.appendText("Processing of element "+oldIndex.intValue()+" complete\n");
                }
                if (newIndex.intValue() != Procedure.NO_ELEMENT) {
                    log.appendText("Processing element "+newIndex.intValue()+" started\n");
                }
            });
    
    
            Scene scene = new Scene(root, 600, 600);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private void addElement(List<String> list) {
            count++ ;
            list.add("Item "+count);
        }
    
        public static class Procedure {
    
            private static final int NO_ELEMENT = - 1; 
    
            private final ReadOnlyIntegerWrapper currentlyChosenElementIndex = new ReadOnlyIntegerWrapper(NO_ELEMENT);
    
            public void process(List<?> items) {
                if (Platform.isFxApplicationThread()) {
                    throw new IllegalStateException("This method blocks and must not be executed on the FX Application Thread");
                }
                try {
                    for (int i = 0 ; i < items.size(); i++) {
                        currentlyChosenElementIndex.set(i);
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                currentlyChosenElementIndex.set(NO_ELEMENT);
            }
    
            public final ReadOnlyIntegerProperty currentlyChosenElementIndexProperty() {
                return this.currentlyChosenElementIndex.getReadOnlyProperty();
            }
    
    
            public final int getCurrentlyChosenElementIndex() {
                return this.currentlyChosenElementIndexProperty().get();
            }
    
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    【讨论】:

      【解决方案3】:

      我在 DoubleProperty 上遇到了类似的问题,并以这种方式解决了它:

      class DelicateSimpleDoubleProperty extends SimpleDoubleProperty{
                  @Override
                  public void set( double newValue ) {
                      if (get() == newValue){
                          super.set(newValue + 0.0000000000001);
                      }else {
                          super.set(newValue);
                      }
                  } 
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-01-05
        • 2020-09-06
        • 2015-12-18
        • 1970-01-01
        • 2013-05-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多