I need to transform an ObservableValue.
For instance: I have an ObervableStringValue and I want to display the length of the string in a JavaFX controll. When I do: value.get().length() i just get an int, but i need an ObservableValue.
So i quickly wrote a wrapper my own:
/**
* Wraps an ObervableValue and offers a transformed value of it.
*
* #param <F> From type.
* #param <T> To type.
*/
public class TransformedValue<F, T> extends ObservableValueBase<T>
{
private final ObservableValue<F> original;
private final Function<F, T> function;
/**
* #param original ObservableValue to transform.
* #param function Transform function.
*/
public TransformedValue(ObservableValue<F> original, Function<F, T> function)
{
this.original = original;
this.function = function;
original.addListener((observable, oldValue, newValue) -> fireValueChangedEvent());
}
#Override
public T getValue()
{
return function.apply(original.getValue());
}
}
Usage:
new TransformedValue<>(someObservableStringValue, s -> s.length());
Here my questions:
Is my approach totaly stupid?
Is there a JavaFX way to do this?
Is there a third party library to do this?
Any suggestions to my code? (e.g. unregister listener)
Edit:
The example with String.length() was too simple, so here is the big story:
I have an ObservableList of sensors. Every sensor provides one ore more measurements, depending on type of sensor. The properties of a sensor are ObservabeValues. I display the sensors and their current measurements in a TreeTableView. Every sensor has its node and its measurements as subnodes. If will now focus on the timestamp column.
Initialisation of TreeTableColumns:
...
sensorTreeTimestamp.setCellValueFactory(cell -> cell.getValue().getValue().getTimestamp());
...
As there are totally different datatypes in the TreeTableView I have a own class SensorTreeValue to hold the data:
private static class SensorTreeValue
{
...
private final ObservableValue<String> timestamp;
...
It has one constructor to represent a sensor and one for a measurement:
private SensorTreeValue(Sensor sensor)
{
...
timestamp = new TransformedValue<>(sensor.getLastSeen(), (time) -> Utils.formatDateTime(time));
}
private SensorTreeValue(Sensor sensor, ValueType valueType)
{
...
timestamp = new TransformedValue<>(sensor.getLastMeasurement(), measure -> Utils.formatDateTime(measure.getTime()));
}
I know there is a asString(format) function. But this is not enough because I still need to get the time out of the measurement and I didn't find a format string to transform a date to a locale formatted string.
I also could place the logic into the CellValueFactory but there I would have to do a type check if its a Sensor or a Measurement.
Have a look at the EasyBind framework, which provides exactly this kind of functionality and whole lot more.
The example you suggest (creating an ObservableValue<Integer> representing the length of an ObservableValue<String>) is an example on the home page for the framework:
ObservableValue<String> text = ... ;
ObservableValue<Integer> textLength = EasyBind.map(text, String::length);
Use cases such as getting a "property of a property" are also shown on the project home page linked above.
Here's a way of doing it, but your structure is too complicated. I don't know how you're adding items to your table. If it's a TreeTableView<SensorTreeItem> I think it's too complicated. I would only go the trouble of a custom item like that if there were multiple levels of nodes. Like for a file/directory view, it's necessary.
I would make every line in the table a measurement but have the sensor name in the measurement class. Then when adding to the table you make a simple node for the sensor name if it doesn't already exist and add it to that node.
import java.util.Date;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FXTest extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) {
Label lbl1 = new Label();
Sensor sens1 = new Sensor();
SensorTreeValue stv1 = new SensorTreeValue(sens1);
lbl1.textProperty().bind(stv1.timeStamp.concat(" sens1"));
Label lbl2 = new Label();
Sensor sens2 = new Sensor();
SensorTreeValue stv2 = new SensorTreeValue(sens2, 0);
lbl2.textProperty().bind(stv2.timeStamp.concat(" sens2"));
Scene scene = new Scene(new VBox(5, lbl1, lbl2));
stage.setScene(scene);
stage.show();
Timeline timer = new Timeline(new KeyFrame(
javafx.util.Duration.millis(1000), ae -> {
sens1.lastSeen.set(System.currentTimeMillis());
sens2.lastMeasurement.get().time.set(System.currentTimeMillis());
}));
timer.setCycleCount(Animation.INDEFINITE);
timer.play();
}
private static class SensorTreeValue {
private final SimpleStringProperty timeStamp = new SimpleStringProperty();
private SensorTreeValue(Sensor sensor) {
//you can bind or set a property, not an ObsValue<T>
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastSeen.get()).toString();
},sensor.lastSeen));
}
private SensorTreeValue(Sensor sensor, int valueType) {
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastMeasurement.get().time.get()).toString();
},sensor.lastMeasurement.get().time));
}
}
private static class Sensor{
private SimpleLongProperty lastSeen = new SimpleLongProperty();
private SimpleObjectProperty<Measure> lastMeasurement = new SimpleObjectProperty<>(new Measure());
}
private static class Measure{
private SimpleLongProperty time = new SimpleLongProperty();
private SimpleLongProperty value = new SimpleLongProperty();
}
}
Related
I'm recently using JavaFX and would like to implement the Observer pattern by binding my stack update, with a ListView or TableView from JavaFX. However, I don't know what changes to make to my ComplexNumberStack class.
public class ComplexNumberStack extends Stack<ComplexNumber> {
private static ComplexNumberStack instance = null;
/** This method provide the unique instance of ComplexNumberStack. */
public static ComplexNumberStack getInstance() {
if (instance == null)
instance = new ComplexNumberStack();
return instance;
}
/**
* This method provides a secure implementation of massive pop of operandNumber operands from the stack
*
* #param operandNumber specifies the number of operands to be taken from the stack
* #return an iterator of complexNumber taken
*/
public Iterator<ComplexNumber> getOperand(int operandNumber) {
List<ComplexNumber> operands = new ArrayList<>();
for (int i = 0; i < operandNumber; i++) {
try {
operands.add(pop());
} catch (EmptyStackException e) {
Collections.reverse(operands);
operands.forEach(this::push);
throw new InvalidParameterException("There aren't enough operands into the stack");
}
}
return operands.iterator();
}
}
This example adds a wrapper class around your stack implementation which provides an ObservableList that can be:
Placed in a ListView AND
Respond to bindings (see the pop button disable property binding in the example app).
For it to work, the mutation operations (e.g. push/pop) must be called on the wrapper class rather than the underlying class.
There are more efficient ways of implementing this (e.g. don't subclass stack, instead implement the Deque interface and use an ObservableList directly as storage, extending ObservableListBase).
However, this is what I came up with that still kept your underlying class and it might be fine or easily adaptable for your purposes.
public record ComplexNumber(double real, double imaginary) {}
The underlying stack implementation is unchanged from the class in your question.
import java.security.InvalidParameterException;
import java.util.*;
public class ComplexNumberStack extends Stack<ComplexNumber> {
private static ComplexNumberStack instance = null;
/**
* This method provide an instance of a ComplexNumberStack.
*/
public static ComplexNumberStack getInstance() {
if (instance == null)
instance = new ComplexNumberStack();
return instance;
}
/**
* This method provides a secure implementation of massive pop of operandNumber operands from the stack
*
* #param operandNumber specifies the number of operands to be taken from the stack
* #return an iterator of complexNumber taken
*/
public Iterator<ComplexNumber> getOperand(int operandNumber) {
List<ComplexNumber> operands = new ArrayList<>();
for (int i = 0; i < operandNumber; i++) {
try {
operands.add(pop());
} catch (EmptyStackException e) {
Collections.reverse(operands);
operands.forEach(this::push);
throw new InvalidParameterException("There aren't enough operands into the stack");
}
}
return operands.iterator();
}
}
Provides observability for the stack.
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.EmptyStackException;
public class ObservedComplexNumberStack {
private final ObservableList<ComplexNumber> observableList;
public ObservedComplexNumberStack(ComplexNumberStack complexNumberStack) {
observableList = FXCollections.observableList(complexNumberStack);
}
public ComplexNumber pop() {
if (observableList.size() == 0) {
throw new EmptyStackException();
}
return observableList.remove(observableList.size() - 1);
}
public ComplexNumber push(ComplexNumber number) {
observableList.add(number);
return number;
}
public ObservableList<ComplexNumber> getObservableList() {
return FXCollections.unmodifiableObservableList(observableList);
}
}
Test application.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.concurrent.ThreadLocalRandom;
public class StackApplication extends Application {
#Override
public void start(Stage stage) {
ObservedComplexNumberStack stack = new ObservedComplexNumberStack(
ComplexNumberStack.getInstance()
);
ListView<ComplexNumber> listView = new ListView<>(stack.getObservableList());
listView.setPrefSize(80, 150);
listView.setCellFactory(param -> new ListCell<>() {
#Override
protected void updateItem(ComplexNumber item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
return;
}
setText(String.format("%.2f + %.2fi", item.real(), item.imaginary()));
}
});
Button push = new Button("Push");
push.setOnAction(e -> {
stack.push(randomNum());
scrollToLastItem(listView);
});
Button pop = new Button("Pop");
pop.setOnAction(e -> {
stack.pop();
scrollToLastItem(listView);
});
pop.disableProperty().bind(Bindings.isEmpty(listView.getItems()));
HBox controls = new HBox(10, push, pop);
VBox layout = new VBox(10, controls, listView);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private void scrollToLastItem(ListView<ComplexNumber> listView) {
if (listView.getItems().size() > 0) {
listView.scrollTo(listView.getItems().size() - 1);
}
}
private ComplexNumber randomNum() {
ThreadLocalRandom r = ThreadLocalRandom.current();
return new ComplexNumber(r.nextDouble(9), r.nextDouble(9));
}
public static void main(String[] args) {
launch();
}
}
Potential alteratives or improvements
hmm .. this looks a bit brittle - external code could change the stack without notifying the list (especially, since is a singleton and potential collaborators spread across the world
Yes, it's true, its buyer beware :-)
The alternate proposed solution of implementing Deque with operations directly on a backing observableList is probably preferred, but I'm not going to write that at this time (it would be quite a bit more work to do well).
The solution in this answer uses the FXCollections list wrapper, which, incidentally, on its own is another simple solution to this problem:
FXCollections.observableList(ComplexNumberStack.getInstance());
Though, it has some disadvantages:
Changes to the underlying stack will not be observed (also true of the solution in this answer).
You need to change the list to observe changes and the list won't have push/pop ops (unlike the solution in this answer, which does at least provide push/pop ops which will be observed).
If you are interested in how the JavaFX framework implementation wrapper works, you can see the code for ObservableListWrapper.
If you wished to, you could copy a version of ObservableListWrapper to your own package (you don't want to depend on com.sun code directly), then subclass it and adapt it to add your additional push/pop ops (as suggested by kleopatra in comments).
What I'm trying to do
I'm looking for a way to change a property, without a call to the listeners's changed method.
More specifically I'm trying to implement an undo/redo functionality. The way I've implemented it is as following, in an example with a BooleanProperty and a JavaFX CheckBox.
The selectedProperty of the CheckBox is changed by a mouse click.
A BooleanProperty (actually a JavaFX SimpleBooleanProperty) is changed because it is bound bidirectionally to the selectedProperty
The ChangeListener of the BooleanProperty registers this and adds a Command on the application's undoStack. The Command stores the property, the old and the new value.
The user clicks the undo button
Via the button the application takes that last Command from the stack and calls it's undo() method.
The undo() method changes the BooleanProperty back.
The ChangeListener registers this change again and creates a new Command
An endless cycle is created
My Hacky Solution
The way I did it is by passing the ChangeListener to the Command object. Then the undo() method first removes the ChangeListener, changes the BooleanProperty and then adds the ChangeListener again.
It feels wrong and hacky to pass the ChangeListener to the Command (in my actual implementation in the 3. step there are actually a few more classes between the ChangeListener and the Command which now all need to know about the ChangeListener)
My Question
Is this really the way to do it? Isn't there a way to change the property in step 6 and just tell it to not inform it's listeners? Or at least to get it's listeners?
There's no supported way of bypassing listeners, as you describe. You just need to build this logic into your undo/redo mechanism. The idea is basically to set a flag if you are performing an undo/redo, and not add the change to your stack if so.
Here's a very simple example: note this is not production quality - for example typing in a text control will add to the stack for every character change (keeping copies of the current text at each change). In real code, you should coalesce these changes together.
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
public class UndoManager {
private boolean performingUndoRedo = false ;
private Deque<Command<?>> undoStack = new LinkedList<>();
private Deque<Command<?>> redoStack = new LinkedList<>();
private Map<Property<?>, ChangeListener<?>> listeners = new HashMap<>();
public <T> void register(Property<T> property) {
// don't register properties multiple times:
if (listeners.containsKey(property)) {
return ;
}
// FIXME: should coalesce (some) changes on the same property, so, e.g. typing in a text
// control does not result in a separate command for each character
ChangeListener<? super T> listener = (obs, oldValue, newValue) -> {
if (! performingUndoRedo) {
Command<T> cmd = new Command<>(property, oldValue, newValue) ;
undoStack.addFirst(cmd);
}
};
property.addListener(listener);
listeners.put(property, listener);
}
public <T> void unregister(Property<T> property) {
listeners.remove(property);
}
public void undo() {
if (undoStack.isEmpty()) {
return ;
}
Command<?> command = undoStack.pop();
performingUndoRedo = true ;
command.undo();
redoStack.addFirst(command);
performingUndoRedo = false ;
}
public void redo() {
if (redoStack.isEmpty()) {
return ;
}
Command<?> command = redoStack.pop();
performingUndoRedo = true ;
command.redo();
undoStack.addFirst(command);
performingUndoRedo = false ;
}
private static class Command<T> {
private final Property<T> property ;
private final T oldValue ;
private final T newValue ;
public Command(Property<T> property, T oldValue, T newValue) {
super();
this.property = property;
this.oldValue = oldValue;
this.newValue = newValue;
}
private void undo() {
property.setValue(oldValue);
}
private void redo() {
property.setValue(newValue);
}
#Override
public String toString() {
return "property: "+property+", from: "+oldValue+", to: "+newValue ;
}
}
}
And here's a quick test harness:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class UndoExample extends Application {
#Override
public void start(Stage stage) throws Exception {
ComboBox<Color> textColor = new ComboBox<Color>();
textColor.getItems().addAll(Color.BLACK, Color.RED, Color.DARKGREEN, Color.BLUE);
textColor.setValue(Color.BLACK);
textColor.setCellFactory(lv -> new ColorCell());
textColor.setButtonCell(new ColorCell());
CheckBox italic = new CheckBox("Italic");
TextArea text = new TextArea();
updateStyle(text, textColor.getValue(), italic.isSelected());
ChangeListener<Object> listener = (obs, oldValue, newValue) ->
updateStyle(text, textColor.getValue(), italic.isSelected());
textColor.valueProperty().addListener(listener);
italic.selectedProperty().addListener(listener);
UndoManager undoMgr = new UndoManager();
undoMgr.register(textColor.valueProperty());
undoMgr.register(italic.selectedProperty());
undoMgr.register(text.textProperty());
Button undo = new Button("Undo");
Button redo = new Button("Redo");
undo.setOnAction(e -> undoMgr.undo());
redo.setOnAction(e -> undoMgr.redo());
HBox controls = new HBox(textColor, italic, undo, redo);
controls.setSpacing(5);
BorderPane root = new BorderPane(text);
root.setTop(controls);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private void updateStyle(TextArea text, Color textColor, boolean italic) {
StringBuilder style = new StringBuilder()
.append("-fx-text-fill: ")
.append(hexString(textColor))
.append(";")
.append("-fx-font: ");
if (italic) {
style.append("italic ");
}
style.append("13pt sans-serif ;");
text.setStyle(style.toString());
}
private String hexString(Color color) {
int r = (int) (color.getRed() * 255) ;
int g = (int) (color.getGreen() * 255) ;
int b = (int) (color.getBlue() * 255) ;
return String.format("#%02x%02x%02x", r, g, b);
}
private static class ColorCell extends ListCell<Color> {
private Rectangle rect = new Rectangle(25, 25);
#Override
protected void updateItem(Color color, boolean empty) {
super.updateItem(color, empty);
if (empty || color==null) {
setGraphic(null);
} else {
rect.setFill(color);
setGraphic(rect);
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
There is pretty much not a possibility to do this without "hacks"!
However, there is also a shorter solution, via using reflection:
/**
* Set the value of property without firing any change event.
* The value of property will be set via reflection.
* This property must be "Base" property such as {#link DoublePropertyBase}.
*
* #param property | Property to set!
* #param newValue | New value of property.
*/
public static <T> void setPropertyWithoutFiringEvent(Property<T> property, T newValue)
{
Class<?> cls = property.getClass();
while (cls != null) //While until helper variable is found
{
try
{
Field fieldH = cls.getDeclaredField("helper"), fieldV = cls.getDeclaredField("valid");
fieldH.setAccessible(true);
fieldV.setAccessible(true);
Object helper = fieldH.get(property), valid = fieldV.getBoolean(property); //Temporary values
fieldH.set(property, null); //Disabling ExpressionHelper by setting it on null;
property.setValue(newValue);
fieldH.set(property, helper); //Setting helper back!
fieldV.set(property, valid); //Important
return;
}
catch (Exception e)
{
cls = cls.getSuperclass(); //If not found go to super class of property next time!
}
}
System.err.println("Property " + property + " cant be set because variable \"helper\" was not found!");
}
This function temporarily disables ExpressionHelper what is an object responsible for firing change events, and then it will change the value of property and enable ExpressionHelper back! This will cause that one change will not be notified!
If the reflection is not friendly solution for you, then just use the solution above however this one is far shorter and simpler.
I have an ObservableSet<DoubleProperty> itemSet which holds the DoubleProperty prop1 for any number of instances of Item.
I want to create another DoubleProperty total that will reflect an up to date total of all DoubleProperty's in itemSet.
The double value of each DoubleProperty in the set can change independently. The total value would need to reflect those changes.
This is the Item class:
class Item {
DoubleProperty prop1;
DoubleProperty prop2;
public Item() {
this.prop1 = new SimpleDoubleProperty(1.0);
this.prop2 = new SimpleDoubleProperty(2.0);
itemSet.add(this.prop1);
}
}
This is a sort of global variable class...
class ItemValue {
private ItemValue itemValue = null;
ObservableSet<DoubleProperty> itemSet = FXCollections.observableSet();
DoubleProperty total;
private ItemValue() {
this.total = new SimpleDoubleProperty(0.0);
// create several Item's here...
itemSet.addListener((InvalidationListener) observable -> {
/*
Something which binds the total
I figure it will need to go here so that if new items get added the total will reflect that?
*/
});
}
public ItemValue get() {
if (itemValue == null) itemValue = new ItemValue();
return itemValue;
}
As far as I know there is no built in way to do this simply. However, there's a couple of ways you could do this. The most(?) efficient, but more complicated way would be to listen to the ObservableSet for additions/removals, observe any current DoubleProperty elements, and modify the total property yourself.
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
public class SomeClass {
private final ReadOnlyDoubleWrapper total = new ReadOnlyDoubleWrapper(this, "total");
private void setTotal(double total) { this.total.set(total); }
public final double getTotal() { return total.get(); }
public final ReadOnlyDoubleProperty totalProperty() { return total.getReadOnlyProperty(); }
private final ObservableSet<DoubleProperty> propertySet = FXCollections.observableSet();
private final ChangeListener<Number> elementListener = this::elementValueChanged;
private final WeakChangeListener<Number> weakElementListener =
new WeakChangeListener<>(elementListener);
public SomeClass() {
propertySet.addListener(this::propertySetChanged);
}
private void propertySetChanged(SetChangeListener.Change<? extends DoubleProperty> change) {
if (change.wasRemoved()) {
change.getElementRemoved().removeListener(weakElementListener);
setTotal(getTotal() - change.getElementRemoved().get());
}
if (change.wasAdded()) {
change.getElementAdded().addListener(weakElementListener);
setTotal(getTotal() + change.getElementAdded().get());
}
}
private void elementValueChanged(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
setTotal(getTotal() - oldValue.doubleValue() + newValue.doubleValue());
}
}
Here the SetChangeListener, whose value is a method reference to propertySetChanged, watches for any changes to the ObservableSet. When a DoubleProperty is added it adds said property's value to the current total. When a DoubleProperty is removed it subtracts said property's value from the current total. This listener also adds or removes a ChangeListener to or from the DoubleProperty when it is added or removed from the ObservableSet, respectively.
The ChangeListener, whose value is a method reference to elementValueChanged, updates the total property when the value of any DoubleProperty changes. It does this by first subtracting the old value and then adding the new value to the current total. It is actually the WeakChangeListener, which wraps the original ChangeListener, that is added or removed. This helps avoid potential memory leaks. Remember to maintain a strong reference to the original ChangeListener when using WeakChangeListener otherwise the original ChangeListener may be garbage collected too soon.
A second option is to rebuild a binding every time the ObservableSet is invalidated and then bind the total property to said binding.
import javafx.beans.Observable;
import javafx.beans.binding.DoubleExpression;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
public class SomeClass {
private final ReadOnlyDoubleWrapper total = new ReadOnlyDoubleWrapper(this, "total");
private void setTotal(double total) { this.total.set(total); }
public final double getTotal() { return total.get(); }
public final ReadOnlyDoubleProperty totalProperty() { return total.getReadOnlyProperty(); }
private final ObservableSet<DoubleProperty> propertySet = FXCollections.observableSet();
public SomeClass() {
propertySet.addListener(this::propertySetInvalidated);
}
private void propertySetInvalidated(Observable observable) {
if (propertySet.isEmpty()) {
total.unbind();
setTotal(0.0);
} else if (propertySet.size() == 1) {
total.bind(propertySet.iterator().next());
} else {
DoubleExpression sum = null;
for (DoubleProperty property : propertySet) {
sum = (sum != null) ? sum.add(property) : property;
}
total.bind(sum);
}
}
}
In this case we add an InvalidationListener to the ObservableSet. This listener will be invoked whenever an element(s) is added to or removed from the ObservableSet. When this happens 1 of 3 things will happen:
If the ObservableSet is now empty unbind the total property and set it to zero.
This is a special case to deal with no elements
If the ObservableSet now only contains a single element simply bind the total property to said element.
Another special case dealing with a single element. It stops us from creating unnecessary objects and preforming unnecessary computations that would happen if we just skipped to the third branch.
Otherwise create one big binding that calculates the sum and then bind total to that binding.
This branch uses DoubleExpression.add(ObservableNumberValue). The resulting DoubleBinding from that method call will update whenever one of the two observables change. This is reduced into a single DoubleExpression which we then bind the total property to.
This second option will be less efficient because it requires iterating the entire ObservableSet every time. It also potentially leads to a lot of DoubleBinding objects being created. However, you may find it simpler to code/understand and the performance hit may not be significant enough for your application.
I would give the itemSet a change listener and just recalculate the total with some method every time its called.
See https://stackoverflow.com/a/44141262/8729420.
I have a layout which basically consists of ChoiceBox and TableView. What I'd like to achieve is to display different data in TableView basing on the selected option in ChoiceBox.
What I have so far is:
MainController class:
private void configureChoiceBox() {
choiceBox.getSelectionModel().selectedIndexProperty().addListener((v, oldValue, newValue) -> {
if(newValue.intValue() == 0) {
workshopList.setItems(data.getPipeCableList());
}
else if(newValue.intValue() == 1) {
workshopList.setItems(data.getElementList());
}
});
}
Data class:
private ObservableList<PipeCable> pipeCableList;
private ObservableList<Element> elementList;
/**/
private ObservableList<StoredItem> displayedList;
public Data() {
this.pipeCableList = FXCollections.observableArrayList();
this.elementList = FXCollections.observableArrayList();
/**/
this.displayedList = FXCollections.observableArrayList();
}
public ObservableList<StoredItem> getPipeCableList() {
displayedList.removeAll(elementList);
displayedList.addAll(pipeCableList);
return displayedList;
}
public ObservableList<StoredItem> getElementList() {
displayedList.removeAll(pipeCableList);
displayedList.addAll(elementList);
return displayedList;
}
The problem is: when I change between options in ChoiceBox the data from both elementList and pipeCableList are mixed together and changing option in ChoiceBox has no result whatsoever.
What I'd like to achieve: be able to display different data which is contained in elementList and pipeCableList according to option selected with ChoiceBox. What's more, when one option is selected (one list is displayed) all new items which are added to this list will be visible on TableView.
EDIT(added missing info): PipeCable and Element extends StoredItem and TableView takes items of type StoredItem.
What's going wrong
Get rid of displayList, the TableView already has a reference to the list of items it displays, so just set that to the appropriate list. Currently your display list values are getting out of synch with your underlying data values.
Assumptions
I assume your TableView takes items of type StoredItem and both PipeCable and Element are also of type StoredItem (though inheritance or interface implementation).
How to fix it
Usually, you could just do:
tableView.setItems(data.getPipeCableList())
and the same for the element list as appropriate when it is chosen. But due to some limitations of Java generics that I can't seem to easily get around, that does not compile. If both the element list and pipe cable list were the same types (rather than children of a common parent type), it would be no issue.
To get around the generics issue, you can do:
tableView.getItems().setAll(data.getPipeCableList())
Which works fine, but does not keep the table view items in synch with the data items if the data items change.
To keep these in synch, you can do:
Bindings.bindContent(tableView.getItems(), data.getPipeCableList());
which is a bit ugly, but appears to work.
Full Sample App
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.stream.IntStream;
public class MultiListTable extends Application {
enum ItemType {
PipeCable, Element
}
#Override public void start(Stage stage) throws IOException {
TableView<StoredItem> tableView = new TableView<>();
TableColumn<StoredItem, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setPrefWidth(120);
tableView.getColumns().add(nameColumn);
Data data = new Data();
ChoiceBox<ItemType> choiceBox = new ChoiceBox<>(
FXCollections.observableArrayList(ItemType.values())
);
choiceBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
switch (newValue) {
case PipeCable:
Bindings.bindContent(tableView.getItems(), data.getPipeCableList());
break;
case Element:
Bindings.bindContent(tableView.getItems(), data.getElementList());
break;
}
});
choiceBox.getSelectionModel().select(0);
Button addPipe = new Button("Add Pipe");
addPipe.setOnAction(event -> data.getPipeCableList().add(
new PipeCable("Pipe " + (data.getPipeCableList().size() + 1))
));
IntStream.range(0, 3).forEach(i -> addPipe.fire());
Button addElement = new Button("Add Element");
addElement.setOnAction(event -> data.getElementList().add(
new Element("Element " + (data.getElementList().size() + 1))
));
IntStream.range(0, 2).forEach(i -> addElement.fire());
HBox controls = new HBox(10, choiceBox, addPipe, addElement);
VBox layout = new VBox(10, controls, tableView);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class Data {
private ObservableList<PipeCable> pipeCableList = FXCollections.observableArrayList();
private ObservableList<Element> elementList = FXCollections.observableArrayList();
ObservableList<PipeCable> getPipeCableList() {
return pipeCableList;
}
ObservableList<Element> getElementList() {
return elementList;
}
}
static public class StoredItem {
private final ReadOnlyStringWrapper name;
public StoredItem(String name) {
this.name = new ReadOnlyStringWrapper(name);
}
public String getName() {
return name.get();
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
}
static public class PipeCable extends StoredItem {
public PipeCable(String name) {
super(name);
}
}
static public class Element extends StoredItem {
public Element(String name) {
super(name);
}
}
}
I am trying to set items to a tableview but the setitems method expects an observablelist while I have an observableset in my model.The FXCollections utility class does not have a method for creating an observable list given an observable set.I tried casting but that caused a class cast exception (as expected).
Currently I am using this kind of code
new ObservableListWrapper<E>(new ArrayList<E>(pojo.getObservableSet()));
And I have some problems with it:
Will editing this in the table update the underlying set as expected?
Is it the 'right' way of doing this
So in short I need a style guide or best practice for converting between observable set and observable list because I expect to be doing this a lot when building a java fx GUI
Will editing this in the table update the underlying set as expected ?
No because, you are doing a copy of the set:
new ArrayList<E>(pojo.getObservableSet())
Is it the 'right' way of doing this ?
I think the right way is not doing that. Set are not List and vice versa. Both have specific contraints. For example, the lists are ordered and sets contains no duplicate elements.
Moreover, nor FXCollections neither Bindings provides this kind of stuff.
I would like the collection to remain as a set to enforce uniqueness
I guess you could write a custom ObservableList, for example the Parent::children have a similar behavior. It throws an IllegalArgumentException if a duplicate children is added. If you look at the source code, you will see that it is a VetoableListDecorator extension. You could write your own:
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import com.sun.javafx.collections.VetoableListDecorator;
public class CustomObservableList<E> extends VetoableListDecorator<E> {
public CustomObservableList(ObservableList<E> decorated) {
super(decorated);
}
#Override
protected void onProposedChange(List<E> toBeAdded, int... indexes) {
for (E e : toBeAdded) {
if (contains(e)) {
throw new IllegalArgumentException("Duplicament element added");
}
}
}
}
class Test {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Set<Object> set = new HashSet<Object>();
set.add(o1);
CustomObservableList<Object> list = new CustomObservableList<Object>(FXCollections.observableArrayList(set));
list.add(o2);
list.add(o1); // throw Exception
}
}
Just in Case someone stumbles over this question looking for a one-way to convert an ObservableSet into an ObservableList... I post my solution. It doesn't support feeding back data to the set (which in my opinion wouldn't be nice since TableView doesn't have a concept of not being able to change a value) but supports updates of the set and preserves the (in this case) sorted order.
package de.fluxparticle.lab;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Collections;
import java.util.Random;
import java.util.TreeSet;
import static javafx.collections.FXCollections.observableSet;
/**
* Created by sreinck on 23.01.17.
*/
public class Set2List extends Application {
private final ObservableSet<Integer> setModel = observableSet(new TreeSet<Integer>());
#Override
public void start(Stage primaryStage) throws Exception {
TableView<Integer> tableView = new TableView<>();
addColumn(tableView, "Number");
ObservableList<Integer> list = convertSetToList(setModel);
tableView.setItems(list);
Random rnd = new Random();
scheduleTask(Duration.millis(1000), () -> setModel.add(rnd.nextInt(10)));
primaryStage.setScene(new Scene(tableView, 800, 600));
primaryStage.setTitle("Set2List");
primaryStage.show();
}
private static void scheduleTask(Duration interval, Runnable task) {
Timeline timeline = new Timeline(new KeyFrame(interval, event -> task.run()));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
private static ObservableList<Integer> convertSetToList(ObservableSet<Integer> set) {
ObservableList<Integer> list = FXCollections.observableArrayList(set);
set.addListener((SetChangeListener<Integer>) change -> {
if (change.wasAdded()) {
Integer added = change.getElementAdded();
int idx = -Collections.binarySearch(list, added)-1;
list.add(idx, added);
} else {
Integer removed = change.getElementRemoved();
int idx = Collections.binarySearch(list, removed);
list.remove(idx);
}
});
return list;
}
private static void addColumn(TableView<Integer> tableView, String text) {
TableColumn<Integer, String> column = new TableColumn<>(text);
column.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().toString()));
tableView.getColumns().add(column);
}
public static void main(String[] args) {
launch(args);
}
}