Updating the color of rows of a TableView consumes too much CPU - javafx-8

I am making an application that receives alerts.
An alert can have 4 possible states:
Unresolved_New_0
Unresolved_New_1
Unresolved_Old
Resolved
When an alert is received, it is in Unresolved_New_0 state. For 10 seconds, every 0.5s the state changes from Unresolved_New_0 to Unresolved_New_1 and vice-versa. Depending on state I, set a different background color to the table row (so that it flashes, for 10s).
When the 10s pass, the alert transitions to Unresolved_Old state. This causes its color to stop changing.
To implement this, I have a ScheduledThreadPoolExecutor that I use to submit an implementation of Runnable that for some time executes a runnable using Platform.runLater.
static class FxTask extends Runnable {
/**
*
* #param runnableDuring Runnable to be run while the task is active (run on the JavaFX application thread).
* #param runnableAfter Runnable to be run after the given duration is elapsed (run on the JavaFX application thread).
* #param duration Duration to run this task for.
* #param unit Time unit.
*/
public static FxTask create(final Runnable runnableDuring, final Runnable runnableAfter, final long duration, final TimeUnit unit) {
return new FxTask(runnableDuring, runnableAfter, duration, unit);
}
#Override
public void run() {
if (System.nanoTime() - mTimeStarted >= mTimeUnit.toNanos(mDuration) )
{
cancel();
Platform.runLater(mRunnableAfter);
}
else
Platform.runLater(mRunnableDuring);
}
private FxTask(final Runnable during, final Runnable after, final long duration, final TimeUnit unit) {
mRunnableDuring = during;
mRunnableAfter = after;
mDuration = duration;
mTimeUnit = unit;
mTimeStarted = System.nanoTime();
}
private final Runnable mRunnableDuring;
private final Runnable mRunnableAfter;
private final long mDuration;
private final TimeUnit mTimeUnit;
private final long mTimeStarted;
}
And I schedule Alerts using that Runnable as follows:
final Alert alert = new Alert(...);
scheduler.scheduleAtFixedRate(FxTask.create(
() -> {
switch (alert.alertStateProperty().get()) {
case UNRESOLVED_NEW_0:
alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_1);
refreshTable(mAlertsTable);
break;
case UNRESOLVED_NEW_1:
alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_0);
refreshTable(mAlertsTable);
break;
}
},
() -> { // This is run at the end
if (equalsAny(alert.alertStateProperty().get(), Alert.State.UNRESOLVED_NEW_0, SpreadAlert.State.UNRESOLVED_NEW_1)) {
alert.alertStateProperty().set(Alert.State.UNRESOLVED_OLD);
refreshTable(mAlertsTable);
}
},
10, TimeUnit.SECONDS), 0, 500, TimeUnit.MILLISECONDS
);
Note: alertStateProperty() is not shown on the TableView (it is not bound to any of its columns).
So in order to force JavaFx to redraw, I have to use refreshTable(), which unfortunately redraws the whole table (?).
public static <T> void refreshTable(final TableView<T> table) {
table.getColumns().get(0).setVisible(false);
table.getColumns().get(0).setVisible(true);
}
The problem is that even if I create a small number of Alerts at the same time, CPU usage goes very high: from 20% to 84% sometimes, averaging at about 40%. When the 10s pass for all alerts, CPU consumptions returns to 0%. If I comment out refreshTable(), CPU stays near 0%, which indicates that it is the problem.
Why is so much CPU being used? (I have 8 cores by the way).
Is there another way to redraw just a single row without redrawing the whole table?
I even tried a 'hacky' method -- changing all values of the Alerts and then resetting them back to cause JavaFx to detect the change and redraw, but CPU was again at the same levels.

Probably the most efficient way to change the color of a table row is to use a table row factory, have the table row it creates observe the appropriate property, and update one or more CSS PseudoClass states as appropriate. Then just define the colors in an external css file.
Here's a standalone version of the application you described. I just used a Timeline to perform the "flashing new alerts", which is less code; but use the executor as you have it if you prefer. The key idea here is the table row factory, and the pseudoclass state it manipulates by observing the property. On my system, if I fill the entire table with new (flashing) rows, the CPU doesn't exceed about 35% (percentage of one core), which seems perfectly acceptable.
Note that PseudoClass was introduced in Java 8. In earlier versions of JavaFX you can achieve the same by manipulating the style classes instead, though you have to be careful not to duplicate any style classes as they are stored as a List. Anecdotally, the pseudoclass approach is more efficient.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener.Change;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AlertTableDemo extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Alert> table = new TableView<>();
table.getColumns().add(createColumn("Name", Alert::nameProperty));
table.getColumns().add(createColumn("Value", Alert::valueProperty));
TableColumn<Alert, Alert> resolveCol =
createColumn("Resolve", ReadOnlyObjectWrapper<Alert>::new);
resolveCol.setCellFactory(this::createResolveCell);
table.getColumns().add(resolveCol);
// just need a wrapper really, don't need the atomicity...
AtomicInteger alertCount = new AtomicInteger();
Random rng = new Random();
Button newAlertButton = new Button("New Alert");
newAlertButton.setOnAction( event ->
table.getItems().add(new Alert("Alert "+alertCount.incrementAndGet(),
rng.nextInt(20)+1)));
// set psuedo-classes on table rows depending on alert state:
table.setRowFactory(tView -> {
TableRow<Alert> row = new TableRow<>();
ChangeListener<Alert.State> listener = (obs, oldState, newState) ->
updateTableRowPseudoClassState(row, row.getItem().getState());
row.itemProperty().addListener((obs, oldAlert, newAlert) -> {
if (oldAlert != null) {
oldAlert.stateProperty().removeListener(listener);
}
if (newAlert == null) {
clearTableRowPseudoClassState(row);
} else {
updateTableRowPseudoClassState(row, row.getItem().getState());
newAlert.stateProperty().addListener(listener);
}
});
return row ;
});
// flash new alerts:
table.getItems().addListener((Change<? extends Alert> change) -> {
while (change.next()) {
if (change.wasAdded()) {
List<? extends Alert> newAlerts =
new ArrayList<>(change.getAddedSubList());
flashAlerts(newAlerts);
}
}
});
HBox controls = new HBox(5, newAlertButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(table, null, null, controls, null);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(
getClass().getResource("alert-table.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private void flashAlerts(List<? extends Alert> newAlerts) {
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.5),
event -> {
for (Alert newAlert : newAlerts) {
if (newAlert.getState()==Alert.State.UNRESOLVED_NEW_0) {
newAlert.setState(Alert.State.UNRESOLVED_NEW_1);
} else if (newAlert.getState() == Alert.State.UNRESOLVED_NEW_1){
newAlert.setState(Alert.State.UNRESOLVED_NEW_0);
}
}
}));
timeline.setOnFinished(event -> {
for (Alert newAlert : newAlerts) {
if (newAlert.getState() != Alert.State.RESOLVED) {
newAlert.setState(Alert.State.UNRESOLVED_OLD);
}
}
});
timeline.setCycleCount(20);
timeline.play();
}
private void clearTableRowPseudoClassState(Node node) {
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"), false);
}
private void updateTableRowPseudoClassState(Node node, Alert.State state) {
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"),
state==Alert.State.UNRESOLVED_NEW_0);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"),
state==Alert.State.UNRESOLVED_NEW_1);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"),
state==Alert.State.UNRESOLVED_OLD);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"),
state==Alert.State.RESOLVED);
}
private TableCell<Alert, Alert> createResolveCell(TableColumn<Alert, Alert> col) {
TableCell<Alert, Alert> cell = new TableCell<>();
Button resolveButton = new Button("Resolve");
resolveButton.setOnAction(event ->
cell.getItem().setState(Alert.State.RESOLVED));
cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
cell.setAlignment(Pos.CENTER);
cell.graphicProperty().bind(
Bindings.when(cell.emptyProperty())
.then((Node)null)
.otherwise(resolveButton));
return cell ;
}
private <S, T> TableColumn<S, T> createColumn(String title,
Function<S, ObservableValue<T>> propertyMapper) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> propertyMapper.apply(cellData.getValue()));
col.setMinWidth(Region.USE_PREF_SIZE);
col.setPrefWidth(150);
return col ;
}
public static class Alert {
public enum State {
UNRESOLVED_NEW_0, UNRESOLVED_NEW_1, UNRESOLVED_OLD, RESOLVED
}
private final ObjectProperty<State> state = new SimpleObjectProperty<>();
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public final ObjectProperty<State> stateProperty() {
return this.state;
}
public final AlertTableDemo.Alert.State getState() {
return this.stateProperty().get();
}
public final void setState(final AlertTableDemo.Alert.State state) {
this.stateProperty().set(state);
}
public final StringProperty nameProperty() {
return this.name;
}
public final java.lang.String getName() {
return this.nameProperty().get();
}
public final void setName(final java.lang.String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
public Alert(String name, int value) {
setName(name);
setValue(value);
setState(State.UNRESOLVED_NEW_0);
}
}
public static void main(String[] args) {
launch(args);
}
}
alert-table.css:
.table-row-cell:resolved {
-fx-background: green ;
}
.table-row-cell:unresolved-old {
-fx-background: red ;
}
.table-row-cell:unresolved-new {
-fx-background: blue ;
}
.table-row-cell:unresolved-new-alt {
-fx-background: yellow ;
}

Related

Implement undo/redo button in JavaFX [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 months ago.
Improve this question
Ive been scrolling and scrolling and i dont seem to find any solution. I am making a simple java fx program where i draw different shapes. The problem is that i dont know how to implement an undo button. I have found that you can use stacks, command pattern, arraydeque and so on... But i cant fix it..
Here is some of the code.
public class shapeModel {
//private final List<Shape> undoHistory = new ArrayList<>();
//private final Deque<Deque<Shape>> redoHistory;
private final ObservableList<Shape> shapeList;
private final ObjectProperty<Color> colorPickerSelect;
private final int historyIndex = -1;
private ShapeType currentShape;
public shapeModel() {
//this.redoHistory = new ArrayDeque<>();
this.colorPickerSelect = new SimpleObjectProperty<>(Color.GREEN);
this.shapeList = FXCollections.observableArrayList(shape -> new Observable[]{
shape.colorProperty()
});
}
public ShapeType getCurrentShape() {
return currentShape;
}
public void setCurrentShape(ShapeType currentShape) {
this.currentShape = currentShape;
}
public void addShapes(Shape shape) {
if (!(shape == null))
this.shapeList.add(shape);
}
public Color getColorPickerSelect() {
return colorPickerSelect.get();
}
public ObjectProperty<Color> colorPickerSelectProperty() {
return colorPickerSelect;
}
public ObservableList<Shape> getShapeObservableList() {
return shapeList;
}
public void undo() {
}
}
Controller class:
public class HelloController {
public Button eraserButton;
public Button undoButton;
public BorderPane scenePane;
public ColorPicker myColorPicker;
public ChoiceBox<String> myChoiceBox;
public GraphicsContext context;
public Canvas canvas;
public shapeModel model;
public void initialize() {
context = canvas.getGraphicsContext2D();
model.getShapeObservableList().addListener((ListChangeListener<Shape>) e -> listChanged());
myColorPicker.valueProperty().bindBidirectional(model.colorPickerSelectProperty());
}
public HelloController() {
this.model = new shapeModel();
}
//
// private int count = 0;
//
// private final Shape[] shapes = new Shape[30];
public void canvasClicked(MouseEvent mouseEvent) {
Shape shape = Shape.creatingShapes(model.getCurrentShape(),mouseEvent.getX(),mouseEvent.getY(),50,50,myColorPicker.getValue());
model.addShapes(shape);
// redraw();
// shapes[count] = shape;
// count++;
// paintCanvas();
}
public void listChanged() {
var context = canvas.getGraphicsContext2D();
model.getShapeObservableList().forEach(s -> s.draw(context));
}
public void undoClicked() {
}
// public void redo() {
//
// if(historyIndex < model.getShapeObservableList().size()-1) {
// historyIndex++;
// model.getShapeObservableList(),(historyIndex).
// }
//
// }
public void handleClick() {
FileChooser chooser = new FileChooser();
File file = chooser.showSaveDialog(scenePane.getScene().getWindow());
}
public void onCircleClicked(ActionEvent e) {
model.setCurrentShape(ShapeType.CIRCLE);
}
public void onRectangleClicked(ActionEvent e) {
model.setCurrentShape(ShapeType.RECTANGLE);
}
public void exitAction(ActionEvent actionEvent) {
Platform.exit();
}
public void eraser() {
context.setFill(Color.WHITE);
context.fillRect(0,0,canvas.getWidth(), canvas.getHeight());
// context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
}
}
Maybe this can help. This is my first time attempting something like this. The experts may have a better, more efficient way of doing this.
My idea was to keep up with a List of WritableImages as you draw. Use that list to go forward and backward through history.
Main
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundImage;
import javafx.scene.layout.BackgroundPosition;
import javafx.scene.layout.BackgroundRepeat;
import javafx.scene.layout.BackgroundSize;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
/**
* Code from: #web http://java-buddy.blogspot.com/
*/
public class App extends Application {
Canvas canvas = new Canvas(400, 400);
StackPane spCanvasHolder = new StackPane(canvas);
List<WritableImage> history = new ArrayList();
AtomicInteger currentlyDisplaying = new AtomicInteger();
#Override
public void start(Stage primaryStage) {
final GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
initDraw(graphicsContext);
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (MouseEvent event) ->
{
graphicsContext.beginPath();
graphicsContext.moveTo(event.getX(), event.getY());
graphicsContext.stroke();
history.add(spCanvasHolder.snapshot(new SnapshotParameters(), null));
System.out.println("Current Display: " + currentlyDisplaying.incrementAndGet());
});
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, (MouseEvent event) ->
{
graphicsContext.lineTo(event.getX(), event.getY());
graphicsContext.stroke();
history.add(spCanvasHolder.snapshot(new SnapshotParameters(), null));
System.out.println("Current Display: " + currentlyDisplaying.incrementAndGet());
});
canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, (MouseEvent event) ->
{
System.out.println("History size: " + history.size());
});
Button btnForwardHistory = new Button(">");
btnForwardHistory.setOnAction((t) ->
{
if(currentlyDisplaying.get() < history.size())
{
File file = new File("CanvasTemp.png");
try
{
canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
ImageIO.write(SwingFXUtils.fromFXImage(history.get(currentlyDisplaying.incrementAndGet()), null), "png", file);
BackgroundImage bI = new BackgroundImage(new Image(new FileInputStream(file)), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, BackgroundSize.DEFAULT);
spCanvasHolder.setBackground(new Background(bI));
}
catch (Exception s) {
System.out.println(s.toString());
}
}
});
Button btnBackwardHistory = new Button("<");
btnBackwardHistory.setOnAction((t) ->
{
System.out.println("Current Display: " + currentlyDisplaying.get());
if(currentlyDisplaying.get() > 0)
{
File file = new File("CanvasTemp.png");
try
{
canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
ImageIO.write(SwingFXUtils.fromFXImage(history.get(currentlyDisplaying.decrementAndGet()), null), "png", file);
BackgroundImage bI = new BackgroundImage(new Image(new FileInputStream(file)), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, BackgroundSize.DEFAULT);
spCanvasHolder.setBackground(new Background(bI));
}
catch (Exception s) {
System.out.println(s.toString());
}
}
});
VBox vbHistoryControls = new VBox(btnBackwardHistory, btnForwardHistory);
StackPane root = new StackPane();
root.getChildren().add(new HBox(vbHistoryControls, spCanvasHolder));
Scene scene = new Scene(root);
primaryStage.setTitle("java-buddy.blogspot.com");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void initDraw(GraphicsContext gc){
double canvasWidth = gc.getCanvas().getWidth();
double canvasHeight = gc.getCanvas().getHeight();
gc.setFill(Color.LIGHTGRAY);
gc.setStroke(Color.BLACK);
gc.setLineWidth(5);
gc.fill();
gc.strokeRect(
0, //x of the upper left corner
0, //y of the upper left corner
canvasWidth, //width of the rectangle
canvasHeight); //height of the rectangle
gc.setFill(Color.RED);
gc.setStroke(Color.BLUE);
gc.setLineWidth(1);
}
}
module-info
module com.mycompany.javafxsimpletest {
requires javafx.controls;
requires javafx.swing;
exports com.mycompany.javafxsimpletest;
}
Output
I would not use this code in a production app, but maybe the ideas can get you in the right direction.
This example is based on the UndoFX framework:
https://github.com/FXMisc/UndoFX
I offer up the example without a lot of explanation. You could study and learn from it or ignore it. It isn't going to do exactly what you want, but there might be some useful ideas from it for you.
The example:
Uses the scene graph instead of a Canvas.
Only adds shapes to a pane.
Provides the ability to remove the added shapes and add them back again.
The example is probably overcomplicated for the limited functionality it provides but might be structured in such a way that it is more easily adaptable to adding additional functionality.
The example does not demonstrate manipulating shapes (such as selecting them, changing their border and fill colors, changing their sizes, etc) and undoing or redoing such changes. But if you wanted to extend functionality for that, you could use the app as a basis and look into the UndoFX demo app which shows how to apply undo and redo to multiple properties on a shape. Depending on the functionality added, this could be quite a difficult task as generic undo/redo across a wide range of functionality is inherently difficult (IMO).
com/example/shapechanger/ShapeChangerApp.java
package com.example.shapechanger;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.reactfx.EventSource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class ShapeChangerApp extends Application {
#Override
public void start(Stage stage) {
Model model = new Model();
EventSource<ShapeChange> changes = new EventSource<>();
UndoManager<ShapeChange> undoManager = new UndoProvider(changes).getUndoManager();
Ribbon ribbon = new Ribbon(model, undoManager);
DrawingPane drawingPane = new DrawingPane(model, changes);
ScrollPane scrollPane = new ScrollPane(drawingPane);
scrollPane.setPrefSize(300, 300);
VBox layout = new VBox(ribbon, scrollPane);
VBox.setVgrow(scrollPane, Priority.ALWAYS);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
class DrawingPane extends Pane {
private static final double DRAWING_PANE_SIZE = 1_000;
private final Model model;
private final EventSource<ShapeChange> changes;
private final ShapeFactory shapeFactory = new ShapeFactory();
public DrawingPane(Model model, EventSource<ShapeChange> changes) {
this.model = model;
this.changes = changes;
setOnMouseClicked(e -> addShape(e.getX(), e.getY()));
setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
setPrefSize(DRAWING_PANE_SIZE, DRAWING_PANE_SIZE);
setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
}
private void addShape(double x, double y) {
DrawableShape drawableShape = model.selectedShapeProperty().get();
Shape shape = shapeFactory.create(
drawableShape, x, y
);
ShapeChange shapeChange = new ShapeChange(
changes, this, shape, true
);
shapeChange.apply();
}
}
class Model {
private final ObjectProperty<DrawableShape> selectedShape = new SimpleObjectProperty<>();
public ObjectProperty<DrawableShape> selectedShapeProperty() {
return selectedShape;
}
}
class Ribbon extends ToolBar {
public Ribbon(Model model, UndoManager<ShapeChange> undoManager) {
UndoControls undoControls = new UndoControls(undoManager);
ShapeSelector shapeSelector = new ShapeSelector();
model.selectedShapeProperty().bindBidirectional(
shapeSelector.selectedShapeProperty()
);
getItems().addAll(undoControls.getControls());
getItems().add(createSpacer());
getItems().addAll(shapeSelector.getControls());
}
private Node createSpacer() {
Pane spacer = new Pane();
spacer.setPrefWidth(10);
return spacer;
}
}
class UndoProvider {
private final UndoManager<ShapeChange> undoManager;
public UndoProvider(EventSource<ShapeChange> changes) {
undoManager = UndoManagerFactory.unlimitedHistorySingleChangeUM(
changes,
ShapeChange::invert,
ShapeChange::apply
);
}
public UndoManager<ShapeChange> getUndoManager() {
return undoManager;
}
}
class ShapeChange {
private final EventSource<ShapeChange> changes;
private final DrawingPane drawingPane;
private final Shape shape;
private final boolean adding;
public ShapeChange(EventSource<ShapeChange> changes, DrawingPane drawingPane, Shape shape, boolean adding) {
this.changes = changes;
this.drawingPane = drawingPane;
this.shape = shape;
this.adding = adding;
}
public void apply() {
if (adding) {
drawingPane.getChildren().add(shape);
} else {
drawingPane.getChildren().remove(shape);
}
changes.push(this);
}
public ShapeChange invert() {
return new ShapeChange(
this.changes,
this.drawingPane,
this.shape,
!adding
);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShapeChange that = (ShapeChange) o;
return adding == that.adding && Objects.equals(changes, that.changes) && Objects.equals(drawingPane, that.drawingPane) && Objects.equals(shape, that.shape);
}
#Override
public int hashCode() {
return Objects.hash(changes, drawingPane, shape, adding);
}
}
enum DrawableShape {
SQUARE, CIRCLE
}
class UndoControls {
private final Button undo = new Button("<");
private final Button redo = new Button(">");
public UndoControls(UndoManager<ShapeChange> undoManager) {
undo.disableProperty().bind(undoManager.undoAvailableProperty().map(x -> !x));
redo.disableProperty().bind(undoManager.redoAvailableProperty().map(x -> !x));
undo.setOnAction(evt -> undoManager.undo());
redo.setOnAction(evt -> undoManager.redo());
}
public List<Node> getControls() {
return List.of(undo, redo);
}
}
class ShapeSelector {
private final ObjectProperty<DrawableShape> selectedShape = new SimpleObjectProperty<>(DrawableShape.SQUARE);
private final RadioButton squareSelector = looksLikeToggleButton(new RadioButton());
private final RadioButton circleSelector = looksLikeToggleButton(new RadioButton());
public ShapeSelector() {
ShapeFactory shapeFactory = new ShapeFactory();
Rectangle square = shapeFactory.createSquare(0, 0, Color.LIGHTGRAY);
Circle circle = shapeFactory.createCircle(0, 0, Color.LIGHTGRAY);
ToggleGroup toggleGroup = new ToggleGroup();
squareSelector.setGraphic(square);
squareSelector.setToggleGroup(toggleGroup);
squareSelector.setSelected(true);
circleSelector.setGraphic(circle);
circleSelector.setToggleGroup(toggleGroup);
Map<Toggle,DrawableShape> shapeMap = Map.of(
squareSelector, DrawableShape.SQUARE,
circleSelector, DrawableShape.CIRCLE
);
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) ->
selectedShape.set(
shapeMap.get(newValue)
)
);
}
public ObjectProperty<DrawableShape> selectedShapeProperty() {
return selectedShape;
}
public List<Node> getControls() {
return List.of(squareSelector, circleSelector);
}
private RadioButton looksLikeToggleButton(RadioButton radioButton) {
radioButton.getStyleClass().remove("radio-button");
radioButton.getStyleClass().add("toggle-button");
return radioButton;
}
}
class ShapeFactory {
private static final double BASE_SIZE = 20;
private static final double STROKE_WIDTH = 3;
private static final List<Color> palette = List.of(
Color.LIGHTBLUE,
Color.LIGHTGREEN,
Color.LIGHTCORAL,
Color.LIGHTGOLDENRODYELLOW,
Color.LIGHTPINK,
Color.LIGHTSALMON,
Color.LIGHTSEAGREEN,
Color.LIGHTYELLOW
);
private int colorIdx = 0;
public Rectangle createSquare(double x, double y, Color color) {
Rectangle square = new Rectangle(BASE_SIZE, BASE_SIZE);
square.setFill(color);
square.setStroke(color.darker());
square.setStrokeWidth(STROKE_WIDTH);
square.relocate(x, y);
return square;
}
public Circle createCircle(double x, double y, Color color) {
Circle circle = new Circle(BASE_SIZE / 2);
circle.setFill(color);
circle.setStroke(color.darker());
circle.setStrokeWidth(STROKE_WIDTH);
circle.setCenterX(x);
circle.setCenterY(y);
return circle;
}
public Shape create(DrawableShape drawableShape, double x, double y) {
return switch (drawableShape) {
case SQUARE -> createSquare(x, y, nextColor());
case CIRCLE -> createCircle(x, y, nextColor());
};
}
private Color nextColor() {
Color chosenColor = palette.get(colorIdx);
colorIdx = (colorIdx + 1) % palette.size();
return chosenColor;
}
}
module-info.java
module com.example.shapechanger {
requires javafx.controls;
requires org.fxmisc.undo;
requires reactfx;
exports com.example.shapechanger;
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ShapeChanger</artifactId>
<version>1.0-SNAPSHOT</version>
<name>ShapeChanger</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>19</version>
</dependency>
<dependency>
<groupId>org.fxmisc.undo</groupId>
<artifactId>undofx</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>19</source>
<target>19</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>

Showing contact from the contact loader Manager

I am showing the contact list from the contact table but not able to get the phone number using the same on RecyclerView.My code is -
package com.oodles.oodlesapprtc;
import android.annotation.SuppressLint;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import java.util.ArrayList;
/**
* Created by ankita on 13/4/17.
*/
public class LoginUserActivity extends AppCompatActivity {
public static final int CONTACT_LOADER_ID = 78;
RecyclerView loginUserRecycler;
ArrayList<ContactBeans> contactBeanses;
String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME +
" COLLATE LOCALIZED ASC";
private static final String[] PROJECTION2 = {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, ContactsContract.Contacts.PHOTO_URI,ContactsContract.Contacts.HAS_PHONE_NUMBER
};
private static final String[] PROJECTION3 = {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, ContactsContract.Contacts.PHOTO_URI, ContactsContract.Contacts.LOOKUP_KEY
};
private static final String[] PROJECTION = {
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY, ContactsContract.CommonDataKinds.Phone.NUMBER
};
// private static final String[] PROJECTION1 = {
// ContactsContract.Data.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY,
// ContactsContract.CommonDataKinds.Phone.NUMBER
// };
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_user_activity);
initViews();
}
private void initViews() {
getLoaderManager().initLoader(0, null, contactLoaderManager);
initRecycler();
}
private void initRecycler() {
loginUserRecycler = (RecyclerView) findViewById(R.id.loginUserRecycler);
loginUserRecycler.setLayoutManager(new LinearLayoutManager(this));
loginUserRecycler.setItemAnimator(new DefaultItemAnimator());
}
LoaderManager.LoaderCallbacks<Cursor> contactLoaderManager = new LoaderManager.LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// String[] projectionFields = new String[]{ContactsContract.Contacts._ID,
// ContactsContract.Contacts.DISPLAY_NAME,ContactsContract.PhoneLookup.NORMALIZED_NUMBER,
// ContactsContract.Contacts.PHOTO_URI};
// Construct the loader
// Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
// ContactsContract.Contacts.CONTENT_URI
// Uri lookupUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
// Uri.encode(phoneNumber));
CursorLoader cursorLoader = new CursorLoader(LoginUserActivity.this,
ContactsContract.Contacts.CONTENT_URI, // URI
PROJECTION2, // projection fields
null, // the selection criteria
null, // the selection args
sortOrder // the sort order
);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
loginUserRecycler.setAdapter(new CursorRecyclerAdapter(LoginUserActivity.this, data));
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
};
}
My Adapter is -
package com.oodles.oodlesapprtc;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by ankita on 13/4/17.
*/
public class CursorRecyclerAdapter extends RecyclerView.Adapter<ContactViewHolder> {
private Cursor mCursor;
private final int mNameColIdx,
mIdColIdx;
int phoneNumber;
int hasPhoneNumber;
private Context mContext;
public CursorRecyclerAdapter(Context context, Cursor cursor) {
mCursor = cursor;
this.mContext = context;
mNameColIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
mIdColIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID);
hasPhoneNumber = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER);
}
#Override
public ContactViewHolder onCreateViewHolder(ViewGroup parent, int pos) {
View listItemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.contact_row, parent, false);
return new ContactViewHolder(listItemView);
}
#Override
public void onBindViewHolder(ContactViewHolder contactViewHolder, int pos) {
mCursor.moveToPosition(pos);
String contactName = mCursor.getString(mNameColIdx);
long contactId = mCursor.getLong(mIdColIdx);
Contact c = new Contact();
Log.e("ddhdhdhhdhdhdhd",hasPhoneNumber+"");
c.name = contactName;
c.number = getPhoneNumberFromContactId(contactId);
c.profilePic = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
getPhoneNumberFromContactId(contactId);
contactViewHolder.bind(c);
}
private String getPhoneNumberFromContactId(long contactId) {
String contactNumber = "8874675724";
return contactNumber;
}
#Override
public int getItemCount() {
return mCursor.getCount();
}
}
How can I get the phone number for the same i am getting a cursorindexoutofbound exception and has phone number value as 3 but it can be only 0 or 1 why it is 3 i don't understand this.
Can Anyone please explain this to me also please explain how the contact fetch works
You have a few issues in the code:
You're querying on the Contacts table, but sorting using a CommonDataKinds.Phone table field, that's not good, you can sort using Contacts.DISPLAY_NAME_PRIMARY instead.
cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER) gives you a column index, not the value of the field, that's why you're getting 3 (it's the 4rd field defined in PROJECTION2), change hasPhoneNumber to mHasPhoneNumberIdx to avoid confusion.
add hasPhoneNumber = mCursor.getInt(mHasPhoneNumberIdx); to your onBindViewHolder method, to get the actual value.
Running a long command like a real implementation of getPhoneNumberFromContactId within onBindViewHolder is really bad... you should change your main query so you do two big queries, one for all contacts, one for all phones, and then use some HashMap to get a phone using a contact-id.

JavaFX Custom Table Cell - Strange behavior

I have this code:
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Example extends Application
{
#Override
public void start(Stage stage) throws Exception
{
TableView<Color> table = new TableView<>();
ObservableList<Color> colors = FXCollections.observableArrayList();
table.setItems(colors);
table.setEditable(true);
TableColumn<Color, Color> column = new TableColumn<>();
column.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue()));
column.setCellFactory(param ->
{
ObservableList<Color> menuColors = FXCollections.observableArrayList();
menuColors.addAll(Color.RED, Color.GREEN, Color.BLUE);
return new ComboBoxTableCell(menuColors);
});
Button button = new Button("Add row");
button.setOnAction(event -> colors.add(Color.BLACK));
VBox box = new VBox(table, button);
table.getColumns().add(column);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
stage.setScene(new Scene(box));
stage.show();
}
public class ComboBoxTableCell extends TableCell<Color, Color>
{
private ComboBox<Color> comboBox;
public ComboBoxTableCell(ObservableList<Color> colors)
{
comboBox = createFancyComboBox(colors);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void updateItem(Color item, boolean empty)
{
if (item == null || empty)
setGraphic(null);
else
setGraphic(comboBox);
}
}
private ComboBox<Color> createFancyComboBox(ObservableList<Color> colors)
{
ComboBox<Color> comboBox = new ComboBox<>(colors);
Callback<ListView<Color>, ListCell<Color>> factory = list -> new ColorSquare();
comboBox.setCellFactory(factory);
comboBox.setButtonCell(factory.call(null));
comboBox.setValue(colors.get(0));
return comboBox;
}
public static class ColorSquare extends ListCell<Color>
{
#Override
public void updateItem(Color item, boolean empty)
{
super.updateItem(item, empty);
Rectangle rect = new Rectangle(18, 18);
if (item != null)
{
rect.setFill(item);
setGraphic(rect);
}
}
}
public static void main(String[] args)
{
launch(args);
}
}
If I try to run it, click on the button, change the color to for Green and click 8 times on the button, the green square will disapper.
How do I fix this and why is it happening? The real code isn't much different from this, this is the only problem I have. Thank you.
I have noticed that at times the rerendering of a table cell doesn't work right after a certain number of attempts. I had one where I was updating a remaining amount when the user entered a value in another cell.
To solve this I would toggle visibility of the column.
column.setVisible(false);
column.setVisible(true);
I would do this immediately after the action that changed the value in the cell.
It's a hack but seems to work.

Is there a way to bind the content of a ListProperty in JavaFX?

Is there an existing way to bind the content of a ListProperty? Consider the following:
private final ListProperty<Worker<?>> workers = new SimpleListProperty<>(FXCollections.observableArrayList());
public ListProperty<Worker<?>> workersProperty() {return workers;}
public ObservableList<Worker<?>> getWorkers() {return workers.get();}
public void setWorkers(ObservableList<Worker<?>> workers) {this.workers.set(workers);}
private final ObservableList<Worker<?>> stateWatchedWorkers = FXCollections.observableArrayList(
new Callback<Worker<?>, Observable[]>() {
#Override
public Observable[] call(final Worker<?> param) {
return new Observable[]{
param.stateProperty()
};
}
}
);
What I want to do is bind the content of the stateWatchedWorkers list to workers. If the entire workers list is replaced, I want stateWatchedWorkers to be updated to match the content of the new list. Put another way, I want the stateWatchedWorkers list to be mapped to the current list being held by the workers property.
You can do
Bindings.bindContent(stateWatchedWorkers, workers);
(since ListProperty<T> implements ObservableList<T>, by delegating the ObservableList methods to the wrapped list, etc).
Here's a complete example:
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ListContentBindingExample extends Application {
private static final Random RNG = new Random();
private static final ExecutorService exec = Executors.newFixedThreadPool(5, r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t ;
});
#Override
public void start(Stage primaryStage) {
final ListProperty<Worker<?>> workers = new SimpleListProperty<>(FXCollections.observableArrayList());
final ObservableList<Worker<?>> stateWatchedWorkers = FXCollections.observableArrayList(worker -> new Observable[] { worker.stateProperty() });
Bindings.bindContent(stateWatchedWorkers, workers);
Button newWorker = new Button("New Worker") ;
newWorker.setOnAction(event -> workers.add(createTask()));
Button replaceAllWorkers = new Button("New Worker List");
replaceAllWorkers.setOnAction(event -> {
ObservableList<Worker<?>> newWorkers = FXCollections.observableArrayList();
for (int i=0; i<=10; i++) {
newWorkers.add(createTask());
}
workers.set(newWorkers);
});
ListView<Worker<?>> workerView = new ListView<>();
workerView.setCellFactory(listView -> new ListCell<Worker<?>>() {
#Override
public void updateItem(Worker<?> worker, boolean empty) {
super.updateItem(worker, empty);
if (empty) {
setText(null);
} else {
setText(worker.getState().toString());
}
}
});
workerView.setItems(stateWatchedWorkers);
workers.get().addListener((Change<? extends Worker<?>> change) -> stateWatchedWorkers.forEach(w -> System.out.println(w.getState())));
stateWatchedWorkers.addListener((Change<? extends Worker<?>> change) -> stateWatchedWorkers.forEach(w -> System.out.println(w.getState())));
HBox buttons = new HBox(5, newWorker, replaceAllWorkers);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(10));
primaryStage.setScene(new Scene(new BorderPane(workerView, null, null, buttons, null), 250, 400));
primaryStage.show();
}
private Worker<Void> createTask() {
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
public Void call() throws Exception {
Thread.sleep(1000 + RNG.nextInt(2000));
return null ;
}
};
}
};
service.setExecutor(exec);
service.start();
return service ;
}
public static void main(String[] args) {
launch(args);
}
}
James_D's hint about ListProperty delegating to its ObservableList is the correct answer. Here's another code example that shows binding the content of ListProperty "just works".
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.util.Callback;
public class ListPropertyContentBinding {
/* This is a quick test to see how ListProperty notifies about list changes
* when the entire list is swapped.
*
* Conclusion: When the backing list is changed, ListProperty will perform
* notification indicating that all items from the original list were
* removed and that all items from the updated (new) list were added.
*/
ObservableList<Person> oldPeople = FXCollections.observableArrayList();
ObservableList<Person> newPeople = FXCollections.observableArrayList();
// A list property that is used to hold and swap the lists.
ListProperty<Person> people = new SimpleListProperty<>(oldPeople);
// A list that includes an extractor. This list will be bound to people and
// is expected to additionally notify whenever the name of a person in the
// list changes.
ObservableList<Person> nameWatchedPeople = FXCollections.observableArrayList(
new Callback<Person, Observable[]>() {
#Override
public Observable[] call(final Person person) {
return new Observable[]{
person.name
};
}
});
public ListPropertyContentBinding() {
Bindings.bindContent(nameWatchedPeople, people);
nameWatchedPeople.addListener((ListChangeListener<Person>) change -> {
while (change.next()) {
System.out.println(" " + change.toString());
}
});
Person john = new Person("john");
System.out.println("Adding john to oldPeople. Expect 1 change.");
oldPeople.add(john);
System.out.println("Changing john's name to joe. Expect 1 change.");
john.name.set("joe");
Person jane = new Person("jane");
System.out.println("Adding jane to newPeople. Expect 0 changes.");
newPeople.add(jane);
System.out.println("Updating people to newPeople. Expect ? changes.");
people.set(newPeople);
Person jacob = new Person("jacob");
System.out.println("Adding jacob to oldPeople. Expect 0 changes.");
oldPeople.add(jacob);
System.out.println("Adding jacob to newPeople. Expect 1 change.");
newPeople.add(jacob);
System.out.println("Changing jacob's name to jack. Expect 1 change.");
jacob.name.set("jack");
System.out.println("Updating people to oldPeople. Expect ? changes.");
System.out.println(" oldPeople: " + oldPeople.toString());
System.out.println(" newPeople : " + newPeople.toString());
people.set(oldPeople);
}
public static void main(String[] args) {
new ListPropertyContentBinding();
}
class Person {
private final StringProperty name = new SimpleStringProperty();
public Person(String name) {
this.name.set(name);
}
#Override
public String toString() {
return name.get();
}
}
}
And here's the output from the above example:
Adding john to oldPeople. Expect 1 change.
{ [john] added at 0 }
Changing john's name to joe. Expect 1 change.
{ updated at range [0, 1) }
Adding jane to newPeople. Expect 0 changes.
Updating people to newPeople. Expect ? changes.
{ [joe] removed at 0 }
{ [jane] added at 0 }
Adding jacob to oldPeople. Expect 0 changes.
Adding jacob to newPeople. Expect 1 change.
{ [jacob] added at 1 }
Changing jacob's name to jack. Expect 1 change.
{ updated at range [1, 2) }
Updating people to oldPeople. Expect ? changes.
oldPeople: [joe, jack]
newPeople : [jane, jack]
{ [jane, jack] removed at 0 }
{ [joe, jack] added at 0 }

Dynamically adding data in Hashmap to TableView in JavaFX

I have a map mapping symbols to prices.I want to display a table with one column containing keys and the other column containing corresponding values in JavaFX
public class myMap {
Map<Symbol, Price> map;
}
I want to display a table like the following
Symbol | Price
I guess it can be done by using TableView with CallBack.
Since I've already done something similar to this I'll add my code.
import java.util.Map;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.NumberStringConverter;
public class MapTable extends Application {
#Override
public void start(Stage primaryStage) {
final ObservableMap<String, Number> obsMap = FXCollections.observableHashMap();
for (int i = 0; i < 3; i++) obsMap.put("key "+i, i*10d);
final TableView<ObservableMap.Entry<String, Number>> tv = new TableView(FXCollections.observableArrayList(obsMap.entrySet()));
tv.setEditable(true);
obsMap.addListener((MapChangeListener.Change<? extends String, ? extends Number> change) -> {
tv.setItems(FXCollections.observableArrayList(obsMap.entrySet()));
});
TableColumn<ObservableMap.Entry<String, Number>,String> keyCol = new TableColumn<>("key");
TableColumn<ObservableMap.Entry<String, Number>,Number> priceCol = new TableColumn<>("price");
tv.getColumns().addAll(keyCol,priceCol);
keyCol.setCellValueFactory((p) -> {
return new SimpleStringProperty(p.getValue().getKey());
});
keyCol.setCellFactory(TextFieldTableCell.forTableColumn());
keyCol.setOnEditCommit((TableColumn.CellEditEvent<Map.Entry<String,Number>, String> t) -> {
final String oldKey = t.getOldValue();
final Number oldPrice = obsMap.get(oldKey);
obsMap.remove(oldKey);
obsMap.put(t.getNewValue(),oldPrice);
});
priceCol.setCellValueFactory((p) -> {
return new ReadOnlyObjectWrapper<>(p.getValue().getValue());
});
priceCol.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
priceCol.setOnEditCommit((TableColumn.CellEditEvent<Map.Entry<String,Number>, Number> t) -> {
obsMap.put(t.getTableView().getItems().get(t.getTablePosition().getRow()).getKey(),//key
t.getNewValue());//val);
});
Button btn1 = new Button();
btn1.setText("Add data");
btn1.setOnAction((ActionEvent event) -> {
obsMap.put("hi",100);
});
Button btn2 = new Button();
btn2.setText("verify data");
btn2.setOnAction((ActionEvent event) -> {
for (Map.Entry<String,Number> me : obsMap.entrySet())
System.out.println("key "+me.getKey()+" val "+me.getValue());
});
VBox root = new VBox(tv,btn1,btn2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Map Table test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Hope this can help.
public class Demo {
public static void main(String[] args) {
// Use Java Collections to create the List.
Map<String,String> map = new HashMap<String,String>();
// Now add observability by wrapping it with ObservableList.
ObservableMap<String,String> observableMap = FXCollections.observableMap(map);
observableMap.addListener(new MapChangeListener() {
#Override
public void onChanged(MapChangeListener.Change change) {
System.out.println("Detected a change! ");
}
});
// Changes to the observableMap WILL be reported.
observableMap.put("key 1","value 1");
System.out.println("Size: "+observableMap.size());
// Changes to the underlying map will NOT be reported.
map.put("key 2","value 2");
System.out.println("Size: "+observableMap.size());
}
}
http://docs.oracle.com/javafx/2/collections/jfxpub-collections.htm