How can I make an auto wrap ListView (multiline when the text is too long) in JavaFX 2? I know that if I put a \n to the string, it will be multiline, but the content is too dynamic.
Or is there a good way to put \n to the String after every xyz pixel length?
You can put a TextArea in the ListCell.graphicProperty(). This is usually used to set an icon in a list cell but can just as easy to set to any Node subclass.
Here is the exact code how I did it finally.
ListView<String> messages = new ListView<>();
messages.relocate(10, 210);
messages.setPrefSize(this.getPrefWidth() - 20, this.getPrefHeight() - 250);
messages.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> list) {
final ListCell cell = new ListCell() {
private Text text;
#Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
text = new Text(item.toString());
text.setWrappingWidth(messages.getPrefWidth());
setGraphic(text);
}
}
};
return cell;
}
});
There is no need to create additional controls such as TextArea or Text. It is enough to just setWrapText(true) of the ListCell and setPrefWidth(50.0) or so. It will be automatically rewrapped on resize.
Here is my working code in Kotlin:
datasets.setCellFactory()
{
object : ListCell<Dataset>()
{
init
{
isWrapText = true
prefWidth = 50.0
}
override fun updateItem(item: Dataset?, empty: Boolean)
{
super.updateItem(item, empty)
text = item?.toString()
}
}
}
By the way, this is how I made it to correctly wrap CamelCase words:
replace(Regex("(?<=\\p{javaLowerCase})(?=\\p{javaUpperCase})|(?<=[_.])"), "\u2028")
Here \u2028 is the Unicode soft line break character, which javaFX respects.
Related
I have a JavaFX table column which I would like to display a comma-separated list of strings, unless the text does not fit within the current bounds of the cell, at which point it would display, for example, "Foo and 3 others...", or "3 Bars", i.e. reflecting the number of elements in the list.
Is there a way to check, when building a CellValueFactory for a table column, whether the text would overrun the cell, so I could switch between these two behaviors?
You can specify an overrun style for Labeled controls like TableCells.
Overrun style ELLIPSIS will automatically add these ellipses as needed to indicate if the content would have extended outside of the label.
I recommend doing this in a cell factory, like so:
column.setCellFactory(() -> {
TableCell<?, ?> cell = new TableCell<>();
cell.setTextOverrun(OverrunStyle.ELLIPSIS);
return cell;
});
So you would need to use the cell factory instead of the cell value factory.
The reason I recommend cell factory is because the table creates and destroys cells on its own as needed, so you'd have a hard time getting all those instances and setting their overrun behavior if you didn't have control of those cells creation like you do with the cell factory.
New attempt
Try something along these lines, you might need to tweak the method to get the length of your string, and you might want to try to figure out the current length of the table cell whenever you update it, but this should get you started. Think it's a decent approach?
public class TestApplication extends Application {
public static void main(String[] args) {
Application.launch(args);
}
public void start(final Stage stage) {
stage.setResizable(true);
TestTableView table = new TestTableView();
ObservableList<String> items = table.getItems();
items.add("this,is,short,list");
items.add("this,is,long,list,it,just,keeps,going,on,and,on,and,on");
Scene scene = new Scene(table, 400, 200);
stage.setScene(scene);
stage.show();
}
/**
* Note: this does not take into account font or any styles.
* <p>
* You might want to modify this to put the text in a label, apply fonts and css, layout the label,
* then get the width.
*/
private static double calculatePixelWidthOfString(String str) {
return new Text(str).getBoundsInLocal().getWidth();
}
public class TestTableView extends TableView<String> {
public TestTableView() {
final TableColumn<String, CsvString> column = new TableColumn<>("COL1");
column.setCellValueFactory(cdf -> {
return new ReadOnlyObjectWrapper<>(new CsvString(cdf.getValue()));
});
column.setCellFactory(col -> {
return new TableCell<String, CsvString>() {
#Override
protected void updateItem(CsvString item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
String text = item.getText();
// get the width, might need to tweak this.
double textWidth = calculatePixelWidthOfString(text);
// might want to compare against current cell width
if (textWidth > 100) {
// modify the text here
text = item.getNumElements() + " elements";
}
setText(text);
}
}
};
});
this.getColumns().add(column);
}
}
private static class CsvString {
private final String text;
private final String[] elements;
public CsvString(String string) {
Objects.requireNonNull(string);
this.text = string;
this.elements = string.split(" *, *");
}
public int getNumElements() {
return elements.length;
}
public String getText() {
return text;
}
}
}
So, I am trying to add a button to a column using Table View in JavaFX. I have successfully created a single button for one column; using the same code to add another button on another column with a small change of variables is resulting me in one error which I am unable to fix. The error is that it is not allowing me to use the word super. Below is the code in which I am having the error on;
TableColumn<UserDetails, UserDetails> addColumn = column("Add", ReadOnlyObjectWrapper<UserDetails>::new, 50);
addColumn.setCellFactory(col -> {
Button addButton = new Button("Add");
TableCell<UserDetails, UserDetails> addCell = new TableCell<UserDetails, UserDetails>() {
public void addItems(UserDetails userDetails, boolean empty) {
super.addItems(userDetails, empty); //This line is the error (super)
if (empty) {
setGraphic(null);
} else {
setGraphic(addButton);
}
}
};
addButton.setOnAction(event -> add(addCell.getItem(), primaryStage));
return addCell;
});
what am I doing wrong?
As you can see in the TableCell javadoc there is no addItems method in TableCell. You probably wanted to use the updateItem method:
#Override
protected void updateItem(UserDetails userDetails, boolean empty) {
super.updateItem(userDetails, empty);
...
I have to display 5000 nodes using ListView. Every node contains complex controls but only some text part is different in nodes. How can i reuse existing nodes controls to recreate my cells while scrolling
The answer of James_D points into the right direction. Normally, in JavaFX you shouldn't worry about reusing existing nodes - the JavaFX framework does exactly this, out-of-the-box. If you want to implement some custom cell rendering, you need to set a cell factory, and that usually looks like this:
listView.setCellFactory(new Callback() {
#Override
public Object call(Object param) {
return new ListCell<String>() {
// you may declare fields for some more nodes here
// and initialize them in an anonymous constructor
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty); // Default behaviour: zebra pattern etc.
if (empty || item == null) { // Default technique: take care of empty rows!!!
this.setText(null);
} else {
this.setText("this is the content: " + item);
// ... do your custom rendering!
}
}
};
}
});
Please note: this should work, but is merely illustrative - we Java Devs know that e.g., we would use a StringBuilder for String concatenation, especially in such cases where the code will execute very often.
If you want some complex rendering, you may build that graphic with additional nodes and set them as graphics property with setGraphic(). This works similar to the Label control:
// another illustrative cell renderer:
listView.setCellFactory(new Callback() {
#Override
public Object call(Object param) {
return new ListCell<Integer>() {
Label l = new Label("X");
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
this.setGraphic(null);
} else {
this.setGraphic(l);
l.setBackground(
new Background(
new BackgroundFill(
Color.rgb(3 * item, 2 * item, item),
CornerRadii.EMPTY,
Insets.EMPTY)));
l.setPrefHeight(item);
l.setMinHeight(item);
}
}
};
}
});
I have a TableView Column set up as follows, with some text in a Text node. I wanted to style up the text, but the only css property being picked up is the italics. How can I associate the text with other properties such as color.
I've tried text.getStyleClass().add("table-text-allign-top-left"); but only italics gets picked up.
I'd also like to add some kind of spacing, like padding, but I don't know how to add such to an item in a TableCell.
The other problem is how to align items: to the left' right in a TableCell.
Would appreciate it a lot if anyone could help. Thank you all in advance.
This is an extract of the TableView:
clientNames.setCellFactory(new Callback<TableColumn<NewClientPOJO, String>, TableCell<NewClientPOJO, String>>() {
#Override
public TableCell<NewClientPOJO, String> call(TableColumn<NewClientPOJO, String> param) {
final TableCell<NewClientPOJO, String> cell = new TableCell<NewClientPOJO, String>() {
private Text text;
private Text emails;
private Text emails2;
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
text = new Text(item.toString());
// Setting the wrapping width to the Text
text.setWrappingWidth(410);
text.getStyleClass().add("table-text-allign-top-left");
emails = new Text("Good DW TV");
emails.getStyleClass().add("lower");
emails2 = new Text("Scandinavia - Lines cold weather");
emails2.getStyleClass().add("lower");
VBox vbTable = new VBox();
vbTable.getChildren().add(text);
vbTable.getChildren().add(emails);
vbTable.getChildren().add(emails2);
setGraphic(vbTable);
}
}
};
return cell;
}
});
Thanks brian for the reply. I got it to work by changing the Node from Text to a Label. The Label picks up the style class.
I have the following problem creating custom cell factory of a ComboBox from an FXML file created with Scene Builder in JavaFX:
I created a custom cell factory of Labels. It works fine when the user clicks on the items. The y are displayed in the "button" area. But when the user wants to click on another items the previously clicked item is gone.
Here is the code of the combobox cell factory:
idCardOnlineStatusComboBox.setCellFactory(new Callback<ListView<Label>, ListCell<Label>>() {
#Override public ListCell<Label> call(ListView<Label> param) {
final ListCell<Label> cell = new ListCell<Label>() {
#Override public void updateItem(Label item,
boolean empty) {
super.updateItem(item, empty);
if(item != null || !empty) {
setGraphic(item);
}
}
};
return cell;
}
});
I suppose there is a problem in the cell factory but i can't figure out where it is.
I extract the combobox from the fxml with this code:
#FXML private ComboBox idCardOnlineStatusComboBox;
then i fill the combobox with this:
idCardOnlineStatusComboBox.getItems().addAll(
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Online.Title"), new ImageView(onlineImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Away.Title"), new ImageView(awayImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.DoNotDisturb.Title"), new ImageView(doNotDisturbImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Invisible.Title"), new ImageView(offlineImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Offline.Title"), new ImageView(offlineImg))
);
The disappearing behavior may be a bug. You can file it to JavaFX Jira, and let the Oracle guys decide it further. Additionally you can investigate the ComboBox.setCellFactory(...) source code for the reason of this behavior and find workaround. But my suggestion is to use the ComboBox Cell's (ListCell) internal Labelled component, instead of yours:
#Override
public void updateItem(Label item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getText());
setGraphic(item.getGraphic());
} else {
setText(null);
setGraphic(null);
}
}
Note the else part of the code, cover all use cases when writing an if-statement.