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();
}
}
Related
I am fairly new in JavaFX. I have a table having multiple columns and a refresh button in each row. I am trying to set the old value in the edited cells whenever I click Refresh button. One idea is Passing the old value through a global variable to refresh button and set it. I can get the old value But how can I set that old value? here is my code
String old=null;
public void initialize(URL arg0, ResourceBundle arg1) {
colName.setCellValueFactory(new PropertyValueFactory<ModelBrBuilding,String>("BranchName"));
colName.setCellFactory(TextFieldTableCell.forTableColumn());
colName.setOnEditCommit(
new EventHandler<CellEditEvent<ModelBrBuilding, String>>() {
#Override
public void handle(CellEditEvent<ModelBrBuilding, String> t) {
old= ((ModelBrBuilding) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).getBranchName();
((ModelBrBuilding) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setBranchName(t.getNewValue());
}
}
);
colAction.setCellFactory(col -> {
Button RefreshButton = new Button("Refresh");
hbox.getChildren().add(RefreshButton);
TableCell<ModelBrBuilding, ModelBrBuilding> cell = new TableCell<ModelBrBuilding, ModelBrBuilding>() {
#Override
//Updating with the number of row
public void updateItem(ModelBrBuilding building, boolean empty) {
super.updateItem(building, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(RefreshButton);
}
}
};
RefreshButton.setOnAction(event->{
//here I need to set the old value
});
return cell ;
});
Can any one give me idea how can I do that?
Finally, I have found my answer which is working
RefreshButton.setOnAction(event -> {
ModelBrBuilding buildin = new ModelBrBuilding();
int i = tableBuilding.getSelectionModel().getSelectedIndex();
buildin.setBranchName(old);
tableBuilding.getItems().set(i, buildin);
});
I have a TableView with 2 columns “Date” (LocalDate) and “FX” (Double). I have enabled the cell editing and following an example I found here (http://physalix.com/javafx8-render-a-datepicker-cell-in-a-tableview/) I have created a custom CellFactory that displays a DatePicker for the cells of column “Date”. This solution though renders the DatePciker immediately, so I changed my code to show the DatePicker only when the user double clicks on any of the (non-empty) Date cells. So far so good…
How do I “go back” and remove the DatePicker rendering from the cell after the user has changed the date or cancelled the input? See the pictures as reference. Pic 1 is the initial state of the list. Pic 2 is after double click. How do I go back to Pic 1 status? Let me know if you need to see my specific code.
Reference pictures
This is the code that checks for the double click and then creates the CellFactory
fxTable.getSelectionModel().setCellSelectionEnabled(true);
fxTable.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
TablePosition pos = fxTable.getSelectionModel().getSelectedCells().get(0);
int col = pos.getColumn();
if (col == 0) {
//The code below creates the DatePicker in the cell using the DatePickerCell class that I created following the example in the code I found
tblDateFX.setCellFactory(new Callback<TableColumn<Map.Entry<LocalDate, Double>, String>, TableCell<Map.Entry<LocalDate, Double>, String>>() {
#Override
public TableCell<Map.Entry<LocalDate, Double>, String> call(TableColumn<Map.Entry<LocalDate, Double>, String> param) {
ObservableMap<LocalDate, Double> items = FXCollections.observableMap(myBasket.getEnrtriesCur(curName));
DatePickerCell datePick = new DatePickerCell(items);
return datePick;
}
});
}
}
}
});
This is the DatePickerCell Class
public class DatePickerCell<S, T> extends TableCell<Map.Entry<LocalDate,Double>, String> {
private DatePicker datePicker;
private ObservableMap<LocalDate,Double> curEntries;
public DatePickerCell(ObservableMap<LocalDate,Double> curEntries) {
super();
this.curEntries = curEntries;
if (datePicker == null) {
createDatePicker();
}
setGraphic(datePicker);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
Platform.runLater(new Runnable() {
#Override
public void run() {
datePicker.requestFocus();
}
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (null == this.datePicker) {
System.out.println("datePicker is NULL");
}
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
setContentDisplay(ContentDisplay.TEXT_ONLY);
} else {
datePicker.setValue(LocalDate.parse(item,df));
setGraphic(this.datePicker);
setText(item);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
}
#Override
public void startEdit() {
super.startEdit();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
setGraphic(null);
}
private void createDatePicker() {
this.datePicker = new DatePicker();
datePicker.setEditable(true);
datePicker.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
setGraphic(datePicker);
setText(df.format(datePicker.getValue()));
}
});
datePicker.setOnAction(new EventHandler() {
public void handle(Event t) {
LocalDate date = datePicker.getValue();
int index = getIndex();
commitEdit(df.format(date));
if (null != getCurEntries()) {
System.out.println("Modify value");
}
}
});
setAlignment(Pos.CENTER);
}
Have you tried the function setOnEditCommit to do reverse of your code?
column.setOnEditCommit((TableColumn.CellEditEvent<MyObject, Date> t) -> {
//modify the rendering of you cell to normal
});
After some research I found out that the default rendering of a cell in a TableView is a label. So I tweaked the DatePickerCell class to render a label in the "updateItem" method and render the DatePicker only when the label is clicked (meaning that the user wants to edit the date in the cell).
In terms of "going back" I added a listener for "ESC keypressed" on the DatePicker so when that key is pressed (during the edit) a label is rendered and the edit is therefore cancelled. That works quite well!
I'm still trying to figure out how to do the same when the user tries to cancel the edit by clicking somewhere else on the screen.
--
So here's my stab at the DatePickerEdit class.
This is doing what I need. Renders the cells normally at first, only when the user clicks on the date cell the datepicker is rendered. If the user clicks away from the cell, the cell goes back to its initial rendering (same happens when "ESC" is pressed whilst editing or indeed a new date is picked).
Note that I am passing to the class the Observable list that contains the values shown in the TableView. In this way I can update the value in the list directly in the class. Not sure if this is a good practice or not, this was a "forced solution" though. Originally I used the "setOnEditCommit" method for the TableColumn but after some testing I noticed that this event is not always called after the cell is updated (i.e. the commitEdit() method is called for the cell). Not sure if this is a bug or there's something wrong in my code. For sure it does not always happen. On multiple runs, I would say that 1 out of 3 showed this bugged behaviour.
Here's the code, not sure if it's a "good" code or not. I would appreciate any advice in merit.
public class DatePickerCell<S, T> extends TableCell<FX, String> {
private DatePicker datePicker;
private Label lbl;
private ObservableList<FX> currencies;
public DatePickerCell(ObservableList<FX> list) {
super();
lbl=new Label();
this.currencies=list;
if (datePicker == null) {
createDatePicker();
}
}
#Override
public void updateItem(String item, boolean empty) {
// This section here manages the graphic rendering of each cell
// As I don't want to generate the datepicker graphics immediately I just render a label
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
createLabel(item);
}
}
#Override
public void startEdit() {
super.startEdit();
}
#Override
public void cancelEdit() {
super.cancelEdit();
}
private void createDatePicker() {
this.datePicker = new DatePicker();
datePicker.setEditable(true);
// when the user clicks on the label the DatePicker graphics is generated
lbl.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
datePicker.setValue(LocalDate.parse(lbl.getText(),df));
setGraphic(datePicker);
setText(lbl.getText());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
datePicker.requestFocus();
}
});
// This listener manages the "lost focus" on the picker
datePicker.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// This combination of OldValue NewValue is generated whenever there is a click outside the DatePicker "graphic area"
// i.e. the calendar (when open), the text filed, the calendar icon OR when a NEW date is selected in the calendar.
// This last case generates the "OnAction" event as well that is managed below.
if (oldValue && !newValue) {
createLabel(df.format(datePicker.getValue()));
}
}
});
// This is generated when a NEW date is picked
// it simply commits the new date and changes the graphics back to a label
datePicker.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
LocalDate date = datePicker.getValue();
int index=getIndex();
if (date!=null) {
commitEdit(df.format(date));
getCurrencies().get(index).setDate(date);
createLabel(df.format(date));
}
}
});
// added this listener in case the user wants to cancel pressing "ESC"
// when this happens the label graphics is rendered
datePicker.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
LocalDate date = datePicker.getValue();
if (event.getCode()== KeyCode.ESCAPE) {
createLabel(df.format(date));
}
}
});
setAlignment(Pos.CENTER_LEFT);
}
private void createLabel(String item) {
lbl.setMinWidth(getWidth());
setGraphic(lbl);
lbl.setText(item);
}
public ObservableList<FX> getCurrencies() {
return currencies;
}
}
I'm designing an application that allows a drag and drop on TableView cells.
However, once the drag and drop is complete and the values of the cells switched successfully, a double-click on the cells where drag and drop has been effected makes the cells change back to their original text before drag and drop.
Help me solve this issue. The following code is used to set cellFactoryValue for all the cells. I am new to javafx.
private void setCellFactoryForColumns(TableColumn<TimeTable, String> col)
{
col.setCellFactory((TableColumn<TimeTable, String> e) -> {
TableCell<TimeTable, String> cell = new TableCell<TimeTable, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
setText(item);
setGraphic(null);
}
}
};
col.setOnEditCommit((CellEditEvent<TimeTable, String> t) -> {
((TimeTable)t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setDay(t.getNewValue());
});
cell.setOnDragDetected((MouseEvent event) -> {
Dragboard db = cell.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.putString(cell.getText());
db.setContent(content);
cell.setStyle("-fx-background-color: red;"
+ "-fx-text-fill: white;");
cell.startEdit();
cell.setText("Dragged");
event.consume();
});
cell.setOnDragOver((DragEvent event) ->{
if(event.getGestureSource() != cell &&
event.getDragboard().hasString())
{
event.acceptTransferModes(TransferMode.MOVE);
cell.setStyle("-fx-background-color: green;"
+ "-fx-text-fill: white;");
}
event.consume();
});
cell.setOnDragEntered((DragEvent event) ->{
if(event.getGestureSource() != cell &&
event.getDragboard().hasString()){
cell.setStyle("-fx-background-color: yellow;");
}
event.consume();
});
cell.setOnDragExited((DragEvent event) -> {
cell.setStyle("-fx-background-color: grey;");
event.consume();
});
cell.setOnDragDropped((DragEvent event) -> {
Dragboard db = event.getDragboard();
boolean success = false;
if(db.hasString())
{
holdData = cell.getText();
cell.setText(db.getString());
success = true;
}
event.setDropCompleted(success);
event.consume();
});
cell.setOnDragDone((DragEvent event) ->{
if(event.getTransferMode() == TransferMode.MOVE)
{
cell.setText(holdData);
}
event.consume();
});
return cell;
});
}
You just change the text in the cell, not the property of the item. Therefore every time a new item is assigned to the TableCell, the old value is used as text.
This also happens, if you scroll the cell containing a item out of view.
To fix this replace the
cell.setText(db.getString());
and
cell.setText(holdData);
statements in the onDragDone/onDragDropped handlers with
update(cell, db.getString());
and
update(cell, holdData);
respectively and implement the update method like this:
static void update(TableCell<TimeTable, String> cell, String newValue) {
((TableRow<TimeTable>)cell.getTableRow()).getItem().setDay(newValue);
// the below line would be needed, if the setter does not trigger
// a update of the ObservableValue returned by the cellValueFactory
// cell.getTableView().refresh();
}
In case your cellValueFactory returns a WritableProperty that allows writing to the item, the method can be written more general as
static <I,P>void update(TableCell<I, P> cell, P newValue) {
WritableValue<P> property = (WritableValue<P>) cell.getTableColumn().getCellObservableValue(((TableRow<I>)cell.getTableRow()).getItem());
property.setValue(newValue);
}
I am writting a little desktop application with a TreeView according to the Oracle-Example from here: https://docs.oracle.com/javafx/2/ui_controls/tree-view.htm.
From a MenuItem action of a ContextMenu, I would like to fire an event which shall create a new TreeItem below the item where I opened the ContextMenu from.
For MenuItem, it is possible to use the setOnAction(EventHandler<ActionEvent> event) method, but I only want to fire the action from a left mouse-click.
First, it is not possible to add an EventHandler to a MenuItem although it provides the method addEventHandler(EventType type, EventHandler<EventType> handler) with the event-type MouseEvent.ANY (or anything else). The handle-method of the event-handler is not called.
Second, i can use a workarround by adding a Label to a MenuItem by menuItem.setGraphic(label) and add an EventHandler to the label. This one works although MouseEvent.MOUSE_CLICKED is not called by an EventHandler's handle-method on a Label.
Is this "normal" behaviour? I understand that a label does not react on a click-event, but I do not understand why it is not possible to register a separate EventHandler or EventFilter on a MenuItem.
ContextMenu uses a MenuItemContainer, which is a
Container responsible for laying out a single row in the menu - in other
words, this contains and lays out a single MenuItem, regardless of it's
specific subtype.
Fur this purpose it seems to create new Nodes representing the MenuItem. So any EventHandlers added to the MenuItem will not be called.
To make it work as you intended, you can use a CustomMenuItem and add the according EventHandler to its content:
public class ContextMenuCell extends TreeCell<String> {
private ContextMenu menu;
public ContextMenuCell() {
Label lbl = new Label("Add item");
MenuItem menuItem = new CustomMenuItem(lbl);
lbl.setOnMouseClicked(evt -> {
if (evt.getButton() != MouseButton.PRIMARY) {
return;
}
TreeItem treeItem =
new TreeItem<String>("New item");
if (getTreeItem().isLeaf()) {
getTreeItem().getParent().getChildren().add(getIndex(), treeItem);
} else {
getTreeItem().getChildren().add(0, treeItem);
}
});
menu = new ContextMenu(menuItem);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(item);
setGraphic(getTreeItem().getGraphic());
setContextMenu(menu);
}
}
}
Menu and MenuItem are not Nodes, so they will not handle mouse clicks since they are not displayed on the screen. A workaround is to set a graphics object (Node) to the MenuItem and add the listener to this Node. Works also for other menus like CheckMenuItem etc.:
public class RunJavaFX extends Application {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
//the label will be our graphics object (Node)
Label l = new Label("Your Menu Text");
l.setTextFill(Color.BLACK); //set black since default CSS Style sets it to background color of the Menu
//add either over addEventFilter or addEventHandler
l.addEventFilter(MouseEvent.MOUSE_PRESSED, ev -> {
if (ev.getButton() == MouseButton.SECONDARY) {
System.out.println("RightClick: " + ev.getSource() + System.nanoTime());
} else {
System.out.println("Not Right Click: " + ev.getSource() + System.nanoTime());
}
ev.consume(); //optional
});
//create the MenuItem with an empty text and set the label l as graphics object
MenuItem mI = new MenuItem("", l);
//create the dummy menu and MenuBar for the example
Menu m = new Menu("Menu");
m.getItems().add(mI);
MenuBar mB = new MenuBar(m);
//create the dummy scene for the example
Scene scene = new Scene(mB);
primaryStage.setScene(scene);
primaryStage.show();
}
}
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.