I have a JavaFX ContextMenu assigned to the right mouse button click of a scrollpane. It opens, but it doesn't close when you click outside the scrollpane. I could add another mouse event to the scrollpane in order to hide it, but that solves only 1 problem. The main problem is that when I click on any component of the scrollpane, then the context menu remains open.
Example: Open popup via right mouse button click, then click on the button. The popup menu is still open.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final ContextMenu contextMenu = new ContextMenu();
MenuItem item1 = new MenuItem("About");
MenuItem item2 = new MenuItem("Preferences");
contextMenu.getItems().addAll(item1, item2);
Rectangle rect = new Rectangle( 100,100,150,150);
Button button = new Button( "Button Text");
// create nodes
Group root = new Group();
root.getChildren().add( rect);
root.getChildren().add( button);
// create scrollpane
ScrollPane sp = new ScrollPane( root);
sp.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.isSecondaryButtonDown()) {
contextMenu.show( sp, event.getScreenX(), event.getScreenY());
}
}
});
// create scene
Scene scene = new Scene(sp, 400, 400, Color.WHITE);
// add scene to primary stage
primaryStage.setScene( scene);
primaryStage.show();
}
}
The documentation says that there's a setAutoHide method, but it doesn't work in my case:
Specifies whether Popups should auto hide. If a popup loses focus and
autoHide is true, then the popup will be hidden automatically. The
only exception is when owner Node is specified using
show(javafx.scene.Node, double, double). Focusing owner Node will not
hide the PopupWindow.
#defaultValue false
Thank you very much!
Interacting with child elements of the parent, will get a focus to that parent. So the context menu will not hide when the button in your code is clicked.
Try these two approaches:
1) Manually manage the visibility of context menu, i.e. hide it on button click:
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
contextMenu.hide();
}
});
2) Use setContextMenu() instead of showing the context menu on mouse press event:
sp.setContextMenu(contextMenu);
I know that this is old post, but for any newcomer I found a new solution. I have an jdk 1.8 and I have the same problem as you, but I have a dynamic generated context menu in TableView. So when you right click on the row I need another context menu by the row content. The key for my solution is that you execute show method in the context menu you pass on the window parameter to the method. Example of my code is below:
ContextMenu contextMenu = this.createContextMenu();
contextMenu.show(this.tableView.getScene().getWindow(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
And when I click to another location of my program, the context menu hide.
Related
I have a problem with JavaFx ListView component. I'm using popup with TextField and ListView inside of VBox. When TextField is in focus, I can normally close this popup window pressing the Esc key on the keyboard, but when ListView item is in focus popup stays open, nothing happens.
Minimal reproducible example:
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
MenuItem rightClickItem = new MenuItem("CLICK!");
rightClickItem.setOnAction(a -> showdialog());
ContextMenu menu = new ContextMenu(rightClickItem);
Label text = new Label("Right Click on me");
text.setContextMenu(menu);
StackPane root = new StackPane(text);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("RightClick MenuItem And Dialog");
primaryStage.setScene(scene);
primaryStage.show();
}
private void showdialog() {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
VBox vBox = new VBox();
ListView listView = new ListView();
listView.getItems().add("Item 1");
listView.getItems().add("Item 2");
vBox.getChildren().add(new TextField());
vBox.getChildren().add(listView);
vBox.addEventHandler(KeyEvent.KEY_PRESSED, keyEvent -> System.err.println("Key pressed: " + keyEvent.getCode()));
dialog.getDialogPane().setContent(vBox);
dialog.showAndWait();
}
public static void main(String[] args) {
launch(args);
}
}
It seems to me that Esc key is consumed in ListView, and this cause a problem with closing a popup.
Just to mention, I'm using zulu-11.0.8 JDKFx version.
It seems to me that Esc key is consumed in ListView, and this cause a problem with closing a popup.
That's indeed the problem - happens with all controls that have a consuming KeyMapping to ESCAPE added by their respective Behavior (f.i. also for a TextField with TextFormatter).
There is no clean way to interfere with it (Behavior and InputMap didn't yet make to move into public api). The way to hack around is to remove the KeyMapping from the Behavior's inputMap. Beware: you must be allowed to go dirty, that is use internal api and use reflection!
The steps:
grab the control's skin (available after the control is added to the scenegraph)
reflectively access the skin's behavior
remove the keyMapping from the behavior's inputMap
Example code snippet:
private void tweakInputMap(ListView listView) {
ListViewSkin<?> skin = (ListViewSkin<?>) listView.getSkin();
// use your favorite utility method to reflectively access the private field
ListViewBehavior<?> listBehavior = (ListViewBehavior<?>) FXUtils.invokeGetFieldValue(
ListViewSkin.class, skin, "behavior");
InputMap<?> map = listBehavior.getInputMap();
Optional<Mapping<?>> mapping = map.lookupMapping(new KeyBinding(KeyCode.ESCAPE));
map.getMappings().remove(mapping.get());
}
It's usage:
listView.skinProperty().addListener(ov -> {
tweakInputMap(listView);
});
To avoid using private API, you can use an event filter that, if the ListView is not editing, copies the Escape key event and fires it on the parent. From there, the copied event can propagate to be useful in other handlers such as closing a popup.
Also, if you need this on all ListViews in your application, you can do it in a derived class of ListViewSkin and set that as the -fx-skin for .list-view in your CSS file.
listView.addEventFilter( KeyEvent.KEY_PRESSED, keyEvent -> {
if( keyEvent.getCode() == KeyCode.ESCAPE && !keyEvent.isAltDown() && !keyEvent.isControlDown()
&& !keyEvent.isMetaDown() && !keyEvent.isShiftDown()
) {
if( listView.getEditingIndex() == -1 ) {
// Not editing.
final Parent parent = listView.getParent();
parent.fireEvent( keyEvent.copyFor( parent, parent ) );
}
keyEvent.consume();
}
} );
In my application I want to have the same menu item appear on both the system menu as well as a context menu. For example, the Copy menu item. In the context menu I also like to show the accelerator key (the same as on the system menu).
Here's where the problem occurs: if you set the accelerator on both menus, both menu items get fired. This is, of course, not what I want…
Is there a way to prevent this? My current work-around is to just not show the accelerators for the context menu, but this isn't really what I want either.
So is there any way of having the accelerator show in the menu but not fire?
Sample code with a context menu and system menu.
If you press CMD/CTRL-C it will fire both handlers :-(
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package test_menu;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author Hayo Baan
*/
public class Test_Menu extends Application {
#Override
public void start(Stage primaryStage) {
// Event handler
EventHandler<ActionEvent> eventHandler = (ActionEvent event) -> {
System.out.println("Got event from " + ((MenuItem) event.getSource()).getText());
event.consume();
};
// The system menubar
MenuBar menuBar = new MenuBar();
menuBar.setUseSystemMenuBar(true);
// Edit menu with Copy item
Menu editMenu = new Menu("Edit");
menuBar.getMenus().add(editMenu);
MenuItem editCopy = new MenuItem("Edit Copy");
editCopy.setAccelerator(KeyCombination.keyCombination("Shortcut+C"));
editMenu.getItems().add(editCopy);
editCopy.setOnAction(eventHandler);
// Context menu with copy item
ContextMenu contextMenu = new ContextMenu();
MenuItem contextCopy = new MenuItem("Context Copy");
contextCopy.setAccelerator(KeyCombination.keyCombination("Shortcut+C"));
contextMenu.getItems().add(contextCopy);
contextCopy.setOnAction(eventHandler);
Label label = new Label("Say 'Hello World'");
VBox root = new VBox();
root.getChildren().addAll(menuBar, label);
label.setContextMenu(contextMenu);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
What you have is two instances of EventHandlers that react on the same key combination. You can circumvent your issue by defining the EventHandler once, use if for both menu types and consume the event:
EventHandler evtHandler = event -> {
System.out.println("Execute copy event");
event.consume();
};
...
editCopy.setOnAction(evtHandler);
...
contextCopy.setOnAction(evtHandler);
I have many ImageViews inside a TilePane, that is inside a StackPane and then a ScrollPane. There is no border, padding, or margin between the children of the TilePane so there is no chance that I'm not clicking on an ImageView. When I click on an image, I want the target of the MouseEvent to be the ImageViews, but instead it is the TilePane.
How can I get the event chain to end on an ImageView instead of ending early on the TilePane?
Otherwise, is there a way I can get the ImageView using other information? Perhaps using the coordinates of the event?
The usual way I do this is just to register the mouse listener with the node in which I am interested; in your case this means register a mouse listener with each ImageView. It's easy then to have each mouse listener have a reference to the particular image view with which it's registered, or to other data (e.g. a filename) if you need.
One thing that might be happening: if your images have transparent pixels, then mouse clicks on that part of the image will by default "drop through" to the node below. You can change this behavior by calling imageView.setPickOnBounds(true); on the image views.
Some test code. If you run this you'll see some numbered images with different colored backgrounds. About 1 in 4 have transparent backgrounds (they appear white). If you click on these (but not on the actual text of the number), you'll see the mouse handlers registered with the scroll pane and stack pane have the tile pane as the target, and the handler registered with the ImageView is not even invoked. For those without the transparent background, the target is always the ImageView. If you select the check box, so pickOnBounds is true for all the ImageViews, both transparent and opaque images behave as you want.
import java.util.Random;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageViewClickTest extends Application {
private static final Random RNG = new Random();
#Override
public void start(Stage primaryStage) {
TilePane tilePane = new TilePane();
CheckBox pickOnBounds = new CheckBox("Pick on bounds");
pickOnBounds.setPadding(new Insets(16));
for (int i=1; i<=200; i++) {
ImageView imageView = createImageView(i);
imageView.pickOnBoundsProperty().bind(pickOnBounds.selectedProperty());
// mouse handler directly on image view:
// can access image-view specific data...
String message = "Clicked on Image "+i ;
imageView.setOnMouseClicked(e ->
System.out.println("From handler on ImageView: "+message));
tilePane.getChildren().add(imageView);
}
StackPane stack = new StackPane(tilePane);
stack.setOnMouseClicked(e -> {
// source will be the stack pane
// target will be the top-most node
// (i.e. the ImageView, in most cases)
System.out.println("From handler on stack pane: Source: "+e.getSource());
System.out.println("From handler on stack pane: Target: "+e.getTarget());
});
ScrollPane scroller = new ScrollPane(stack);
scroller.setFitToWidth(true);
scroller.setOnMouseClicked(e -> {
// source will be the scroll pane
// target will be the top-most node
// (i.e. the ImageView, in most cases)
System.out.println("From handler on scroller: Source: "+e.getSource());
System.out.println("From handler on scroller: Target: "+e.getTarget());
});
BorderPane root = new BorderPane(scroller, pickOnBounds, null, null, null);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private ImageView createImageView(int index) {
Label label = new Label(Integer.toString(index));
label.setAlignment(Pos.CENTER);
label.setMinSize(48, 48);
label.setStyle(randomStyle());
Image image = new Scene(label, Color.TRANSPARENT).snapshot(null);
ImageView imageView = new ImageView(image);
return imageView ;
}
private String randomStyle() {
StringBuilder style = new StringBuilder();
style.append("-fx-background-color: -fx-background;");
style.append("-fx-background: ");
if (RNG.nextDouble() < 0.25) {
style.append( "transparent;");
style.append(" -fx-text-fill: black;") ;
} else {
String bg = String.format("#%02x%02x%02x;",
RNG.nextInt(256), RNG.nextInt(256), RNG.nextInt(256));
style.append(bg);
}
return style.toString();
}
public static void main(String[] args) {
launch(args);
}
}
I would like to be able to capture the MOUSEENTER event when hovering over a tab.
I have tried to do it from the Graphic of the tab, which is not the optimal solution, but it is a Node object with such events.
This is what I wrote:
tab.getGraphic().setOnMouseEntered((MouseEvent event) -> {
System.out.println("..... mouse entered");
//...
});
This solution does not error but is ignored by Javafx, any way to do this?
UPDATE: The way to create the tab and add its graphic, is like the following excerpt. The tab itself works fine and the graphic displays fine.
Tab tab = addChatTab(root, strName, strID, chat, false);
// setup tab graphic
switch (win.type) {
case wtChat:
if (chat !=null)
if (chat.isPublic()) {
tab.setGraphic(new ImageView(Main.me.imgTabPublic));
} else {
if (chat.isDCC())
tab.setGraphic(new ImageView(Main.me.imgTabDCC));
else tab.setGraphic(new ImageView(Main.me.imgTabPrivate));
}
break;
case wtWall:
tab.setGraphic(new ImageView(Main.me.imgTabWall));
break;
case wtMessage:
tab.setGraphic(new ImageView(Main.me.imgTabMessage));
break;
}
If you set a mouse handler on a graphic, then the handler will only be invoked when the mouse interacts with the graphic itself. In this example, the first tab has both text and a graphic set, so the mouse handler is not invoked when the mouse moves onto the text. The second tab sets no text but uses a label as the graphic, with the label containing the text. In that case the mouse handler is invoked when the mouse moves onto the text or image.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class TabPaneHoverTest extends Application {
#Override
public void start(Stage primaryStage) {
// hover only applies on graphic:
Tab tab1 = new Tab("Tab 1");
tab1.setGraphic(new Rectangle(16, 16, Color.RED));
// Tab only uses graphic (no text),
// so hover appears to apply to whole tab:
Tab tab2 = new Tab();
Label tab2Graphic = new Label("Tab 2", new Rectangle(16, 16, Color.GREEN));
tab2.setGraphic(tab2Graphic);
tab1.getGraphic().setOnMouseEntered(e -> System.out.println("Hover on tab 1"));
tab2.getGraphic().setOnMouseEntered(e -> System.out.println("Hover on tab 2"));
BorderPane root = new BorderPane(new TabPane(tab1, tab2));
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Mouse handling on the SplitPane and ScrollBar controls breaks after displaying an application-modal Stage. The problem goes away after the application window loses and regains focus. Does anyone know a solution or workaround to this problem?
In what way is the mouse handling broken? When you click and start dragging on the control (SplitPane or ScrollBar), the control stops responding to your mouse movements the moment your mouse cursor leaves the control by a single pixel. This requires the user to be impossibly precise with the mouse. You would expect the control to respond to mouse movements, no matter where your mouse cursor happens to be, up until you release the mouse button.
The following code exhibits the problem on Ubuntu Linux and JRE 1.7.0_21. I have seen the problem on other JREs, but I have not tried another OS.
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.Modality;
public class SplitPaneBug extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Button button = new Button(
"Move the SplitPane divider, then click here to show the modal"
+ " dialog.");
button.setOnAction(
new EventHandler() {
public void handle(Event event) {
Stage dialog = new ModalDialog();
dialog.showAndWait();
}
});
button.setMaxWidth(Double.MAX_VALUE);
SplitPane splitPane = new SplitPane();
splitPane.getItems().setAll(new BorderPane(), new BorderPane());
VBox vbox = new VBox();
vbox.getChildren().setAll(button, splitPane);
vbox.setVgrow(splitPane, Priority.ALWAYS);
primaryStage.setTitle("SplitPane Bug?");
primaryStage.setScene(new Scene(vbox, 640, 480));
primaryStage.show();
}
class ModalDialog extends Stage {
public ModalDialog() {
Button button = new Button(
"Click here to dismiss this dialog, then move the SplitPane"
+ " divider again.");
button.setOnAction(
new EventHandler() {
public void handle(Event event) {
close();
}
});
BorderPane borderPane = new BorderPane();
borderPane.setCenter(button);
initModality(Modality.APPLICATION_MODAL);
setTitle("Modal Dialog");
setScene(new Scene(borderPane, 600, 100));
sizeToScene();
}
}
}
Are you sure that you use 7u21? Please, set to output VersionInfo.getRuntimeVersion().
I don't reproduce on my ubuntu 12.10 with jdk 7u21(b11) downloaded from official site,
but there is known bug in fx 8.0 - https://javafx-jira.kenai.com/browse/RT-29576