4

Lets consider a sample code:

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

When I set a new value using a setValue() method, if the oldValue and newValue are the same, the event is not fired. Only when they differ.

An example:

  • I have a ListView<Element> binded with an ObservableList<Element> containing some "elements".
  • I can add more elements in a different place of my application.
  • There's a button "Start" which launches a procedure - it iterates throught the list and does some actions with every element.
  • A Procedure is a different class. It does some stuff with elements and also it contains SimpleIntegerPorperty - currentlyChosenElementIndex to indicate an index of the currently chosen element.

When the current element is being proceeded, I'd like the ListView to show that. Now, during the procedure, the GUI is blocked and the current element is selected on the ListView, while being proceeded. After the end of the procedure the application resets currentlyChosenElementIndex to zero and this is an index with I have my problem. When the procedure starts, the first element is not selected because the application setValue() to the same one that was previously.

Is there any way to change that?

  • 2
    A `ChangeListener` does what it says: it listens for *changes*. If the value isn't changed, it isn't notified. The fact that you want to respond when it hasn't actually changed value suggests your design is somehow not correct. Can you give an example of why you would need to do this? – James_D Apr 13 '17 at 13:25
  • If you can change the SimpleIntegerProperty to another type then look at my answer ;) – Flood2d Apr 13 '17 at 13:55
  • 1
    This just sounds like you are using `currentlyChosenElementIndex` improperly in some sense. If this represents the currently chosen index, then if it's equal to `0`, it means the element at index `0` is currently chosen. If *nothing* is currently chosen, you should set the value to a special value indicating that "nothing is currently chosen" (typically `-1` is used for this). So it seems this should be solved simply by initializing it to `-1` (and making sure your code handles this special case, if necessary). Or am I completely misunderstanding the problem? – James_D Apr 13 '17 at 13:55
  • Oh, so there exist negative numbers . Thanks, of course it solves my problem. I don't know why I thought I could set it only to the number within the ``ListView`` size. – Przemysław Długoszewski-Tamoń Apr 13 '17 at 14:05
  • Added an example to the answer. – James_D Apr 13 '17 at 14:34

3 Answers3

5

You cannot do this by simply using SimpleIntegerProperty class. But you can extend class and add the required functionality. Create a class like this

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

Then you can use it and be notified when setValue or set method is called

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

Will output

1
0
Flood2d
  • 1,338
  • 8
  • 9
1

If your Procedure's currentlyChosenElementIndex represents the index of the element currently being processed, then having it equal to 0 when no element is currently being processed essentially leaves your application in an inconsistent state. The usual convention for something that represents an index is to use -1 to represent "no value". So I think it would make more sense to initialize currentlyChosenElementIndex to -1, and reset it to -1 when the procedure is complete. (This would also be consistent with the selected index of the selection model when nothing is selected.)

This does mean you have to be careful when using that value, to avoid any ArrayIndexOutOfBoundsExceptions - i.e. you have to check for the special value and treat it separately.

Here's a 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);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
-1

I had a similar problem with DoubleProperty and solved it this way:

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