Reputation: 735
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:
ListView<Element>
binded with an ObservableList<Element>
containing some "elements".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?
Upvotes: 4
Views: 11587
Reputation: 186
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);
}
}
}
Upvotes: -1
Reputation: 209330
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 ArrayIndexOutOfBoundsException
s - 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);
}
}
Upvotes: 1
Reputation: 1358
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
Upvotes: 6