Displaying different ObservableList in a single TableView [JavaFX] - java

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

Related

Is there a way to change a Java property without firing a value changed event to it's listeners?

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.

ChangeListener in a TreeTableView CheckBoxTreeTableCell getting triggered many times every click

I have a simple model class Person and I use it to populate a TreeTableView. I want to have one column with a checkbox and I want to populate another TreeTableView with the data I check in the first table. Seems reasonable but my problem is the ChangeListener added to the BooleanProperty via the SetCellValueFactory is triggered many times, 4 to 8 times randomly (or so it seems random, I did not really test that).
Main class:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.stage.Stage;
public class ChangeListenerBug extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
// create the treeTableView and colums
TreeTableView<Person> ttv = new TreeTableView<Person>();
TreeTableColumn<Person, String> colName = new TreeTableColumn<>("Name");
TreeTableColumn<Person, Boolean> colSelected = new TreeTableColumn<>("Selected");
ttv.getColumns().add(colName);
ttv.getColumns().add(colSelected);
ttv.setShowRoot(false);
ttv.setEditable(true);
colSelected.setEditable(true);
// set the columns
colName.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
colSelected.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(colSelected));
colSelected.setCellValueFactory(cellData -> {
// binding the cell property with the model
BooleanProperty selected = cellData.getValue().getValue().selectedProperty();
// listening for a change in the property
selected.addListener((obs, oldVal, newVal) -> {
System.out.println(newVal);// WHY IS THIS GETTING CALLED MULTIPLE TIMES
});
return selected;
});
// creating treeItems to populate the treetableview
TreeItem<Person> rootTreeItem = new TreeItem<Person>();
rootTreeItem.getChildren().add(new TreeItem<Person>(new Person("Name 1")));
rootTreeItem.getChildren().add(new TreeItem<Person>(new Person("Name 2")));
ttv.setRoot(rootTreeItem);
// build and show the window
Group root = new Group();
root.getChildren().add(ttv);
stage.setScene(new Scene(root, 300, 300));
stage.show();
}
}
Person Class:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private StringProperty name;
private BooleanProperty selected;
public Person(String name) {
this.name = new SimpleStringProperty(name);
selected = new SimpleBooleanProperty(false);
}
public StringProperty nameProperty() {return name;}
public BooleanProperty selectedProperty() {return selected;}
public void setName(String name){this.name.set(name);}
public void setSelected(boolean selected){this.selected.set(selected);}
}
Output when I check a checkbox (same but false when I deselect it) :
true
true
true
true
true
cellData.getValue().getValue().selectedProperty() doesn’t create a new BooleanProperty. For any given Person object, it returns the same BooleanProperty again and again, and you keep adding yet another listener to that same BooleanProperty, again and again.
Do not add a listener in your cellValueFactory. Add the listener once to each Person object.
Since your tree is only one level deep (aside from the root), you can just iterate through them:
rootTreeItem.getChildren().forEach(item -> {
BooleanProperty selected = item.getValue().selectedProperty();
selected.addListener((obs, oldVal, newVal) -> {
System.out.println(newVal);
});
});
If the tree had multiple levels, you’d need a recursive method:
listenForSelection(rootTreeItem,
selected -> System.out.println(selected));
// ...
private void listenForSelection(TreeItem<Person> treeItem,
Consumer<Boolean> listener) {
BooleanProperty selected = treeItem.getValue().selectedProperty();
selected.addListener(
(obs, oldVal, newVal) -> listener.accept(newVal));
treeItem.getChildren().forEach(item -> listenForSelection(item, listener));
}
Taking a step back to address your underlying requirement:
I want to have one column with a checkbox and I want to populate another TreeTableView with the data I check in the first table
An alternative to manual listening (as in your attempt and the good answer provided by VGR) is to indirectly bind the content of the other treeTable to a filtered version of the first, as outlined by James_D.
Applied to your context (single level only):
public class TreeTableDriveTreeTableWithSelected extends Application {
public static void main(String[] args) {
launch(args);
}
FilteredList<TreeItem<Person>> targetItems;
#Override
public void start(Stage stage) throws Exception {
// create the treeTableView and colums
TreeTableView<Person> source = createTreeTable(true);
// creating treeItems to populate the treetableview
TreeItem<Person> sourceRoot = createRootItem();
source.setRoot(sourceRoot);
TreeTableView<Person> target = createTreeTable(false);
TreeItem<Person> targetRoot = new TreeItem<>();
target.setRoot(targetRoot);
// backing list for filteredList, configured to fire updates on change
// of selected
ObservableList<TreeItem<Person>> backingTargetItems = FXCollections.observableArrayList(
item -> new ObservableValue[] {item.getValue().selectedProperty()}
);
// fill backing list with items of source
// note: treeItems can't be shared across trees, so need to create with same value
sourceRoot.getChildren()
.forEach(tp -> backingTargetItems.add(new TreeItem<>(tp.getValue())));
// filter the backing list by its selected property
// this must be a strong reference, otherwise the binding is garbage-collected
targetItems = new FilteredList<>(backingTargetItems,
p -> p.getValue().isSelected()
);
// bind content of target root to filtered list
Bindings.bindContent(targetRoot.getChildren(), targetItems);
// build and show the window
HBox root = new HBox(10);
root.getChildren().addAll(source, target);
stage.setScene(new Scene(root, 300, 300));
stage.show();
}
protected TreeItem<Person> createRootItem() {
TreeItem<Person> rootTreeItem = new TreeItem<Person>();
rootTreeItem.getChildren()
.add(new TreeItem<Person>(new Person("Name 1")));
rootTreeItem.getChildren()
.add(new TreeItem<Person>(new Person("Name 2")));
return rootTreeItem;
}
protected TreeTableView<Person> createTreeTable(boolean withSelected) {
TreeTableView<Person> ttv = new TreeTableView<Person>();
TreeTableColumn<Person, String> colName = new TreeTableColumn<>("Name");
colName.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
ttv.getColumns().add(colName);
ttv.setShowRoot(false);
if (withSelected) {
ttv.setEditable(true);
// column editable is true by default
// colSelected.setEditable(true);
TreeTableColumn<Person, Boolean> colSelected = new TreeTableColumn<>(
"Selected");
ttv.getColumns().add(colSelected);
// set the columns
// updating the property
colSelected.setCellFactory(
CheckBoxTreeTableCell.forTreeTableColumn(colSelected));
colSelected.setCellValueFactory(new TreeItemPropertyValueFactory<>("selected"));
}
return ttv;
}
}

Transform an ObervableValue

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

How to convert an observableset to an observablelist

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

How to Get First Column Value on click in JavaFX TableView like JTable in swing?

I want to get First column value as we can achieve in Jtable using swing. below is my code and image for jtable.
String Table_Clicked = jTable1.getModel().getValueAt(row, 0).toString();
As you can see in image when i click to the Name column value eight it gives me first column value like 8. But I select Name Column
So how to achieve this in JavaFX with TableView Componenet.
I get the selected Value from the TableView as you can see in image below with the code.
tableview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object oldValue, Object newValue) {
if(tableview.getSelectionModel().getSelectedItem() != null)
{
TableViewSelectionModel selectionModel = tableview.getSelectionModel();
ObservableList selectedCells = selectionModel.getSelectedCells();
TablePosition tablePosition = (TablePosition) selectedCells.get(0);
Object val = tablePosition.getTableColumn().getCellData(newValue);
System.out.println("Selected value IS :" + val);
}
}
});
So I want the same in tableview First column data as we can get in Jtable? so how to get that NO value.. as using my above code I get the value of selected Cell that is eight print in console.. but I want to get First Column Value.. Help me to go thorough Ahead.
Thanks..
UPDATE FOR DATA FILLING CODE OF TABLEVIEW
PreparedStatement psd = (PreparedStatement) conn.prepareStatement("SELECT No,name FROM FieldMaster");
psd.execute();
ResultSet rs = psd.getResultSet();
for(int i=0 ; i<rs.getMetaData().getColumnCount(); i++){
//We are using non property style for making dynamic table
final int j = i;
namecol = new TableColumn(rs.getMetaData().getColumnName(i+1));
namecol.setCellValueFactory(new Callback<CellDataFeatures<ObservableList, String>, ObservableValue<String>>()
{
#Override
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param)
{
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
tableview.getColumns().addAll(namecol);
System.out.println("Column ["+i+"] ");
}
while(rs.next())
{
//Iterate Row
ObservableList<String> row = FXCollections.observableArrayList();
for(int i=1 ; i<=rs.getMetaData().getColumnCount(); i++)
{
//Iterate Column
row.add(rs.getString(i));
}
System.out.println("Row [1] added "+row );
data.add(row);
}
tableview.setItems(data);
conn.close();
Solution
You can retrieve the relevant field by calling the getter on the model object corresponding to the selected row.
In the code below the newValue.getId() call is the key.
Without Java 8 lambdas:
final Label selected = new Label();
table.getSelectionModel().selectedItemProperty().addListener(
new ChangeListener<IdentifiedName>() {
#Override
public void changed(
ObservableValue<? extends IdentifiedName> observable,
IdentifiedName oldValue,
IdentifiedName newValue
) {
if (newValue == null) {
selected.setText("");
return;
}
selected.setText("Selected Number: " + newValue.getId());
}
}
);
With Java 8 lambdas:
final Label selected = new Label();
table.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue == null) {
selected.setText("");
return;
}
selected.setText("Selected Number: " + newValue.getId());
}
);
Sample Code
import javafx.application.Application;
import javafx.collections.*;
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;
public class TableViewSample extends Application {
private TableView<IdentifiedName> table = new TableView<>();
private final ObservableList<IdentifiedName> data =
FXCollections.observableArrayList(
new IdentifiedName(3, "three"),
new IdentifiedName(4, "four"),
new IdentifiedName(7, "seven"),
new IdentifiedName(8, "eight"),
new IdentifiedName(9, "nineses")
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
TableColumn<IdentifiedName, Integer> idColumn = new TableColumn<>("No");
idColumn.setMinWidth(100);
idColumn.setCellValueFactory(
new PropertyValueFactory<>("id")
);
TableColumn<IdentifiedName, String> nameColumn = new TableColumn<>("Name");
nameColumn.setMinWidth(100);
nameColumn.setCellValueFactory(
new PropertyValueFactory<>("name")
);
table.setItems(data);
table.getColumns().setAll(idColumn, nameColumn);
table.setPrefHeight(180);
final Label selected = new Label();
table.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
selected.setText("");
return;
}
selected.setText("Selected Number: " + newValue.getId());
});
final VBox layout = new VBox(10);
layout.setPadding(new Insets(10));
layout.getChildren().addAll(table, selected);
VBox.setVgrow(table, Priority.ALWAYS);
stage.setScene(new Scene(layout));
stage.show();
}
public static class IdentifiedName {
private final int id;
private final String name;
private IdentifiedName(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
}
Answers to Additional Questions
check my updated question so I can't use this?
So in your update you can see that the type of each row's data is ObservableList<String>, whereas in my answer the type is IdentifiedName. To get the posted solution to work for your data type, the change is trivial. The equivalent of newValue.getId() would be newValue.get(0), to return the first item in your list for the selected row.
final Label selected = new Label();
table.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue == null) {
selected.setText("");
return;
}
selected.setText("Selected Number: " + newValue.get(0));
}
);
or is it possible to use identifiedname class? then how?
Yes you could, but you would have to make extensive changes to your database fetching code to load the data created into an IdentifiedName class rather than an ObservableList<String> and doing so would lose the generic nature of your database loading code.
I implement your code into my project I got ... java.lang.ClassCastException:
You need to setup the type of your Table and columns correctly for your data types, not the sample ones I provided for my use case.
Replace these types:
TableView<IdentifiedName>
TableColumn<IdentifiedName, Integer>
With these types:
TableView<ObservableList<String>>
TableColumn<ObservableList<String>, String>
Minor advice
I advise taking a refresher by reading up on the Java Generics Trail. Tables in JavaFX are pretty complicated in their use of generics, but using correct generics in your table code can make it easier to write (as long as you are using a good IDE which is good at guessing the generics when needed).
You also might want to provide an minimal, complete, tested and readable example with future questions of this type (not all questions). Construction of one could help you solve your issues quicker. Also, ensuring your code has consistent indentation makes it easier to read.
You have to take a look at the underlying ObservableList. The code that works for me was:
tableView.getItems().get(tableView.getSelectionModel().getSelectedIndex())
In my test that returns a Person object (POJO i wrote) which has to contain a get() method.
I can get first column value using below code:
tableview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object oldValue, Object newValue) {
//Check whether item is selected and set value of selected item to Label
if (tableview.getSelectionModel().getSelectedItem() != null) {
TableView.TableViewSelectionModel selectionModel = tableview.getSelectionModel();
ObservableList selectedCells = selectionModel.getSelectedCells();
TablePosition tablePosition = (TablePosition) selectedCells.get(0);
tablePosition.getTableView().getSelectionModel().getTableView().getId();
//gives you selected cell value..
Object GetSinglevalue = tablePosition.getTableColumn().getCellData(newValue);
getbothvalue = tableview.getSelectionModel().getSelectedItem().toString();
//gives you first column value..
Finalvaluetablerow = getbothvalue.toString().split(",")[0].substring(1);
System.out.println("The First column value of row.." + Finalvaluetablerow);
}
}
});
Thanks..

Categories

Resources