JavaFX TabPane fails to render when inside ScrollPane and zoomed Group - javafx

I need a bunch of widgets, including a TabPane, inside a scrollable and zoomable view, basically this:
ScrollPane[ Group[ widgets .. including TabPane ] ]
The ScrollPane is obviously needed for scrolling, and the Group holds all the widgets and supports zooming.
The initial problem with that approach is that the ScrollPane shows scroll bars based on the original size of the widgets, not based on the actual size.
In the screenshot, note how scrollbars are shown even though the tab pane is much smaller than the viewport, so no scrollbars are needed.
The web site https://pixelduke.wordpress.com/2012/09/16/zooming-inside-a-scrollpane explains how to solve that by adding another nested Group:
ScrollPane[ Group[ Group[ widgets .. including TabPane ] ] ]
The inner Group, as before, holds all the widgets and supports zooming.
The outer Group automatically gets the layout bounds of the zoomed
widgets in the inner group, allowing the ScrollPane to correctly configure the scroll bars.
.. but now the TabPane will fail to properly draw itself.
All you see is the red background of the TabPane:
The complete tab pane only shows up once it's somehow forced to refresh.
The example code toggles the 'side' property of the tab pane when you press 'SPACE'.
Now I have it all: Tab Pane draws OK, inner group can be zoomed, scroll bars appear as soon as the zoomed content no longer fits the viewport. But having to force the Tab Pane refresh is certainly a hack.
Is there a fault in my scene graph?
Is this a bug in the TabPane rendering?
The problem certainly seems limited to the TabPane. When I add other groups, rectangles, buttons, text nodes to the 'widgets' in the inner group, they all render fine. Only the TabPane refuses to show its tabs.
Tried this with both JDK 1.8.0_51 and 1.8.0_73, also tried on Windows, Linux and Mac OS X.
import javafx.application.Application;
import javafx.geometry.Side;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class TabDemo extends Application
{
#Override
public void start(final Stage stage)
{
// TabPane with some tabs
final TabPane tabs = new TabPane();
tabs.setStyle("-fx-background-color: red;");
for (int i=0; i<3; ++i)
{
final Rectangle rect = new Rectangle(i*100, 100, 10+i*100, 20+i*80);
rect.setFill(Color.BLUE);
final Pane content = new Pane(rect);
final Tab tab = new Tab("Tab " + (i+1), content);
tab.setClosable(false);
tabs.getTabs().add(tab);
}
tabs.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
tabs.setPrefSize(400, 300);
final Group widgets = new Group(tabs);
widgets.setScaleX(0.5);
widgets.setScaleY(0.5);
final Group scroll_content = new Group(widgets);
final ScrollPane scroll = new ScrollPane(scroll_content);
final Scene scene = new Scene(scroll);
stage.setTitle("Tab Demo");
stage.setScene(scene);
stage.show();
// Unfortunately, the setup of ScrollPane -> Group -> Group -> TabPane
// breaks the rendering of the TabPane.
// While the red background shows the area occupied by TabPane,
// the actual Tabs are missing..
System.out.println("See anything?");
scene.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) ->
{
if (event.getCode() == KeyCode.SPACE)
{ // .. until 'side' or 'tabMinWidth' or .. are twiddled to force a refresh
tabs.setSide(Side.BOTTOM);
tabs.setSide(Side.TOP);
System.out.println("See it now?");
}
});
}
public static void main(String[] args)
{
launch(args);
}
}

Related

Why strange painting behavior in JavaFX

I have a simple FX example with a simple component.
package fxtest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
var bp = new BorderPane();
var r = new Rectangle(0, 0, 200, 200);
r.setFill(Color.GREEN);
var sp = new StackPane(r);
bp.setCenter(sp);
bp.setTop(new XPane());
bp.setBottom(new XPane());
bp.setLeft(new XPane());
bp.setRight(new XPane());
var scene = new Scene(bp, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
package fxtest;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
public class XPane extends Region {
public XPane() {
setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
setMinSize(100, 100);
setPrefSize(100, 100);
widthProperty().addListener((o) -> {
populate();
});
heightProperty().addListener((o) -> {
populate();
});
populate();
}
private void populate() {
ObservableList<Node> children = getChildren();
Rectangle r = new Rectangle(getWidth(), getHeight());
r.setFill(Color.WHITE);
r.setStroke(Color.BLACK);
children.add(r);
Line line = new Line(0, 0, getWidth(), getHeight());
children.add(line);
line = new Line(0, getHeight(), getWidth(), 0);
children.add(line);
}
}
When run, it does what I expect:
When I grow the window, the X's grow.
But when I shrink the window, I get artifacts of the side panels.
I would have thought erasing the backgrounds would have fixed this, but I guess there's some ordering issue. But even still, when you drag the corner, all of the XPanes change size, and they all get repainted, but the artifacts remain.
I tried wrapping the XPanes in to a StackPane, but that didn't do anything (I didn't think it would, but tried it anyway).
How do I remedy this? This is JavaFX 13 on JDK 16 on macOS Big Sur.
Why you get artifacts
I think a different approach should be used rather than fixing the approach you have, but you could fix it if you want.
You are adding new rectangles and lines to your XPane in listeners. Every time the height or width changes, you add a new set of nodes, but the old set of nodes at the old height and widths remains. Eventually, if you resize enough, performance will drop or you will run out of memory or resources, making the program unusable.
A BorderPane paints its children (the center and the XPanes) in the order they were added without clipping, so these old lines will remain and the renderer will paint them over some panes as you resize. Similarly, some panes will paint over some lines because you are building up potentially lots of filled rectangles in the panes and they are partially overlapping lots of lines created.
To fix this, clear() the child node list in your populate() method before you add any new nodes.
private void populate() {
ObservableList<Node> children = getChildren();
children.clear();
// now you can add new nodes...
}
Alternate Solution
Change listeners on widths and heights aren't really the place to add content to a custom region, IMO.
I think that it is best to take advantage of the scene graph and let it handle the repainting and updating of existing nodes after you change the attributes of those nodes, instead of creating new nodes all the time.
Here is an example that subclasses Region and paints fine when a resize occurs.
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
public class XPane extends Region {
public XPane() {
super();
Rectangle border = new Rectangle();
Line topLeftToBottomRight = new Line();
Line bottomLeftToTopRight = new Line();
getChildren().addAll(
border,
topLeftToBottomRight,
bottomLeftToTopRight
);
border.setStroke(Color.BLACK);
border.setFill(Color.WHITE);
border.widthProperty().bind(
widthProperty()
);
border.heightProperty().bind(
heightProperty()
);
topLeftToBottomRight.endXProperty().bind(
widthProperty()
);
topLeftToBottomRight.endYProperty().bind(
heightProperty()
);
bottomLeftToTopRight.startYProperty().bind(
heightProperty()
);
bottomLeftToTopRight.endXProperty().bind(
widthProperty()
);
setMinSize(100, 100);
setPrefSize(100, 100);
}
}
On Region vs Pane
I'm not sure if you should be subclassing Pane or Region, the main difference between the two is that a Pane has a public accessor for a modifiable child list, but a Region does not. So it would depend on what you are trying to do. If it is just drawing X's like the example, then Region is appropriate.
On layoutChildren() vs binding
The Region documentation states:
By default a Region inherits the layout behavior of its superclass,
Parent, which means that it will resize any resizable child nodes to
their preferred size, but will not reposition them. If an application
needs more specific layout behavior, then it should use one of the
Region subclasses: StackPane, HBox, VBox, TilePane, FlowPane,
BorderPane, GridPane, or AnchorPane.
To implement a more custom layout, a Region subclass must override
computePrefWidth, computePrefHeight, and layoutChildren. Note that
layoutChildren is called automatically by the scene graph while
executing a top-down layout pass and it should not be invoked directly
by the region subclass.
Region subclasses which layout their children will position nodes by
setting layoutX/layoutY and do not alter translateX/translateY, which
are reserved for adjustments and animation.
I am not actually doing that here, instead, I am binding in the constructor rather than overriding layoutChildren(). You could implement an alternate solution that operates as the documentation discusses, overriding layoutChildren() rather than using binding, but it is more complicated and less well documented on how to do that.
It is uncommon to subclass Region and override layoutChildren(). Instead, usually, a combination of standard layout Panes will be used and constraints set on the panes and nodes to get the desired layout. This lets the layout engine do a lot of the work such as snapping to pixels, calculating margins and insets, respecting constraints, repositioning content, etc, a lot of which would need to be done manually for a layoutChildren() implementation.
One common approach is to bind the relevant geometric properties to the desired properties of the enclosing container. A related example is examined here, and others are collected here.
The variation below binds the vertices of several Shape instances to the Pane width and height properties. Resize the enclosing stage to see how the BorderPane children conform to entries in the BorderPane Resize Table. The example also adds a red Circle, which stays centered in each child, growing and shrinking in the center to fill the smaller of the width or height. The approach relies on the fluent arithmetic API available to properties that implement NumberExpression or methods defined in Bindings.
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2));
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
/**
* #see https://stackoverflow.com/q/70311488/230513
*/
public class App extends Application {
#Override
public void start(Stage stage) {
var bp = new BorderPane(new XPane(), new XPane(),
new XPane(), new XPane(), new XPane());
stage.setScene(new Scene(bp, 640, 480));
stage.show();
}
private static class XPane extends Pane {
private final Rectangle r = new Rectangle();
private final Circle c = new Circle(8, Color.RED);
private final Line line1 = new Line();
private final Line line2 = new Line();
public XPane() {
setPrefSize(100, 100);
r.widthProperty().bind(this.widthProperty());
r.heightProperty().bind(this.heightProperty());
r.setFill(Color.WHITE);
r.setStroke(Color.BLACK);
getChildren().add(r);
line1.endXProperty().bind(widthProperty());
line1.endYProperty().bind(heightProperty());
getChildren().add(line1);
line2.startXProperty().bind(widthProperty());
line2.endYProperty().bind(heightProperty());
getChildren().add(line2);
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2));
getChildren().add(c);
}
}
public static void main(String[] args) {
launch();
}
}

JavaFX TabPane width and Button width donĀ“t match

While I was trying to use the TabPane container in JavaFX I noticed, that if I give the Tabs in the TabPane a specific width and another element e.g. a Button the exact same width, they show up on the screen with different sizes.
Here is an example: As you can see in this image, the Button is smaller than the width of the tap
Here is the Code I for this specific image:
package stackOverflow;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TabPaneMystery extends Application {
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
StackPane stackPane = new StackPane();
Scene scene = new Scene(stackPane, 600, 400);
stage.setScene(scene);
stage.show();
TabPane tabPane = new TabPane();
stackPane.getChildren().add(tabPane);
Tab tab1 = new Tab();
tabPane.setTabMaxWidth(160); //The width of the tab is '160'
tabPane.setTabMinWidth(160);
tabPane.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
tab1.setText("Tab");
tabPane.getTabs().add(tab1);
Button b = new Button();
b.setText("Button");
b.setPrefWidth(160); //The width of the button is '160'
b.setTranslateX(6);
tab1.setContent(b);
}
}
in Line 33 and 34 (where the first comment is) I set the width of the tapPane to be 160 and in Line 42 (where the second comment is) I set the exact same width
This results in the shown image.
My question is:
Have I made an obvious mistake or is it some kind of bug or does the compiler interpret the width for every node somewhat different?
I suspect you're seeing some padding in the Tab. The actual width of the Tab comes out to 170. You can confirm this by changing the width of the Tab to 150; it will then be the exact same size as the Button:
You could also use CSS to remove the padding:
tab1.setStyle("-fx-padding: 0");
Although, as you can see below, it isn't an exact match and there may be other CSS properties to look into. Hope this gets you closer to your goal, though:

Using javafx using circle.setCenterX and circle.setCenterY not working

I am working through some coursework and am running into an odd issue. I'm working with javafx learning how to build shapes and work with alignment. Anyway my circle object will not respond to setCenterX or setCenterY commands (the radius definition statement does work) in the original definition statements nor in the commands issued by my event handlers which should be redefining these set x and set y values. I cannot figure out why. Please see my code below. When working correctly my code would allow me to move the circle object around the screen with the buttons and event handlers I've created. If I can figure out why the setCenterX and setCenterY don't work, I'm sure I can get the rest. Thanks for your help in advance.
package bravo15;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.scene.Scene;
public class FifteenDotThreeVersionThree extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
Circle circle = new Circle();
circle.setCenterX(300);
circle.setCenterY(300);
circle.setRadius(50);
// Hold four buttons in an HBox
// Define hbox
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER);
// define buttons
Button btLeft = new Button("Left");
Button btRight = new Button("Right");
Button btUp = new Button("Up");
Button btDown = new Button("Down");
// add defined buttons into the hbox
hBox.getChildren().add(btLeft);
hBox.getChildren().add(btRight);
hBox.getChildren().add(btUp);
hBox.getChildren().add(btDown);
// Create and register the handlers for the four buttons
btLeft.setOnAction(e -> circle.setCenterX(circle.getCenterX() - 10));
btRight.setOnAction(e -> circle.setCenterX(circle.getCenterX() + 10));
btUp.setOnAction(e -> circle.setCenterY(circle.getCenterY() + 10));
btDown.setOnAction(e -> circle.setCenterY(circle.getCenterY() - 10));
BorderPane borderPane = new BorderPane();
borderPane.setTop(circle);
borderPane.setBottom(hBox);
BorderPane.setAlignment(hBox, Pos.CENTER);
// Create a scene and place it in the stage
Scene scene = new Scene(borderPane, 200, 200);
primaryStage.setTitle("ControlCircle Version 3"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
A BorderPane manages the layout of its components, so it positions the circle for you by setting its layoutX and layoutY properties so that it appears at the top left.
Wrap it in a Pane, which performs no layout, and place the Pane in the top of the border pane:
borderPane.setTop(new Pane(circle));
Note that you have things set up so that it is initially off-screen. You probably want to increase the size of the scene:
Scene scene = new Scene(borderPane, 600, 600);
You can do it this way to shift the circle:
btLeft.setOnAction(e -> circle.setTranslateX(circle.getTranslateX() - 10));
btRight.setOnAction(e -> circle.setTranslateX(circle.getTranslateX() + 10));
btUp.setOnAction(e -> circle.setTranslateY(circle.getTranslateY() - 10));
btDown.setOnAction(e -> circle.setTranslateY(circle.getTranslateY() + 10));
setTranslateX():
Defines the x coordinate of the translation that is added to this
Node's transform. The node's final translation will be computed as
layoutX + translateX, where layoutX establishes the node's stable
position and translateX optionally makes dynamic adjustments to that
position. This variable can be used to alter the location of a node
without disturbing its layoutBounds, which makes it useful for
animating a node's location.
And it looks better with borderPane.setCenter(circle); than borderPane.setTop(circle);.
I have also removed the following lines:
circle.setCenterX(300);
circle.setCenterY(300);

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.

Resources