I am using jdk1.8.0_40
I want to make a TableView which can have dynamically changing columns, but it leaks memory in Old/Tenured gen and eventually hangs the JVM.
Here is a simple program that demonstrates the leak (imports not included):
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
TableView<ObservableList<String>> tableView = new TableView<>();
Button button = new Button("button");
button.setOnAction((actionEvent) -> {
// Clearing items and/or columns here doesn't remove the leak
tableView.getItems().clear();
tableView.getColumns().clear();
for (int g = 0; g < 50; g++) { // iterate 50 times to see the leak quickly
TableColumn<ObservableList<String>, String> tableColumn = new TableColumn<>("column");
tableView.getColumns().add(tableColumn);
}
tableView.getItems().add(FXCollections.observableArrayList("1"));
// clearing the items here removes the leak somehow, but also makes the table useless
/* tableView.getItems().clear(); */
});
primaryStage.setScene(new Scene(new VBox(tableView, button), 800, 600));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
In the code above pressing the button 2nd time should clear all items and columns, and it does, but some reference somehow persists somewhere.
Some things I've already tried:
Tried searching for similar issues, but didn't find anything relevant.
Tried storing all the references in an ArrayList and explicitly remove()-ing them instead of clear() - didn't work.
Tried using ListView, which works fine, but it isn't a table and can't have columns.
Tried using different combinations of classes (e.g. StringProperty instead of String), which to no surprise also didn't work.
I have only recently started learning JavaFX so I may just be missing something obvious, if so - sorry for a dumb question.
How do I fix this leak?
If it can't be fixed, is it a bug, or is this intended behavior and I should not create TableColumn dynamically?
To answer my own question in case somebody is also having this issue - this is a bug.
I submitted a JIRA bug report, for more info and maybe updates see https://javafx-jira.kenai.com/browse/RT-40325
If you suspect that there's a leak, you fix it by first identifying it using a profiler.
I rewrote your sample (which wasn't showing any data btw), there doesn't seem to be a leak. Try this and check the console output.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MainLeak extends Application {
private static final int iterations = 100;
private static final int columns = 50;
private static final int rows = 100;
#Override
public void start(Stage primaryStage) {
TableView<ObservableList<StringProperty>> table = new TableView<>();
Button button = new Button("button");
button.setOnAction((actionEvent) -> {
addData( table, iterations);
});
addData( table, iterations);
primaryStage.setScene(new Scene(new VBox(table, button), 800, 600));
primaryStage.show();
}
private void addData( TableView table, int iterations) {
for (int i = 0; i < iterations; i++) {
final int index = i;
addData(table);
System.gc();
System.out.println(index + ": free: " + Runtime.getRuntime().freeMemory() / 1024 + " kb, max: " + Runtime.getRuntime().maxMemory() / 1024 + " kb");
}
}
private void addData(TableView table) {
table.getItems().clear();
table.getColumns().clear();
for( int col=0; col < columns; col++) {
table.getColumns().add(createColumn(col));
}
for( int row=0; row < rows; row++) {
ObservableList<StringProperty> data = FXCollections.observableArrayList();
for( int col=0; col < columns; col++) {
data.add(new SimpleStringProperty(String.valueOf( row + " / " + col)));
}
table.getItems().add(data);
}
}
private TableColumn<ObservableList<StringProperty>, String> createColumn(final int columnIndex) {
TableColumn<ObservableList<StringProperty>, String> column = new TableColumn<>();
String title = "Column " + columnIndex;
column.setText(title);
column.setCellValueFactory(cellDataFeatures -> cellDataFeatures.getValue().get(columnIndex));
return column;
}
public static void main(String[] args) {
launch(args);
}
}
Related
I am trying to set up the buttons on the following program, but they will not control the program properly. I am not sure why they are not working. The reverse button works, but the start and stop buttons do not.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ch30 extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
FanPane fan = new FanPane();
HBox hBox = new HBox(5);
Button btPause = new Button("Pause");
Button btResume = new Button("Resume");
Button btReverse = new Button("Reverse");
hBox.setAlignment(Pos.CENTER);
hBox.getChildren().addAll(btPause, btResume, btReverse);
BorderPane pane = new BorderPane();
pane.setCenter(fan);
pane.setBottom(hBox);
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 200, 200);
primaryStage.setTitle("Exercise15_28"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
//Runnable first = new Begin();
//Thread first = new Thread();
//t1.start();
Thread first = new Thread(new Runnable() {
#Override public void run() {
while (true) {
try {
//Pause
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
Platform.runLater(new Runnable() {
#Override public void run() {
fan.move();
}
});
}
}
});
first.start();
//Timeline animation = new Timeline(
//new KeyFrame(Duration.millis(100), e -> fan.move()));
//animation.setCycleCount(Timeline.INDEFINITE);
//animation.play(); // Start animation
scene.widthProperty().addListener(e -> fan.setW(fan.getWidth()));
scene.heightProperty().addListener(e -> fan.setH(fan.getHeight()));
//btPause.setOnAction(e -> first.wait());
btResume.setOnAction(e -> first.start());
btReverse.setOnAction(e -> fan.reverse());
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
class FanPane extends Pane {
private double w = 200;
private double h = 200;
private double radius = Math.min(w, h) * 0.45;
private Arc arc[] = new Arc[4];
private double startAngle = 30;
private Circle circle = new Circle(w / 2, h / 2, radius);
public FanPane() {
circle.setStroke(Color.BLACK);
circle.setFill(Color.WHITE);
getChildren().add(circle);
for (int i = 0; i < 4; i++) {
arc[i] = new Arc(w / 2, h / 2, radius * 0.9, radius * 0.9, startAngle + i * 90, 35);
arc[i].setFill(Color.RED); // Set fill color
arc[i].setType(ArcType.ROUND);
getChildren().addAll(arc[i]);
}
}
private double increment = 5;
public void reverse() {
increment = -increment;
}
public void move() {
setStartAngle(startAngle + increment);
}
public void setStartAngle(double angle) {
startAngle = angle;
setValues();
}
public void setValues() {
radius = Math.min(w, h) * 0.45;
circle.setRadius(radius);
circle.setCenterX(w / 2);
circle.setCenterY(h / 2);
for (int i = 0; i < 4; i++) {
arc[i].setRadiusX(radius * 0.9);
arc[i].setRadiusY(radius * 0.9);
arc[i].setCenterX(w / 2);
arc[i].setCenterY(h / 2);
arc[i].setStartAngle(startAngle + i * 90);
}
}
public void setW(double w) {
this.w = w;
setValues();
}
public void setH(double h) {
this.h = h;
setValues();
}
}
This should be done with a Timeline, I know it's your homework and for some crazy reason your homework has been specified to not use a Timeline. But for anybody else, don't do it this way, just use a Timeline.
That said...
You mention start and stop buttons of which you have none. I assume start means resume and stop means pause as those are the buttons you do have. So I will answer accordingly.
The easiest way to deal with this is to use a boolean variable to control whether or not the fan is moving.
Define a member of your application:
private boolean paused = false;
In your thread only move the fan if not paused:
Platform.runLater(() -> { if (!paused) fan.move(); });
Configure your buttons to set your flag:
btPause.setOnAction(e -> paused = true);
btResume.setOnAction(e -> paused = false);
I've just put the pause variable directly in the calling application, but you could encapsulate the pause status inside the fan object if you wished.
Normally when dealing with multi-threaded stuff you have to be careful about data getting corrupted due to race-conditions. For example, you would use constructs like AtomicBoolean or synchronized statements. But runLater puts everything on to the JavaFX application thread, so you don't necessarily need to worry about that.
There are alternate mechanisms you could use to ensure that your thread didn't keep looping and and sleeping, such as wait/notify or Conditions, but for a sample like this, you probably don't need that here.
Updated Application
Updated sample demonstrating the suggested modifications, tested on JDK 8u60, OS X 10.11.4.
public class ch30 extends Application {
private boolean paused = false;
#Override
public void start(Stage primaryStage) {
FanPane fan = new FanPane();
HBox hBox = new HBox(5);
Button btPause = new Button("Pause");
Button btResume = new Button("Resume");
Button btReverse = new Button("Reverse");
hBox.setAlignment(Pos.CENTER);
hBox.getChildren().addAll(btPause, btResume, btReverse);
BorderPane pane = new BorderPane();
pane.setCenter(fan);
pane.setBottom(hBox);
Thread first = new Thread(() -> {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
break;
}
Platform.runLater(() -> { if (!paused) fan.move(); });
}
});
first.setDaemon(true);
first.start();
btPause.setOnAction(e -> paused = true);
btResume.setOnAction(e -> paused = false);
btReverse.setOnAction(e -> fan.reverse());
Scene scene = new Scene(pane, 200, 200);
scene.widthProperty().addListener(e -> fan.setW(fan.getWidth()));
scene.heightProperty().addListener(e -> fan.setH(fan.getHeight()));
primaryStage.setScene(scene);
primaryStage.show();
}
}
Aside
Set the daemon status of your thread so that your application shuts down cleanly when somebody closes the main stage.
first.setDaemon(true);
I want to have squares added to a pane, and those squares maximized.
One square takes up the whole pane.
Two squares split the pane.
Three squares make it all in thirds.
When it overflows it goes to the next "row" and continues the process.
All the squares should be the same size.
Is there a way of using the standard layouts or which should I modify?
Thanks
Here's my take on this problem. I don't think it's a great solution, but at least it might help point out some techniques somebody might build on to get a better solution. Basically the solution overrides layoutChildren() to recalculate the preferred tile size as the number of tiles or the available space changes. I'm not sure getWidth or getHeight should really be called from layoutChildren (though it seems to work in this case).
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.Random;
public class TilePaneSample extends Application {
private static final Random random = new Random(42);
#Override
public void start(Stage stage) {
TilePane tiles = createTiles();
VBox layout = new VBox(
createControls(tiles),
tiles
);
VBox.setVgrow(tiles, Priority.ALWAYS);
stage.setScene(new Scene(layout, 400, 300));
stage.show();
}
private TilePane createTiles() {
TilePane tiles = new TilePane() {
#Override
protected void layoutChildren() {
if (getChildren().size() > 0) {
setPrefTileWidth(
Math.floor(
Math.min(
Math.max(
Tile.MIN_SIZE,
getWidth() / getChildren().size()
),
getHeight()
)
)
);
setPrefTileHeight(getPrefTileWidth());
}
super.layoutChildren();
}
};
tiles.setStyle("-fx-background-color: papayawhip;");
tiles.setPrefColumns(5);
tiles.setPrefRows(5);
tiles.getChildren().add(new Tile());
return tiles;
}
private ToolBar createControls(TilePane tiles) {
Button addTile = new Button("Add Tile");
addTile.setOnAction(action -> tiles.getChildren().add(new Tile()));
Button removeTiles = new Button("Remove Tiles");
removeTiles.setOnAction(action -> tiles.getChildren().clear());
ToolBar controls = new ToolBar(addTile, removeTiles);
controls.setMinHeight(ToolBar.USE_PREF_SIZE);
return controls;
}
private class Tile extends StackPane {
public static final int MIN_SIZE = 100;
public Tile() {
setStyle(
"-fx-background-color: " +
"rgb(" + random.nextInt(256) + ", " +
+ random.nextInt(256) + ", "
+ random.nextInt(256) + ");"
);
setMinSize(MIN_SIZE, MIN_SIZE);
}
}
public static void main(String[] args) { launch(args); }
}
I found this example of Internal Frames
http://docs.oracle.com/javase/tutorial/uiswing/components/internalframe.html
Is it possible to make the same internal Frames in JavaFX?
With JFXtras there is a Window control, where you can add content and handle the internal window behavior.
First you will need to put in your classpath the jfxtras library. They have some instructions where you can get the library. If you are using maven, just need to add:
<dependency>
<groupId>org.jfxtras</groupId>
<artifactId>jfxtras-labs</artifactId>
<version>2.2-r5</version>
</dependency>
Or download the library and put it into your project classpath, whatever.
Now I put a sample of the demo of the Window with a little difference, allowing generation of several windows.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import jfxtras.labs.scene.control.window.CloseIcon;
import jfxtras.labs.scene.control.window.MinimizeIcon;
import jfxtras.labs.scene.control.window.Window;
public class WindowTests extends Application {
private static int counter = 1;
private void init(Stage primaryStage) {
final Group root = new Group();
Button button = new Button("Add more windows");
root.getChildren().addAll(button);
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, 600, 500));
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// create a window with title "My Window"
Window w = new Window("My Window#"+counter);
// set the window position to 10,10 (coordinates inside canvas)
w.setLayoutX(10);
w.setLayoutY(10);
// define the initial window size
w.setPrefSize(300, 200);
// either to the left
w.getLeftIcons().add(new CloseIcon(w));
// .. or to the right
w.getRightIcons().add(new MinimizeIcon(w));
// add some content
w.getContentPane().getChildren().add(new Label("Content... \nof the window#"+counter++));
// add the window to the canvas
root.getChildren().add(w);
}
});
}
public double getSampleWidth() {return 600;}
public double getSampleHeight() {return 500;}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {launch(args);}
}
In the original demo, the event code was in the init method, and no button was included. I add the button to create dynamically windows and adding them to the screen.
Here is a snapshot of the result of the application:
I totally recommend you try the demo of jfxtras. They have really great stuff. Hope it helps.
You can implement simple internal window themselves. Main idea, that InternalWindow class just skeleton, that has internal frame like functionality. You can apply any content to it.
1) Declare class
public class InternalWindow extends Region
2) You should be able to set content in window
public void setRoot(Node node) {
getChildren().add(node);
}
3) You should be able to bring window to front if many window exist
public void makeFocusable() {
this.setOnMouseClicked(mouseEvent -> {
toFront();
});
}
4) Now we need dragging functionality
//just for encapsulation
private static class Delta {
double x, y;
}
//we can select nodes that react drag event
public void makeDragable(Node what) {
final Delta dragDelta = new Delta();
what.setOnMousePressed(mouseEvent -> {
dragDelta.x = getLayoutX() - mouseEvent.getScreenX();
dragDelta.y = getLayoutY() - mouseEvent.getScreenY();
//also bring to front when moving
toFront();
});
what.setOnMouseDragged(mouseEvent -> {
setLayoutX(mouseEvent.getScreenX() + dragDelta.x);
setLayoutY(mouseEvent.getScreenY() + dragDelta.y);
});
}
5) Also we want able to resize window (I show only simple right-bottom resizing)
//current state
private boolean RESIZE_BOTTOM;
private boolean RESIZE_RIGHT;
public void makeResizable(double mouseBorderWidth) {
this.setOnMouseMoved(mouseEvent -> {
//local window's coordiantes
double mouseX = mouseEvent.getX();
double mouseY = mouseEvent.getY();
//window size
double width = this.boundsInLocalProperty().get().getWidth();
double height = this.boundsInLocalProperty().get().getHeight();
//if we on the edge, change state and cursor
if (Math.abs(mouseX - width) < mouseBorderWidth
&& Math.abs(mouseY - height) < mouseBorderWidth) {
RESIZE_RIGHT = true;
RESIZE_BOTTOM = true;
this.setCursor(Cursor.NW_RESIZE);
} else {
RESIZE_BOTTOM = false;
RESIZE_RIGHT = false;
this.setCursor(Cursor.DEFAULT);
}
});
this.setOnMouseDragged(mouseEvent -> {
//resize root
Region region = (Region) getChildren().get(0);
//resize logic depends on state
if (RESIZE_BOTTOM && RESIZE_RIGHT) {
region.setPrefSize(mouseEvent.getX(), mouseEvent.getY());
} else if (RESIZE_RIGHT) {
region.setPrefWidth(mouseEvent.getX());
} else if (RESIZE_BOTTOM) {
region.setPrefHeight(mouseEvent.getY());
}
});
}
6) Usage. First we construct all layout. Then apply it to InternalWindow.
private InternalWindow constructWindow() {
// content
ImageView imageView = new ImageView("https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Cheetah4.jpg/250px-Cheetah4.jpg");
// title bar
BorderPane titleBar = new BorderPane();
titleBar.setStyle("-fx-background-color: green; -fx-padding: 3");
Label label = new Label("header");
titleBar.setLeft(label);
Button closeButton = new Button("x");
titleBar.setRight(closeButton);
// title bat + content
BorderPane windowPane = new BorderPane();
windowPane.setStyle("-fx-border-width: 1; -fx-border-color: black");
windowPane.setTop(titleBar);
windowPane.setCenter(imageView);
//apply layout to InternalWindow
InternalWindow interalWindow = new InternalWindow();
interalWindow.setRoot(windowPane);
//drag only by title
interalWindow.makeDragable(titleBar);
interalWindow.makeDragable(label);
interalWindow.makeResizable(20);
interalWindow.makeFocusable();
return interalWindow;
}
7) And how add window to layout
#Override
public void start(Stage primaryStage) throws Exception {
Pane root = new Pane();
root.getChildren().add(constructWindow());
root.getChildren().add(constructWindow());
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
Result
Full code: gist
Upd about close button:
You can add method to InternalWindow
public void setCloseButton(Button btn) {
btn.setOnAction(event -> ((Pane) getParent()).getChildren().remove(this));
}
And when construct:
interalWindow.setCloseButton(closeButton);
I have a problem where when I add data to a category axis and the axis titles have been used before, the order gets screwed up. Run the simple app below and click twice on the graph and look at the x axis to see what I mean. The data is supplied to the graph from month 0 to 10 but displayed in another order. Any Help?
package testCategoryBug;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.input.MouseEvent;
public class TestCategoryBug extends Application {
static Boolean even=true;
#Override public void start(Stage stage) {
stage.setTitle("Test Category Bug");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Month");
final LineChart<String,Number> lineChart = new LineChart<String,Number>(xAxis, yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
// Initialize some data for plotting.
final Series<String, Number> plottablePairs = new Series<String, Number>();
final Series<String, Number> plottablePairs2 = new Series<String, Number>();
for (int i = 0; i < 10; i++) {
Data<String,Number> pair= new Data<String,Number>("Month "+i,10.0*i);
plottablePairs.getData().add(pair);
}
for (int i = 5; i < 10; i++) {
Data<String,Number> pair= new Data<String,Number>("Month "+i,100.0-10*i);
plottablePairs2.getData().add(pair);
}
// add the first of the series
final ObservableList<XYChart.Series<String, Number>> chartData=FXCollections.observableArrayList();;
chartData.add(plottablePairs);
lineChart.setData(chartData);
// now on a mouse click, swap the data. Click twice and see what happens
lineChart.addEventHandler(MouseEvent.MOUSE_CLICKED,new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
chartData.clear();
if (even) {
chartData.add(plottablePairs2);
} else {
chartData.add(plottablePairs);
// note that after this, the axis is screwed up.
}
lineChart.setData(chartData);
even=!even;
}
});
// Put up the scene
Scene scene = new Scene(lineChart,800,600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Works for me using FX8 developers version. You may want to upgrade.
There were few issues with chart's axis ordering, like RT-23094, but they all were fixed in FX8.
I had the same problem. No matter how I setup the plot the ordering would always get messed up by reploting with new datapoints. The only fix was using java 1.8.
This was with the latest fx at the time, 2.2.51-b13. had to go with fx8 early access release.
I need to change the style of arbitrary cells in a TableView which has an variable number of columns. The code below shows the basic problem.
The ExampleRow class is proxy for the real data which comes from a spreadsheet, it's other function is to hold the highlighting information. Since I can't know how many columns there will be I just hold a list of columns that should be highlighted (column re-arrangement won't be supported). The ExampleTableCell class just sets the text for the cell and applies the highlight if needed.
If I set a highlight before the table gets drawn [cell (2,2)] then the cell correctly gets displayed with red text when the application starts. The problem is clicking the button sets cell (1,1) to be highlighted but the table doesn't change. If I resize the application window to nothing then open it back up again the highlighting of cell (1,1) is correctly drawn - presumably because this process forces a full redraw.
What I would like to know is how can I trigger the table to redraw newly highlighted cells (or all visible cells) so the styling is correct?
TIA
package example;
import java.util.HashSet;
import java.util.Set;
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
public class CellHighlightExample extends Application {
private final int columnCount = 4;
private final int rowCount = 5;
private TableView<ExampleRow> table = new TableView<>();
#Override
public void start(Stage stage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root);
Callback<TableColumn.CellDataFeatures<ExampleRow, String>, ObservableValue<String>> cellValueFactory = new Callback<TableColumn.CellDataFeatures<ExampleRow, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<ExampleRow, String> p) {
int row = p.getValue().getRow();
int col = p.getTableView().getColumns().indexOf(p.getTableColumn());
return new SimpleObjectProperty<>("(" + row + ", " + col + ")");
}
};
Callback<TableColumn<ExampleRow, String>, TableCell<ExampleRow, String>> cellFactory = new Callback<TableColumn<ExampleRow, String>, TableCell<ExampleRow, String>>() {
#Override
public TableCell<ExampleRow, String> call(TableColumn<ExampleRow, String> p) {
return new ExampleTableCell<>();
}
};
for (int i = 0, n = columnCount; i < n; i++) {
TableColumn<ExampleRow, String> column = new TableColumn<>();
column.setCellValueFactory(cellValueFactory);
column.setCellFactory(cellFactory);
table.getColumns().add(column);
}
ObservableList<ExampleRow> rows = FXCollections.observableArrayList();
for (int i = 0, n = rowCount; i < n; i++) {
ExampleRow row = new ExampleRow(i);
//Force a cell to be highlighted to show that highlighting works.
if (i == 2) { row.addHighlightedColumn(2); }
rows.add(row);
}
table.setItems(rows);
Button b = new Button("Click to Highlight");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
ExampleRow row = table.getItems().get(1);
row.addHighlightedColumn(1);
//How to trigger a redraw of the table or cell to reflect the new highlighting?
}
});
root.setTop(b);
root.setCenter(table);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class ExampleTableCell<S extends ExampleRow, T extends String> extends TableCell<S, T> {
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setText(null);
setGraphic(null);
} else {
setText(item);
int colIndex = getTableView().getColumns().indexOf(getTableColumn());
ExampleRow row = getTableView().getItems().get(getIndex());
if (row.isHighlighted(colIndex)) {
setTextFill(Color.RED);
}
}
}
}
private class ExampleRow {
private SimpleIntegerProperty row;
private Set<Integer> highlightedColumns = new HashSet<>();
public ExampleRow(int row) {
this.row = new SimpleIntegerProperty(row);
}
public int getRow() { return row.get(); }
public void setRow(int row) { this.row.set(row); }
public SimpleIntegerProperty rowProperty() { return row; }
public boolean isHighlighted(int col) {
if (highlightedColumns.contains(col)) {
return true;
}
return false;
}
public void addHighlightedColumn(int col) {
highlightedColumns.add(col);
}
}
}
There are lots of discussions about this problem, namely refreshing tableview after altering the item(s).
See
JavaFX 2.1 TableView refresh items
Issues
http://javafx-jira.kenai.com/browse/RT-21822
http://javafx-jira.kenai.com/browse/RT-22463
http://javafx-jira.kenai.com/browse/RT-22599
The solution is to trigger internal tableview update method. Some suggests to remove tableview items and add them again vs.. but the simplest workaround for your case seems to:
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
ExampleRow row = table.getItems().get(1);
row.addHighlightedColumn(1);
//How to trigger a redraw of the table or cell to reflect the new highlighting?
// Workaround
table.getColumns().get(0).setVisible(false);
table.getColumns().get(0).setVisible(true);
}
});
which found in issue comments linked above. Is this a really workaround or illusion of it? You need to dig deeper yourself.