I need RadioButtons inside ListView so i find this answer:
javaFX:listview with Radio Button
but the problem is that selected cell in ListView and selected RadioButton are not bind. If a click on cell in list i want automatically to select the corresponding RadioButton.
So my question is how can i bind this two?
UPDATE:
So the only way i managed to do it is similar to #Sedrick Jefferson answer but without adding StackPane in front of RadioButton.
I add list of RadioButtons namesRadioButtons to ToggleGroup and add listener to selectedToggleProperty: when new RadioButton is selected i select corresponding row in ListView
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class RadioButtonListView extends Application
{
public static final ObservableList<RadioButton> namesRadioButtons
= FXCollections.observableArrayList();
private ToggleGroup group = new ToggleGroup();
#Override
public void start(Stage primaryStage)
{
primaryStage.setTitle("List View Sample");
final ListView<RadioButton> listView = new ListView();
listView.setPrefSize(200, 250);
listView.setEditable(true);
String[] names =
{
"Adam", "Alex", "Alfred", "Albert",
"Brenda", "Connie", "Derek", "Donny",
"Lynne", "Myrtle", "Rose", "Rudolph",
"Tony", "Trudy", "Williams", "Zach"
};
for (String name : names)
{
namesRadioButtons.add(new RadioButton(name));
}
group.getToggles().addAll(namesRadioButtons);
listView.setItems(namesRadioButtons);
group.selectedToggleProperty().addListener((obs, oldSel, newSel) -> {
listView.getSelectionModel().select((RadioButton) newSel);
listView.getFocusModel().focus(listView.getSelectionModel().getSelectedIndex());
});
listView.setCellFactory(param -> new RadioListCell());
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldSel, newSel) ->
{
if (newSel != null)
{
RadioButton tempRadioButton = (RadioButton) newSel;
tempRadioButton.setSelected(true);
}
if (oldSel != null)
{
RadioButton tempRadioButton = (RadioButton) oldSel;
tempRadioButton.setSelected(false);
}
});
StackPane root = new StackPane();
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
private class RadioListCell extends ListCell<RadioButton>
{
#Override
public void updateItem(RadioButton obj, boolean empty)
{
super.updateItem(obj, empty);
if (empty)
{
setText(null);
setGraphic(null);
}
else
{
setGraphic(obj);
}
}
}
}
Question: Is there any better solution to this?
To repeat: adding controls as data items is not a solution!
Instead, use a custom cell that has-a control as needed and configure with the state of the item/list/selection, just as in the QA cited by the OP. The only part missing is the back-sync (from the radio state to the list selection): to achieve that, install a listener in the cell.
Something like (modified example):
public class RadioButtonListView extends Application {
public static final ObservableList names =
FXCollections.observableArrayList();
private ToggleGroup group = new ToggleGroup();
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("List View Sample");
final ListView listView = new ListView();
listView.setPrefSize(200, 250);
listView.setEditable(true);
names.addAll(
"Adam", "Alex", "Alfred", "Albert",
"Brenda", "Connie", "Derek", "Donny",
"Lynne", "Myrtle", "Rose", "Rudolph",
"Tony", "Trudy", "Williams", "Zach"
);
listView.setItems(names);
listView.setCellFactory(param -> new RadioListCell());
StackPane root = new StackPane();
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private class RadioListCell extends ListCell<String> {
RadioButton radioButton;
ChangeListener<Boolean> radioListener = (src, ov, nv) -> radioChanged(nv);
WeakChangeListener<Boolean> weakRadioListener = new WeakChangeListener(radioListener);
public RadioListCell() {
radioButton = new RadioButton();
radioButton.selectedProperty().addListener(weakRadioListener);
radioButton.setFocusTraversable(false);
// let it span the complete width of the list
// needed in fx8 to update selection state
radioButton.setMaxWidth(Double.MAX_VALUE);
}
protected void radioChanged(boolean selected) {
if (selected && getListView() != null && !isEmpty() && getIndex() >= 0) {
getListView().getSelectionModel().select(getIndex());
}
}
#Override
public void updateItem(String obj, boolean empty) {
super.updateItem(obj, empty);
if (empty) {
setText(null);
setGraphic(null);
radioButton.setToggleGroup(null);
} else {
radioButton.setText(obj);
radioButton.setToggleGroup(group);
radioButton.setSelected(isSelected());
setGraphic(radioButton);
}
}
}
}
Update:
The example is working fine in fx9 but has issues in fx8:
when clicking outside of the radiobutton (somewhere in the trailing whitespace of a row) the radio selected is not always updated. This can be fixed by forcing the radio to stretch to the full width of the list.
the selected state of the radio is not always updated when the listView's selection changed. This can be handled by installing a listener to the cell's selected property and update the radio in that listener.
the selected state of the radio is not reliably updated when the cell is re-used. This needs further digging ...
Related
My TableView uses a custom CellFactory to display a ComboBox in one column, allowing the user to select from available options. Those options are loaded after the TableView is populated (as they can change based on the user's selections elsewhere in the scene).
In the MCVE below, I have two columns for my Item class: Name and Color. Within the Color column, I have the ComboBox which will display the current value of the Item's itemColor property.
You will see that the ComboBox is not populated with a list of values yet and item "Three" has no value selected.
What I need is this:
When the user clicks on the "Load Available Colors" button, the list for the ComboBox is created. The user can now select any of the available colors. However, if there is not already a value for the item's color, I want the first color in the ComboBoxes to be selected automatically; so item "Three" would now show the color "Red" as being selected.
THE MCVE
Item.java:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Item {
private StringProperty itemName = new SimpleStringProperty();
private StringProperty itemColor = new SimpleStringProperty();
public Item(String name, String color) {
this.itemName.set(name);
this.itemColor.set(color);
}
public String getItemName() {
return itemName.get();
}
public void setItemName(String itemName) {
this.itemName.set(itemName);
}
public StringProperty itemNameProperty() {
return itemName;
}
public String getItemColor() {
return itemColor.get();
}
public void setItemColor(String itemColor) {
this.itemColor.set(itemColor);
}
public StringProperty itemColorProperty() {
return itemColor;
}
}
Main.java:
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
// List of items
private static ObservableList<Item> listOfItems = FXCollections.observableArrayList();
// List of available Colors. These will be selectable from the ComboBox
private static ObservableList<String> availableColors = FXCollections.observableArrayList();
public static void main(String[] args) {
launch(args);
}
private static void buildSampleData() {
availableColors.addAll("Red", "Blue", "Green", "Yellow", "Black");
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Build a list of sample data. This data is loaded from my data model and passed to the constructor
// of this editor in my real application.
listOfItems.addAll(
new Item("One", "Black"),
new Item("Two", "Black"),
new Item("Three", null),
new Item("Four", "Green"),
new Item("Five", "Red")
);
// TableView to display the list of items
TableView<Item> tableView = new TableView<>();
// Create the TableColumn
TableColumn<Item, String> colName = new TableColumn<>("Name");
TableColumn<Item, String> colColor = new TableColumn<>("Color");
// Cell Property Factories
colName.setCellValueFactory(column -> new SimpleObjectProperty<>(column.getValue().getItemName()));
colColor.setCellValueFactory(column -> new SimpleObjectProperty<>(column.getValue().getItemColor()));
// Add ComboBox to the Color column, populated with the list of availableColors
colColor.setCellFactory(tc -> {
ComboBox<String> comboBox = new ComboBox<>(availableColors);
comboBox.setMaxWidth(Double.MAX_VALUE);
TableCell<Item, String> cell = new TableCell<Item, String>() {
#Override
protected void updateItem(String color, boolean empty) {
super.updateItem(color, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(comboBox);
comboBox.setValue(color);
}
}
};
// Set the action of the ComboBox to set the right Value to the ValuePair
comboBox.setOnAction(event -> {
listOfItems.get(cell.getIndex()).setItemColor(comboBox.getValue());
});
return cell;
});
// Add the column to the TableView
tableView.getColumns().addAll(colName, colColor);
tableView.setItems(listOfItems);
// Add button to load the data
Button btnLoadData = new Button("Load Available Colors");
btnLoadData.setOnAction(event -> {
buildSampleData();
});
root.getChildren().add(btnLoadData);
// Add the TableView to the root layout
root.getChildren().add(tableView);
Button btnPrintAll = new Button("Print All");
btnPrintAll.setOnAction(event -> {
for (Item item : listOfItems) {
System.out.println(item.getItemName() + " : " + item.getItemColor());
}
});
root.getChildren().add(btnPrintAll);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
}
Now, with a regular ComboBox, a simple call to comboBox.getSelectionModel().selectFirst() after loading the availableColors would be fine. But since this ComboBox is created within the CellFactory, I am not sure how to update it once the list of colors is populated.
Indidentally, I use this CellFactory implementation instead of a ComboBoxTableCell because I want them to be visible without having to enter edit mode on the TableView.
I actually took kleopatra's advice and updated my data model to include a default value instead. I agree this is cleaner and more appropriate approach.
TL;DR: Listener is also activated on other cells.
I have a TreeView containing different TreeItems representing data of a custom class. If a BooleanProperty of the underlying data changes, the cell should change its colour. If it changes again, the colour should be removed.
I use a listener, but when I scroll through the TreeView, a changing property of a certain cell also changes the colour of other cells. This behaviour can be reproduced when running my MWE and right clicking on some cells, scrolling, clicking again, and so on. The TreeView might be cleaned by just scrolling so that the concerned cells are out a view for a moment.
I could remove the listener, but then the colour will only be changed, if the cell reappears after scrolling away and back to it.
The question is: How can I use a listener properly in a cellFactory?
MWE
CellFactoryQuestion.java
package cellfactoryquestion;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CellFactoryQuestion extends Application {
/** Custom class used as underlying data of TreeItems */
class CustomObject {
String label;
BooleanProperty state = new SimpleBooleanProperty(false);
CustomObject(String s) { label = s; }
}
/** Cell Factory for CustomObject */
class CustomTreeCell extends TreeCell<CustomObject>{
PseudoClass customClass = PseudoClass.getPseudoClass("custom");
#Override
protected void updateItem(CustomObject co, boolean empty) {
super.updateItem(co, empty);
if (empty || co == null) {
setText(null);
setGraphic(null);
pseudoClassStateChanged(customClass, false);
} else {
setText(co.label);
setGraphic(null);
// BEGIN PROBLEMATIC
/* define background color of cell according to state */
pseudoClassStateChanged(customClass, co.state.getValue());
co.state.addListener((o, ov, nv) -> {
pseudoClassStateChanged(customClass, nv);
});
// END PROBLEMATIC
/* if right click, switch state */
this.setOnContextMenuRequested(e -> {
co.state.setValue(co.state.getValue() ^ true);
});
}
}
}
#Override
public void start(Stage primaryStage) {
/* define TreeView 1/3 */
TreeView tw = new TreeView();
TreeItem rootTreeItem = new TreeItem(new CustomObject("Root"));
rootTreeItem.setExpanded(true);
/* define TreeView 2/3 */
for (int c = 0; c != 5; c++) {
TreeItem ci = new TreeItem(new CustomObject("Cat " + c));
rootTreeItem.getChildren().add(ci);
ci.setExpanded(true);
for (int i = 0; i != 5; i++) {
TreeItem ii = new TreeItem(new CustomObject("Item " + i));
ci.getChildren().add(ii);
}
}
/* define TreeView 3/3 */
tw.setRoot(rootTreeItem);
tw.setCellFactory(value -> new CustomTreeCell());
/* define Scene */
StackPane root = new StackPane();
root.getChildren().add(tw);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add("/styles/Styles.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Styles.css
.tree-cell:custom {
-fx-background-color: salmon;
}
The problem is that you do not unregister the listener. Do this before calling super.updateItem. This allows you to retrieve the old item using getItem:
class CustomTreeCell extends TreeCell<CustomObject>{
private final ChangeListener<Boolean> listener = (o, ov, nv) -> pseudoClassStateChanged(customClass, nv);
PseudoClass customClass = PseudoClass.getPseudoClass("custom");
#Override
protected void updateItem(CustomObject co, boolean empty) {
// remove listener from old item
CustomObject oldItem = getItem();
if (oldItem != null) {
oldItem.state.removeListener(listener);
}
super.updateItem(co, empty);
if (empty || co == null) {
setText(null);
setGraphic(null);
pseudoClassStateChanged(customClass, false);
} else {
setText(co.label);
setGraphic(null);
/* define background color of cell according to state */
pseudoClassStateChanged(customClass, co.state.getValue());
co.state.addListener(listener);
...
My problem is as follows,
For the sake of this question I reproduced the problem in a new project.
Say I have this application with a combobox in it, there could be 1 or more items in there. And I would like it to be so that when the user clicks an item in the combobox that 'something' happens.
I produced the following code:
obsvList.add("item1");
cbTest.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Item clicked");
}
});
This works when the application starts and an item is selected for the first time. This also works when there are 2 or more items in the combobox (when the user clicks item 1, then item 2, then item 1 for example)
However my problem is that when there is only 1 item in the combobox, let's say "item1". And the user reopens the combobox and clicks "item1" again then it won't redo the action.
It will only print the line "Item Clicked" when a 'new' item is clicked.
I hope it made it clear what the problem i'm experiencing is, if not please ask for clarification and I will give so where needed.
Thanks in advance!
The functionality of a combo box is to present the user with a list of options from which to choose. When you are using a control which implies selection, you should really ensure that the UI is always consistent with the option that is selected. If you do this, then it makes no sense to "repeat an action" when the user "reselects" the same option (because the UI is already in the required state). One approach to this is to use binding or listeners on the combo box's value:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ComboBoxExample extends Application {
#Override
public void start(Stage primaryStage) {
ComboBox<Item> choices = new ComboBox<>();
for (int i = 1 ; i <=3 ; i++) {
choices.getItems().add(new Item("Choice "+i, "These are the details for choice "+i));
}
Label label = new Label();
choices.valueProperty().addListener((obs, oldItem, newItem) -> {
label.textProperty().unbind();
if (newItem == null) {
label.setText("");
} else {
label.textProperty().bind(newItem.detailsProperty());
}
});
BorderPane root = new BorderPane();
root.setCenter(label);
root.setTop(choices);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public class Item {
private final String name ;
private final StringProperty details = new SimpleStringProperty() ;
public Item(String name, String details) {
this.name = name ;
setDetails(details) ;
}
public String getName() {
return name ;
}
#Override
public String toString() {
return getName();
}
public final StringProperty detailsProperty() {
return this.details;
}
public final String getDetails() {
return this.detailsProperty().get();
}
public final void setDetails(final String details) {
this.detailsProperty().set(details);
}
}
public static void main(String[] args) {
launch(args);
}
}
In this case, there is never a need to repeat an action when the user "reselects" the same option, because the code always assures that the UI is consistent with what is selected anyway (there is necessarily nothing to do if the user selects the option that is already selected). By using bindings in the part of the UI showing the details (just a simple label in this case), we are assured that the UI stays up to date if the data changes externally. (Obviously in a real application, this may be far more complex, but the basic strategy is still exactly the same.)
On the other hand, functionality that requires an action to be repeated if the user selects the same functionality is better considered as presenting the user with a set of "actions". The appropriate controls for this are things like menus, toolbars with buttons, and MenuButtons.
An example of a set of repeatable actions is:
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MenuButtonExample extends Application {
#Override
public void start(Stage primaryStage) {
MenuButton menuButton = new MenuButton("Items");
Label label = new Label();
Item[] items = new Item[3];
for (int i = 1 ; i <=3 ; i++) {
items[i-1] = new Item("Item "+i);
}
for (Item item : items) {
MenuItem menuItem = new MenuItem(item.getName());
menuItem.setOnAction(e -> item.setTimesChosen(item.getTimesChosen() + 1));
menuButton.getItems().add(menuItem);
}
label.textProperty().bind(Bindings.createStringBinding(() ->
Stream.of(items)
.map(item -> String.format("%s chosen %d times", item.getName(), item.getTimesChosen()))
.collect(Collectors.joining("\n")),
Stream.of(items)
.map(Item::timesChosenProperty)
.collect(Collectors.toList()).toArray(new IntegerProperty[0])));
BorderPane root = new BorderPane();
root.setCenter(label);
root.setTop(menuButton);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Item {
private final String name ;
private final IntegerProperty timesChosen = new SimpleIntegerProperty();
public Item(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
#Override
public String toString() {
return getName();
}
public final IntegerProperty timesChosenProperty() {
return this.timesChosen;
}
public final int getTimesChosen() {
return this.timesChosenProperty().get();
}
public final void setTimesChosen(final int timesChosen) {
this.timesChosenProperty().set(timesChosen);
}
}
public static void main(String[] args) {
launch(args);
}
}
The idea is to set a listener on the ListView pane, that appears whenever you click on the ComboBox. The ListView instance is created once the ComboBox is first loaded in the JavaFX scene. Therefore, we add a listener on the ComboBox to check when it appears on the scene, and then through the "lookup" method we get the ListView and add a listener to it.
private EventHandler<MouseEvent> cboxMouseEventHandler;
private void initComboBox() {
ComboBox<String> comboBox = new ComboBox<String>();
comboBox.getItems().add("Item 1");
comboBox.getItems().add("Item 2");
comboBox.getItems().add("Item 3");
comboBox.sceneProperty().addListener((a,oldScene,newScene) -> {
if(newScene == null || cboxMouseEventHandler != null)
return;
ListView<?> listView = (ListView<?>) comboBox.lookup(".list-view");
if(listView != null) {
cboxMouseEventHandler = (e) -> {
Platform.runLater(()-> {
String selectedValue = (String) listView.getSelectionModel().getSelectedItem();
if(selectedValue.equals("Item 1"))
System.out.println("Item 1 clicked");
});
}; // cboxMouseEventHandler
listView.addEventFilter(MouseEvent.MOUSE_PRESSED, cboxMouseEventHandler);
} // if
});
} // initComboBox
I'am working on a java project using javafx multiple input types.but i am having a strangle ComboBox behaviours since i use Labels with images(ImageView) on it.
1- Combobox looks in white! but i need it in black.
2- and every time i choose an item.
3- it disappear!!!
Here is my code:
...
import javafx.scene.control.ComboBox;
import javafx.scene.image.ImageView;
ImageView img_tun = new ImageView("images/icones/flag/Tunisia.png");
Label lbl_tun=new Label("1",img_tun);
ImageView img_fr = new ImageView("images/icones/flag/France.png");
Label lbl_fr=new Label("2",img_fr);
ImageView img_aut = new ImageView("images/icones/flag/World.png");
Label lbl_aut=new Label("3",img_aut);
optionsnat=FXCollections.observableArrayList(lbl_tun,lbl_fr,lbl_aut);
#FXML
ComboBox<Label> cb_nat = new ComboBox<Label>();
private String nat="1";
...
#Override
public void initialize(URL location, ResourceBundle resources) {
...
cb_nat.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number number, Number number2) {
if(cb_nb.getItems().get((Integer) number2)=="1"){setNat("1");}
else if(cb_nb.getItems().get((Integer) number2)=="2"){setNat("2");}
else if(cb_nb.getItems().get((Integer) number2)=="3"){setNat("3");}
else{System.err.println("Erreur lors de changement de nation..");}
}
});
}
...
and code.fxml
<ComboBox fx:id="cb_nat" layoutX="40.0" layoutY="265.0" prefWidth="150.0" />
EDIT:
After reading this Article i know that my approach is tottaly wrong and strongly not recommended.. if anyone have another ideas to put bnation flags in ComboBox please help!!
thanks..(Sorry for my bad english)
What is causing this problem is that when you choose a ListCell, its item (Label in our situation) is being moved by the ComboBox from the ListCell (Items observableList) to the ButtonCell, the ButtonCell is the small box that is empty by default. However, we all know that any Node object cannot be placed twice anywhere inside the same scene, and since there is no clone function for the ListCell class, javafx removes it from its last place to the new place which is the ButtonCell.
The solution is to add strings
items in the list and provide a cell factory to create the label node inside the cell factory. Create a class called "StringImageCell" and do the following:
You need to set the cellFactory property:
cb_nat.setCellFactory(listview -> new StringImageCell());
You need to set the buttonCell property: cb_nat.setButtonCell(new StringImageCell());
Here is an example:
public class ComboBoxCellFactory extends Application {
#Override
public void start(Stage stage) throws Exception {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("1", "2", "3");
//Set the cellFactory property
comboBox.setCellFactory(listview -> new StringImageCell());
// Set the buttonCell property
comboBox.setButtonCell(new StringImageCell());
BorderPane root = new BorderPane();
root.setCenter(comboBox);
Scene scene = new Scene(root, 600, 600);
stage.setScene(scene);
stage.show();
}
//A Custom ListCell that displays an image and string
static class StringImageCell extends ListCell<String> {
Label label;
static HashMap<String, Image> pictures = new HashMap<>();
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setItem(null);
setGraphic(null);
} else {
setText(item);
ImageView image = getImageView(item);
label = new Label("",image);
setGraphic(label);
}
}
}
private static ImageView getImageView(String imageName) {
ImageView imageView = null;
switch (imageName) {
case "1":
case "2":
case "3":
if (!pictures.containsKey(imageName)) {
pictures.put(imageName, new Image(imageName + ".png"));
}
imageView = new ImageView(pictures.get(imageName));
break;
default:
imageName = null;
}
return imageView;
}
public static void main(String[] args) {
launch(args);
}
}
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);
}
}