Increase the cell size of a particular Row -JAVAFX TableView - java

I am trying to resize a particular cell in TableView in a particular Row.
The code i use,
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableColors extends Application {
TableView<String> tView = new TableView<>();
#Override
public void start(Stage primaryStage) {
TableColumn<String, String> tcName = new TableColumn("name");
TableColumn<String, String> tcName1 = new TableColumn("name1");
ObservableList<String> items = FXCollections.observableArrayList("One", "Two", "Three", "Four");
tView.setItems(items);
tView.getColumns().add(tcName);
tView.getColumns().add(tcName1);
tView.setColumnResizePolicy((param) -> true);
Platform.runLater(() -> customResize());
tcName.setCellValueFactory((p) -> new SimpleStringProperty(p.getValue()));
tcName.setCellFactory(new Callback<TableColumn<String, String>, TableCell<String, String>>() {
#Override
public TableCell call(TableColumn p) {
return new TableCell<String, String>() {
#Override
public void updateItem(final String item, final boolean empty) {
super.updateItem(item, empty);//*don't forget!
if (item != null) {
setText(item);
setAlignment(Pos.CENTER);
} else {
setText(null);
}
}
};
}
});
Scene scene = new Scene(tView, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
private void customResize() {
try {
TableColumn<?, ?> col = tView.getColumns().get(0);
col.setPrefWidth(150);
} catch (Exception r) {
r.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
when i run this complete name column size is increased(as expected).
Instead i want to increase only the size of the cell which contains "Three".
Am not sure ,What code should i incorporate to make it work in this way.
How can i solve this?

you can try this logic
Let your cell DataHolder implement something like this
public class DataHolder{
boolean cellOverlaps = false; //tells the cell whether its string overlaps others
int rowPos = -1;// the vertical position
int columnPos =-1; //its column position
boolean startPoint = false;//whether its the start point
//in other words it begins from here hence its right border we care
String text = ""
}
So what you do basically, is in your updateIndex(), or updateItem() methods or your Commit() method of your cell, (use commit()) you calculate the width of the cell by Cell.prefWidth(-1) or Cell.getWidth(), after you get the space your text is occupying in your first cell, compare that to your width if its > then you trim the text and make it overlap. here you have something like
dataHolder.cellOverlaps = true;
dataHolder.startPoint = true;//other overlapping cell will have this false
dataHolder.rowPost = getIndex();
dataHolder.columnPos = getTableView().getColumns().indexof(getTableColumn());
so now you look for cells in row getIndex() with columnPos > the startPoint
and you change the styles of the cell for example the starting cell with have a transparent border right color
-fx-border-color: black black black transparent;
now the other overlaping celss will have the left transparent
-fx-border-color: black transparent black black;
and you set the text of the remaining string to the cell. if possible change the background of the TableRow so it matches your cell hence you do not get to see the divider line.
try it and see, wanted to drop a demo but do not have much time though.
Hope if helps

Related

What is right way to work with tableview?

For some time i have been trying to get my tableview work as kind of spreadsheet that is updated by background thread and when cell get updated, it for few seconds higlights ( changes style ) and then goes back to original style.
I already know, that i can't store and set styles directly in table cell and i need some kind of backing class, that will hold this data. But tableview with its "reusing" of cells (using same cells for different data) acts really weird. When all cells fits on screen it works flawlessly for me, but once i place around 100 cells and it becomes scrollable it starts being buggy, sometimes styles ( or setted graphic) disappears and after scrolling appears, if i disable some top cells of view, some other cells after scrolling get disabled as well and so on. Is there any right way to do this?
What i need basically is
Background data thread ---updates--> tableview
Another thread --after few seconds removes style--> tableview
As i have it now, i have model class that holds data, style and reference to table cell where it should be ( i disabled ordering, so it should be ok ) and background thread updates data in model class, and that model class changes style on referenced cell and register itself in "style remover" thread, that after while removes style.
I think posting my actual code won't be useful, because once i've discovered that cells are being reused my code has become too complicated and a little bit unreadable so i want to completely redo it right way.
Peformance is not that important for me, there wont be more than 100 cells, but this highlighting and having buttons in tableview must work flawlessly.
This is how my app looks like now - for idea of what i need.
EDIT: here is link to my another question related to this.
The collaborators:
on the data side, a (view) model which has a recentlyChanged property, that's updated whenever the value is changed
on the view side, a custom cell that listens to that recentlyChanged property and updates its style as appropriate
The tricky part is to clean up cell state when re-used or not-used: the method that's always (hopefully!) called is cell.updateIndex(int newIndex), so that's the place to un-/register the listener.
Below a runnable (though crude ;) example
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import de.swingempire.fx.util.FXUtils;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
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.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TableCoreRecentlyChanged extends Application {
public static class RecentChanged extends TableCell<Dummy, String> {
private ChangeListener<Boolean> recentListener = (src, ov, nv) -> updateRecentStyle(nv);
private Dummy lastDummy;
/*
* Just to see any effect.
*/
protected void updateRecentStyle(boolean highlight) {
if (highlight) {
setStyle("-fx-background-color: #99ff99");
} else {
setStyle("-fx-background-color: #009900");
}
}
#Override
public void updateIndex(int index) {
if (lastDummy != null) {
lastDummy.recentlyChangedProperty().removeListener(recentListener);
lastDummy = null;
}
updateRecentStyle(false);
super.updateIndex(index);
if (getTableRow() != null && getTableRow().getItem() != null) {
lastDummy = getTableRow().getItem();
updateRecentStyle(lastDummy.recentlyChangedProperty().get());
lastDummy.recentlyChangedProperty().addListener(recentListener);
}
}
#Override
protected void updateItem(String item, boolean empty) {
if (item == getItem()) return;
super.updateItem(item, empty);
if (item == null) {
super.setText(null);
super.setGraphic(null);
} else {
super.setText(item);
super.setGraphic(null);
}
}
}
private Parent getContent() {
TableView<Dummy> table = new TableView<>(createData(50));
table.setEditable(true);
TableColumn<Dummy, String> column = new TableColumn<>("Value");
column.setCellValueFactory(c -> c.getValue().valueProperty());
column.setCellFactory(e -> new RecentChanged());
column.setMinWidth(200);
table.getColumns().addAll(column);
int editIndex = 20;
Button changeValue = new Button("Edit");
changeValue.setOnAction(e -> {
Dummy dummy = table.getItems().get(editIndex);
dummy.setValue(dummy.getValue()+"x");
});
HBox buttons = new HBox(10, changeValue);
BorderPane content = new BorderPane(table);
content.setBottom(buttons);
return content;
}
private ObservableList<Dummy> createData(int size) {
return FXCollections.observableArrayList(
Stream.generate(Dummy::new)
.limit(size)
.collect(Collectors.toList()));
}
private static class Dummy {
private static int count;
ReadOnlyBooleanWrapper recentlyChanged = new ReadOnlyBooleanWrapper() {
Timeline recentTimer;
#Override
protected void invalidated() {
if (get()) {
if (recentTimer == null) {
recentTimer = new Timeline(new KeyFrame(
Duration.millis(2500),
ae -> set(false)));
}
recentTimer.playFromStart();
} else {
if (recentTimer != null) recentTimer.stop();
}
}
};
StringProperty value = new SimpleStringProperty(this, "value", "initial " + count++) {
#Override
protected void invalidated() {
recentlyChanged.set(true);
}
};
public StringProperty valueProperty() {return value;}
public String getValue() {return valueProperty().get(); }
public void setValue(String text) {valueProperty().set(text); }
public ReadOnlyBooleanProperty recentlyChangedProperty() { return recentlyChanged.getReadOnlyProperty(); }
public String toString() {return "[dummy: " + getValue() + "]";}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
// primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableCoreRecentlyChanged.class.getName());
}

How to scale Label within a ListView in JavaFX

I have a ListView with some Labels in it. The labels' width property is bound to the width property of the ListView but they seem to be slightly larger meaning that a horizontal scrollbar is shown on the list view. What I want is to fit the labels in the list view without the scrollbar on the bottom. I have looked at various padding and insets values on both the label and the list view but none I have found are the culprit (most are zero).
Here is an example which demonstrates the problem.
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class ListViewScrollExample extends Application {
private ListView<Node> listView;
#Override
public void start(Stage stage) throws Exception {
listView = new ListView<>();
addItem("Some quite long string to demonstrate the problem");
Scene scene = new Scene(listView);
stage.setScene(scene);
stage.show();
}
public void addItem(String item) {
Label label = new Label(item);
label.setWrapText(true);
label.maxWidthProperty().bind(listView.widthProperty());
listView.getItems().add(label);
}
public static void main(String[] args){
Application.launch(args);
}
}
The default CSS file adds padding to a ListCell (line 2316 in the current release):
.list-cell {
-fx-padding: 0.25em 0.583em 0.25em 0.583em; /* 3 7 3 7 */
}
It generally a bad idea to use Node instances as the data backing a ListView: you should use String in this example, and use the cell factory to create a label displaying the string that is configured as you need. The following seems to work for your example:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class ListViewScrollExample extends Application {
private ListView<String> listView;
#Override
public void start(Stage stage) throws Exception {
listView = new ListView<>();
listView.getItems().add("Some quite long string to demonstrate the problem");
listView.setCellFactory(lv -> {
ListCell<String> cell = new ListCell<String>() {
private Label label = new Label();
{
label.setWrapText(true);
label.maxWidthProperty().bind(Bindings.createDoubleBinding(
() -> getWidth() - getPadding().getLeft() - getPadding().getRight() - 1,
widthProperty(), paddingProperty()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
label.setText(item);
setGraphic(label);
}
}
};
return cell ;
});
Scene scene = new Scene(listView);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args){
Application.launch(args);
}
}
Here I created a list cell that displays a label as its graphic, with the text of the label set to the string to be displayed. The constructor for the cell binds the label's max width to the width of the cell, less any space required for padding. The call to setContentDisplay(ContentDisplay.GRAPHIC_ONLY) appears necessary, so the cell doesn't try to allocate any space for text.
It may be possible to do this by setting the text directly on the list cell and calling setWrapText(true) on the cell (which is, after all, also a subclass of Labeled), but I couldn't get it to work this way.
I couldn't replicate the problem but you can try the following instead of label.maxWidthProperty().bind(listView.widthProperty());
double i = Double.parseDouble(listView.widthProperty().toString());
label.setMaxWidth((i-2.0));
You can change the 2.0 to any pixel count you need to alter the screen by.

Auto-sizing a Shape Assigned as a Graphic to a Cell in JavaFx

At work I created a TableView that needs to have specific cells flash from one color to the other simultaneously. This is relatively easy using Rectangles, FillTransitions, and a ParallelTransition as shown in the toy example below. After assigning the rectangle to the FillTransition, I set the TableCell's graphic to the rectangle. Then I just had to add/remove the FillTransition from the ParallelTransition depending on whether or not the cell should be blinking.
An area where I had a lot of difficulty, however, was in figuring out a way to scale the rectangle to the size of the TableCell containing it as a graphic. The problem I had was that the TableCell would always resize itself to have empty space between its boundaries and the boundaries of the rectangle.
I had to solve this in a very tedious and round-about way: I had to call setFixedCellSize to fix the table's cell height to whatever my rectangle's height was, reposition the rectangle up and to the left by trial and error through calling its setTranslateX/Y, and set the minwidth and minheight of the column to slightly less than whatever my rectangle's width and height was set to. It solved the problem, but I would've hoped for something a little less tedious and annoying.
I would have assumed this could be avoided by doing one or more of the following with the cell:
Calling setScaleShape(true)
Calling setContentDisplay(ContentDisplay.GRAPHIC_ONLY)
Setting the cell's CSS style to include "-fx-scale-shape: true"
Sadly, none of these had any noticeable effect...
My question is a three-parter:
Is there a better way to size a Shape assigned as a graphic for a Cell to fill the boundaries of the Cell?
Why would none of the three methods above have any effect in my case and what is their actual intended purpose? Do they only apply for a shape assigned with setShape() as opposed to setGraphic()?
Are there any legitimate reasons why JavaFx wouldn't support setting the preferred width or height of Nodes other than those that subclass Region? Autosizing seems like something that should be universal to all Nodes in the hierarchy, and it seems intuitive that any Parent node should be able to dictate the size of its children when necessary.
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javafx.animation.Animation;
import javafx.animation.FillTransition;
import javafx.animation.ParallelTransition;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
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.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FlashingPriorityTable extends Application {
public static void main(String args[]) {
FlashingPriorityTable.launch();
}
#Override
public void start(Stage primaryStage) throws Exception {
// periodically add prioritized items to an observable list
final ObservableList<PItem> itemList = FXCollections.observableArrayList();
class ItemAdder {
private int state, count = 0; private final int states = 3;
public synchronized void addItem() {
state = count++ % states;
PItem item;
if(state == 0)
item = new PItem(Priority.LOW, count, "bob saget");
else if(state == 1)
item = new PItem(Priority.MEDIUM, count, "use the force");
else
item = new PItem(Priority.HIGH, count, "one of us is in deep trouble");
itemList.add(item);
}
};
final ItemAdder itemAdder = new ItemAdder();
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
() -> itemAdder.addItem(),
0, // initial delay
1, // period
TimeUnit.SECONDS); // time unit
// set up a table view bound to the observable list
final TableColumn<PItem, Priority> priCol = new TableColumn<>("Priority");
priCol.setCellValueFactory(new PropertyValueFactory<PItem, Priority>("priority"));
priCol.setCellFactory((col) -> new PriorityCell()); // create a blinking cell
priCol.setMinWidth(50);
priCol.setMaxWidth(50);
final TableColumn<PItem, Integer> indexCol = new TableColumn<>("Index");
indexCol.setCellValueFactory(new PropertyValueFactory<PItem, Integer>("index"));
indexCol.setCellFactory((col) -> makeBorderedTextCell());
final TableColumn<PItem, String> descriptionCol = new TableColumn<>("Description");
descriptionCol.setCellValueFactory(new PropertyValueFactory<PItem, String>("description"));
descriptionCol.setCellFactory((col) -> makeBorderedTextCell());
descriptionCol.setMinWidth(300);
final TableView<PItem> table = new TableView<>(itemList);
table.getColumns().setAll(priCol, indexCol, descriptionCol);
table.setFixedCellSize(25);
// display the table view
final Scene scene = new Scene(table);
primaryStage.setScene(scene);
primaryStage.show();
}
// render a simple cell text and border
private <T> TableCell<PItem, T> makeBorderedTextCell() {
return new TableCell<PItem, T>() {
#Override protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if(item == null || empty) {
setText(null);
} else {
setBorder(new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, null, null)));
setText(item.toString());
}
}
};
}
/* for cells labeled as high priority, render an animation that blinks (also include a border) */
public static class PriorityCell extends TableCell<PItem, Priority> {
private static final ParallelTransition pt = new ParallelTransition();
private final Rectangle rect = new Rectangle(49.5, 24);
private final FillTransition animation = new FillTransition(Duration.millis(100), rect);
public PriorityCell() {
rect.setTranslateX(-2.75);
rect.setTranslateY(-2.7);
animation.setCycleCount(Animation.INDEFINITE); animation.setAutoReverse(true); }
#Override
protected void updateItem(Priority priority, boolean empty) {
super.updateItem(priority, empty);
if(priority == null || empty) {
setGraphic(null);
return;
}
setGraphic(rect);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setBorder(new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, null, null)));
if(priority == Priority.HIGH) {
if(!pt.getChildren().contains(animation)) {
animation.setFromValue(Color.BLACK);
animation.setToValue(priority.getColor());
animation.setShape(rect);
pt.getChildren().add(animation);
pt.stop(); pt.play();
}
} else {
if(pt.getChildren().contains(animation)) {
pt.getChildren().remove(animation);
pt.stop(); pt.play();
}
rect.setFill(priority.getColor());
}
}
}
/* an item that has a priority assigned to it */
public static class PItem {
private ObjectProperty<Priority> priority = new SimpleObjectProperty<>();
private IntegerProperty index = new SimpleIntegerProperty();
private StringProperty description = new SimpleStringProperty();
public PItem(Priority priority, Integer index, String description) {
setPriority(priority); setIndex(index); setDescription(description);
}
public void setPriority(Priority priority_) { priority.set(priority_); }
public Priority getPriority() { return priority.get(); }
public void setIndex(int index_) { index.set(index_); }
public Integer getIndex() { return index.get(); }
public void setDescription(String description_) { description.set(description_); }
public String getDescription() { return description.get(); }
}
/* a priority */
public enum Priority {
HIGH(Color.RED), MEDIUM(Color.ORANGE), LOW(Color.BLUE);
private final Color color;
private Priority(Color color) { this.color = color; }
public Color getColor() { return color; }
}
}
Regarding:
the TableCell would always resize itself to have empty space between its boundaries and the boundaries of the rectangle.
This is because the cell has by default 2 px of padding, according to modena.css:
.table-cell {
-fx-padding: 0.166667em; /* 2px, plus border adds 1px */
-fx-cell-size: 2.0em; /* 24 */
}
One easy way to get rid of this empty space is just override it:
#Override
protected void updateItem(Priority priority, boolean empty) {
super.updateItem(priority, empty);
...
setGraphic(rect);
setStyle("-fx-padding: 0;");
...
}
The next problem you also mention is autosizing. According to JavaDoc, for Node.isResizable():
If this method returns true, then the parent will resize the node (ideally within its size range) by calling node.resize(width,height) during the layout pass. All Regions, Controls, and WebView are resizable classes which depend on their parents resizing them during layout once all sizing and CSS styling information has been applied.
If this method returns false, then the parent cannot resize it during layout (resize() is a no-op) and it should return its layoutBounds for minimum, preferred, and maximum sizes. Group, Text, and all Shapes are not resizable and hence depend on the application to establish their sizing by setting appropriate properties (e.g. width/height for Rectangle, text on Text, and so on). Non-resizable nodes may still be relocated during layout.
Clearly, a Rectangle is not resizable, but this doesn't mean you can't resize it: if the layout doesn't do it for you, you'll need to take care of it.
So one easy solution may be binding the dimensions of the rectangle to those of the cell (minus 2 pixels for the cell borders):
private final Rectangle rect = new Rectangle();
#Override
protected void updateItem(Priority priority, boolean empty) {
super.updateItem(priority, empty);
if(priority == null || empty) {
setGraphic(null);
return;
}
setGraphic(rect);
setStyle("-fx-padding: 0;");
rect.widthProperty().bind(widthProperty().subtract(2));
rect.heightProperty().bind(heightProperty().subtract(2));
...
}
Note that you won't need to translate the rectangle, and it won't be necessary to fix the size of the cell nor the width of the column, unless you want to give it a fixed size.
Note also that setShape() is intended to change the cell shape, that by default is already a rectangle.
This may answer your first two questions. For the third one, sometimes you wish the nodes were always resizable... but if that were the case we will have the opposite problem, trying to keep them constrained...

TableCell: how to use a StackedBarChart (or is it impossible)?

The trigger for my experiment was a recent question - one cell in a row should visualize the relative proportion of values in several cell values in the same row. In fx, such a visualization is supported in StackedBarChart (degenerate to a single category and with yAxis being the category axis).
Unfortunately, using such a chart as cell graphics has weird effects when updating the item, depending on how we do the update:
scenario A: initialize the chart with the series and update the data in the series. The bars appear fine only on the very first showing, scrolling back and forth leaves random "gaps" inside
scenario B: create and set new series in each round. The bars seem to have the correct width, but their colors changes randomly on scrolling
Also, there are minor visual quirks f.i. can't find a way to restrict the height of the cell as needed.
The questions:
how to make it work correctly, or
what's wrong, which part of the rendering mechanism interfers?
The example:
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
/**
* from SO, how to show relative bars with colors of
* a related chart
*
* https://stackoverflow.com/a/28141421/203657
*
* That's a solution with manually calculating and
* filling a rectangle with base chart colors
*
* Here trying to use StackedBarChart .. problems as noted in cell doc.
* Extracted TableStackedBarChart for SO question.
*/
public class TableStackedBar extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) {
ObservableList<Data> data = FXCollections.observableArrayList();
for (int i = 0; i<10; i++) data.add(new Data());
TableView<Data> tv = new TableView<>(data);
TableColumn<Data, Number> col1 = new TableColumn<>("num1");
TableColumn<Data, Number> col2 = new TableColumn<>("num2");
col1.setCellValueFactory((p)->{return p.getValue().num1;});
col2.setCellValueFactory((p)->{return p.getValue().num2;});
//make this column hold the entire Data object so we can access all fields
TableColumn<Data, Data> col3 = new TableColumn<>("bar");
col3.setPrefWidth(500);
col3.setCellValueFactory((p)->{return new ReadOnlyObjectWrapper<>(p.getValue());});
col3.setCellFactory(p -> new StackedBarChartCell(2000.));
tv.getColumns().addAll(col1,col2,col3);
tv.setFixedCellSize(50.);
Scene scene = new Scene(tv);
stage.setScene(scene);
stage.show();
}
/**
* TableCell that uses a StackedBarChart to visualize relation of
* data.
*
* Problems with updating items:
* - scenario A: updating the series leaves empty patches horizontally
* - scenario B: re-setting the series changes colors randomly
*
* Other problems
* - runs amok without fixedCellSize on tableView
* - can't max the height of the chart (so it's cut-off in the middle
*/
public static class StackedBarChartCell extends TableCell<Data, Data> {
NumberAxis xAxisHoriz = new NumberAxis();
CategoryAxis yAxisHoriz = new CategoryAxis();
StackedBarChart<Number, String> sbcHoriz = new StackedBarChart<>(xAxisHoriz, yAxisHoriz);
XYChart.Series<Number, String> series1Horiz = new XYChart.Series<>();
XYChart.Series<Number, String> series2Horiz = new XYChart.Series<>();
public StackedBarChartCell(double upperBound) {
yAxisHoriz.setTickLabelsVisible(false);
yAxisHoriz.setTickMarkVisible(false);
yAxisHoriz.setStyle("-fx-border-color: transparent transparent transparent transparent;");
xAxisHoriz.setTickLabelsVisible(false);
xAxisHoriz.setTickMarkVisible(false);
xAxisHoriz.setMinorTickVisible(false);
xAxisHoriz.setStyle("-fx-border-color: transparent transparent transparent transparent;");
xAxisHoriz.setAutoRanging(false);
xAxisHoriz.setUpperBound(upperBound);
xAxisHoriz.setLowerBound(0.);
sbcHoriz.setHorizontalGridLinesVisible(false);
sbcHoriz.setVerticalGridLinesVisible(false);
sbcHoriz.setLegendVisible(false);
sbcHoriz.setAnimated(false);
// scenario A: set series initially
sbcHoriz.getData().setAll(series1Horiz, series2Horiz);
sbcHoriz.setCategoryGap(0);
// no effect
sbcHoriz.setMaxHeight(20);
}
#Override
protected void updateItem(Data item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(sbcHoriz);
// scenario B: set new series
// uncomment for scenario A
// XYChart.Series<Number, String> series1Horiz = new XYChart.Series<>();
// XYChart.Series<Number, String> series2Horiz = new XYChart.Series<>();
// sbcHoriz.getData().setAll(series1Horiz, series2Horiz);
//---- end of scenario B
series1Horiz.getData().setAll(new XYChart.Data(item.num1.get(), "none"));
series2Horiz.getData().setAll(new XYChart.Data(item.num2.get(), "none"));
}
}
}
private static class Data{
private SimpleIntegerProperty num1 = new SimpleIntegerProperty((int)(Math.random()*1000));
private SimpleIntegerProperty num2 = new SimpleIntegerProperty((int)(Math.random()*1000));
public SimpleIntegerProperty num1Property(){return num1;}
public SimpleIntegerProperty num2Property(){return num2;}
}
}
Update: seems to be a regression in 8u40 - works for 8u20/25, not for 8u40b20. Reported as RT-39884
Here's just where I copied stuff into my CellFactory
col3.setCellFactory((TableColumn<Data, Data> param) -> {
return new TableCell<Data, Data>() {
#Override
protected void updateItem(Data item, boolean empty) {
super.updateItem(item, empty);
if (empty) setGraphic(null);
else {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
NumberAxis xAxisHoriz = new NumberAxis(0, 2000, 1000);
CategoryAxis yAxisHoriz = new CategoryAxis(FXCollections.observableArrayList(""));
XYChart.Series<Number, String> series1Horiz = new XYChart.Series<>();
XYChart.Series<Number, String> series2Horiz = new XYChart.Series<>();
StackedBarChart<Number, String> sbcHoriz = new StackedBarChart<>(xAxisHoriz, yAxisHoriz);
sbcHoriz.getData().setAll(series1Horiz, series2Horiz);
yAxisHoriz.setStyle("-fx-border-color: transparent transparent transparent transparent;"
+ "-fx-tick-labels-visible: false;"
+ "-fx-tick-mark-visible: false;"
+ "-fx-minor-tick-visible: false;"
+ "-fx-padding: 0 0 0 0;");
xAxisHoriz.setStyle("-fx-border-color: transparent transparent transparent transparent;"
+ "-fx-tick-labels-visible: false;"
+ "-fx-tick-mark-visible: false;"
+ "-fx-minor-tick-visible: false;"
+ "-fx-padding: 0 0 0 0;");
sbcHoriz.setHorizontalGridLinesVisible(false);
sbcHoriz.setVerticalGridLinesVisible(false);
sbcHoriz.setLegendVisible(false);
sbcHoriz.setAnimated(false);
xAxisHoriz.setMaxWidth(100);
sbcHoriz.setMaxWidth(100);
sbcHoriz.setPadding(Insets.EMPTY);
sbcHoriz.setCategoryGap(0);
setGraphic(sbcHoriz);
series1Horiz.getData().setAll(new XYChart.Data(item.num1.get(), ""));
series2Horiz.getData().setAll(new XYChart.Data(item.num2.get(), ""));
}
}
}
};
});
and also after I set this tv.setFixedCellSize(30);
I also had to change the column width to 200, I can't make the chart smaller.

ListView in javafx, adds multiple cells

Debugging for the code below, it shows that the updateItem() method is called multiple times, but I am unable to figure out why its being called multiple times.
I wanted to add the Tooltip to the ListView.
// THIS TO ADD TOOLTIP, NOT WORKING FULLY.
lstComments.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> p) {
final Tooltip tt = new Tooltip();
final ListCell<String> cell = new ListCell<String>() {
String message = ca.getMessage();
#Override
public void updateItem(String s, boolean empty) {
super.updateItem(s, empty);
tt.setText(message);
setTooltip(tt);
}
};
cell.setText(ca.getMessage());
return cell;
}
});
Recommendation
I find the usability of Tooltips on ListView cells horrible, because the Tooltips intercept standard mouse events used to select rows, scroll the list, etc. So I would not recommend placing Tooltips on ListView cells.
Why multiple cells are created and updateItem is called multiple times
It is expected that a ListView have multiple cells and that updateItem() is potentially called multiple times for each cell.
A cell is created for every row in the ListView that is displayed on the scene, even if some cells are empty. A couple more cells that are offscreen are usually created for efficient scroll handling. Each time the underlying data for a ListView is initially set or modified, or the list is scrolled, updateItem() will be invoked on relevant cells to update the cell's contents. In the case of scrolling a large list, updateItem() will be invoked many, many times for each cell.
Sample code for setting a Tooltip on ListView Cells
The code below is based on the Oracle JavaFX tutorial ListView sample, but customizes it to create Tooltips for cells when you hover over them.
mport javafx.application.Application;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewSample extends Application {
ListView<String> list = new ListView<String>();
ObservableList<String> data = FXCollections.observableArrayList(
"chocolate", "salmon", "gold", "coral", "darkorchid",
"darkgoldenrod", "lightsalmon", "black", "rosybrown", "blue",
"blueviolet", "brown");
final Label label = new Label();
#Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box, 200, 200);
stage.setScene(scene);
stage.setTitle("ListViewSample");
box.getChildren().addAll(list, label);
VBox.setVgrow(list, Priority.ALWAYS);
label.setLayoutX(10);
label.setLayoutY(115);
label.setFont(Font.font("Verdana", 20));
list.setItems(data);
list.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override public ListCell<String> call(ListView<String> list) {
return new ColorRectCell();
}
});
list.getSelectionModel().selectedItemProperty().addListener(
(ov, old_val, new_val) -> {
label.setText(new_val);
label.setTextFill(Color.web(new_val));
});
stage.show();
}
static class ColorRectCell extends ListCell<String> {
final Rectangle swatch = new Rectangle(30, 30);
final Tooltip tip = new Tooltip();
public ColorRectCell() {
tip.setGraphic(swatch);
}
#Override
public void updateItem(String color, boolean empty) {
super.updateItem(color, empty);
if (color != null) {
swatch.setFill(Color.valueOf(color.toUpperCase()));
setText(color);
setTooltip(tip);
} else {
setText("");
setTooltip(null);
}
}
}
public static void main(String[] args) {
launch(args);
}
}

Categories

Resources