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.
Related
I'm using a treeview in a javafx application and today I added drag and drop support to reorder the elements of the treeview. To do this i had to create my own CellFactory class but my code has a bug.
When the content of my treeview gets smaller the area under the treeview that should be empty still contains text that shouldn't be there.
This is what my treeview looks like before collapsing and after collapsing:
before collapsing the item
after collapsing the item
I got my inspiration for the drag and drop feature from this blogpost
https://brianyoung.blog/2018/08/23/javafx-treeview-drag-drop/
Before I had my own cellFactory class I had a lambda function in the setCellFactory method of my treeview and that solution didn't have this problem.
In my CellFactory class I override the call method. I think this is where i need to somehow refresh the content of the treeview to get rid of the residue text.
public class CellFactory implements Callback<TreeView<QuizElement>, TreeCell<QuizElement>> {
...
#Override
public TreeCell<QuizElement> call(TreeView<QuizElement> treeView) {
TreeCell<QuizElement> cell = new TreeCell<QuizElement>() {
#Override
protected void updateItem(QuizElement item, boolean empty) {
super.updateItem(item, empty);
if (item == null) return;
if (empty) {
setText("");
} else {
textProperty().bind(item.getNameProperty());
}
}
};
cell.setOnDragDetected((MouseEvent event) -> dragDetected(event, cell));
cell.setOnDragOver((DragEvent event) -> dragOver(event, cell));
cell.setOnDragDropped((DragEvent event) -> drop(event, cell, treeView));
cell.setOnDragDone((DragEvent event) -> clearDropLocation());
return cell;
}
...
In the after collapsing picture I expected the area under the last openable element to be white.
I have a TableView that represents a calendar. Each cell is one day. and I want to add an event to the cells. When the cell is clicked, the background must be changed to red ... it should be possible to select more than one cell
If it's just selection that you need you can use a selection model with multiple selection an cell selection mode.
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.getSelectionModel().setCellSelectionEnabled(true);
CSS stylesheet
.table-cell {
-fx-selection-bar: red;
-fx-selection-bar-non-focused: red;
}
If you don't want to use the selection model, you need to store the data in your items and use a custom TableCell
public class DayCell extends TableCell<Week, Boolean> {
{
setOnMouseClicked(evt -> {
if (!isEmpty() && getItem() != null && evt.getButton() == MouseButton.PRIMARY) {
WritableValue<Boolean> property = (WritableValue<Boolean>) getTableColumn().getCellObservableValue((Week) getTableRow().getItem());
property.setValue(!getItem());
}
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setStyle(null);
} else {
setStyle(item ? "-fx-background-color: red;" : null);
}
}
}
column.setCellFactory(c -> new DayCell());
This requires the ObservableValues returned by the cellValueFactory to implement WritableValue<Boolean> in a way that stores the data in table item class. Using a BooleanProperty stored in this class would do the trick.
I don't think it's a good idea to use TableView in this case though. It's not designed to display a fixed size non-resizable grid.
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 am creating TableView in JavaFX. In which I want to show Context Menu in right click of mouse in tableView. So I am adding an EventHandler on table as given below :
TableView tableView=new TableView();
EventHandler event = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent me) {
if (me.getButton() == MouseButton.SECONDARY) {
tableView.getContextMenu().show(tableView, me.getSceneX(), me.getSceneY());
}
}
};
tableView.addEventHandler(MouseEvent.MOUSE_CLICKED, event);
But my problem is that Context Menu is visible wherever I right click on any part of table.
I want to do that Context Menu should be only visible if I clicked on any rows in TableView.
i.e. How would I get row number in TableView at specific point, So that my Context Menu should be only visible, if I clicked on any row of TableView.
The best solution I found was to check if the y coordinate is outside of the bounds of the column header and then to explicitly show the menu.
ContextMenu visibleMenu = null;
tableView.setOnMouseClicked((MouseEvent e) -> {
if (visibleMenu !=null) {
visibleMenu.hide();
visibleMenu = null;
}
if (e.getButton()==MouseButton.SECONDARY) {
double columnHeaderHeight = tableView.lookup(".column-header-background").getBoundsInLocal().getHeight();
if (e.getY()>columnHeaderHeight) {
visibleMenu = getContextMenu(); // build on the fly or use a prebuild menu
visibleMenu.show(tableView, e.getScreenX(), e.getScreenY());
} else {
// you could show a header specific context menu here
}
}
});
The added benefit is that you can build the context menu on the fly with context sensitive items (that for example only appear if a certain type of cell is selected), or just reuse a prebuild contextmenu as setContextMenu does, up to you.
Add context menu to the specific cells using CellFactory not to the whole table.
E.g. using Table from Oracle tutorial:
TableColumn firstNameCol = new TableColumn();
firstNameCol.setText("First");
firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
firstNameCol.setCellFactory(new Callback<TableColumn, TableCell>() {
#Override
public TableCell call(final TableColumn param) {
final TableCell cell = new TableCell() {
#Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
if (isEditing()) {
setText(null);
} else {
setText(getItem().toString());
setGraphic(null);
}
}
}
};
// This way I will have context menu only for specific column
cell.setContextMenu(ContextMenuBuilder.create().items(MenuItemBuilder.create().text("menu").build()).build());
return cell;
}
});
may be the older question. There is a solution, like getting the target of the mouse event of the table and check for instance for class TableCellSkin and display the context menu as,
table.addEventHandler(MouseEvent.MOUSE_CLICKED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getButton() == MouseButton.SECONDARY
&& !isRowEmpty) {
EventTarget target = e.getTarget();
if (target instanceof TableCellSkin
|| ((Node) target).getParent() instanceof TableCellSkin) {
// do your stuff. Context menu will be displayed by default
} else {
// hide the context menu when click event is outside table row
table.getContextMenu().hide();
}
}
}
});
#FXML
void tableContextMenuRequested(ContextMenuEvent event) {
if (tableview.getSelectionModel().getSelectedItems().size() == 0) {
tableContextMenu.hide();
}
}