Capturing MouseEnter event in a Tab [Javafx] - javafx

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);
}
}

Related

JavaFx 11 ListView consumes ESCAPE key pressed event even if is not in editing state

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();
}
} );

JavaFx Overlapping mouse events

I’m trying to build a board game interface where the user can switch between multiple eras, each one with its own board. To do so, I’m creating 4 different board, each within its own pane, and I’m toggling the nodes Visibility and disabling the nodes that aren’t being used. The problem I have is the mouse event handlers I’m using to see where the user is clicking only work on the top layer, the last one that was rendered. The event Handlers underneath don’t work even if they are enabled.
Here’s what I wrote:
static EventHandler<MouseEvent> eventMouseClickRoad = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
final Shape innerShape = (Shape) (e.getTarget());
System.out.println("click");
Color color = (Color) innerShape.getFill();
if(color.getOpacity() != 1)
{
innerShape.setFill(Color.RED);
//and do the data treatment
}
}
};
public void boardControler(Vector2DList sideList,PointList hexEdge,Pane groupPane,float scaleX, float scaleY, float buttonSize)
{
//set road button
for(Vector2D v : sideList.getVectorList()){
Path mypath = new Path(new MoveTo(v.getP1().getX(),v.getP1().getY()),new LineTo(v.getP2().getX(),v.getP2().getY()));
groupPane.getChildren().add(mypath);
}
for(Vector2D v : sideList.getVectorList()){
float midX=(v.getP1().getX()+v.getP2().getX())/2;
float diffY=v.getP1().getY()-v.getP2().getY();
float diffX=v.getP1().getX()-v.getP2().getX();
Rectangle rectangle = new Rectangle(midX-buttonSize/2,midY-Math.abs(diffY)+buttonSize+(Math.abs(diffY)-scaleY/4),buttonSize,(scaleY/2)-(buttonSize*2));
rectangle.setRotate(Math.toDegrees(Math.atan(diffY/diffX))+90);
rectangle.setFill(Color.TRANSPARENT);
rectangle.addEventFilter(MouseEvent.MOUSE_ENTERED, Event.eventMouseEntered);
rectangle.addEventFilter(MouseEvent.MOUSE_EXITED, Event.eventMouseExit);
rectangle.addEventFilter(MouseEvent.MOUSE_CLICKED, Event.eventMouseClickRoad);
groupPane.getChildren().add(rectangle);
}
}
And this is what i use to toggle the board that's being used:
to disable
for(Node n : groupPane2.getChildren())
{
n.setDisable(true);
n.setManaged(false);
n.setVisible(false);
}
to enable
for(Node n : groupPane2.getChildren())
{
n.setDisable(false);
n.setManaged(true);
n.setVisible(true);
}
Perhaps using a StackPane would be the solution here. Your question doesn't include much code to show all of your context, but the MCVE below may help to demonstrate the idea.
Basically, we create a StackPane as our root display container for all of your boards. Your "boards" can be anything, a Pane, another StackPane, or a VBox like in my example. This should allow you to continue using whatever layout system you currently are.
One thing to note, it appears that each board will need to have a background set, or the lower boards will show through and may accept mouse events.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class StackPaneSample extends Application {
public static void main(String[] args) {
launch(args);
}
private static StackPane stackPane = new StackPane();
#Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Create our StackPane
stackPane.setStyle("-fx-border-color: black");
VBox.setVgrow(stackPane, Priority.ALWAYS);
// Let's create 3 "boards" for our StackPane. A background color seems necessary to hide layers below the top one
VBox board1 = new VBox() {{
setStyle("-fx-background-color: whitesmoke");
setAlignment(Pos.CENTER);
setUserData("Board #1");
getChildren().add(new Label((String) getUserData()));
}};
VBox board2 = new VBox() {{
setStyle("-fx-background-color: whitesmoke");
setAlignment(Pos.CENTER);
setUserData("Board #2");
getChildren().add(new Label((String) getUserData()));
}};
VBox board3 = new VBox() {{
setStyle("-fx-background-color: whitesmoke");
setAlignment(Pos.CENTER);
setUserData("Board #3");
getChildren().add(new Label((String) getUserData()));
}};
stackPane.getChildren().add(board1);
stackPane.getChildren().add(board2);
stackPane.getChildren().add(board3);
// Create three buttons that will switch between the boards
Button btnBoard1 = new Button("Board #1");
Button btnBoard2 = new Button("Board #2");
Button btnBoard3 = new Button("Board #3");
HBox hbButtons = new HBox(20) {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
getChildren().addAll(btnBoard1, btnBoard2, btnBoard3);
}};
// Finish out layout
root.getChildren().addAll(
stackPane,
new Separator(Orientation.HORIZONTAL),
hbButtons
);
// ** Now let's add our functionality **
// Print out which board has been clicked upon
// We need to first cast our List to VBox
for (Node vbox : stackPane.getChildren()) {
vbox.setOnMouseClicked(event -> System.out.println("Clicked on " + vbox.getUserData()));
}
// Set the buttons to set the top board
btnBoard1.setOnAction(event -> selectBoard(board1));
btnBoard2.setOnAction(event -> selectBoard(board2));
btnBoard3.setOnAction(event -> selectBoard(board3));
// Show the Stage
primaryStage.setWidth(400);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
// Method to remove the board and readd it, placing it on top of all others.
private static void selectBoard(VBox board) {
stackPane.getChildren().remove(board);
stackPane.getChildren().add(board);
}
}
The Result:
I am, admittedly, not familiar with the Cartesian coordinates you mention in your comment, so perhaps this won't work for you. Adding more code/context to your question might help us narrow down the issue better.

Getting a MouseEvent to target ImageViews inside a TilePane

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);
}
}

JavaFX ContextMenu doesn't auto-hide

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.

JavaFX: SplitPane Mouse Handling Breaks After Displaying an Application-Modal Stage

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

Resources