Reputation: 145
I'm looking for some guidance on how to proceed with a problem I'm having. I hava a JavaFX scene and within it some nodes (shapes) that connect to each other with one or more lines. I can right-click on a shape to bring up a context menu. Let's say this particular shape that was just right-clicked has 3 lines coming out of it (call them line1, line2, line3) and you want to use the context menu to delete one. You can select "line2" for example, and it will fire the onAction event to remove that line. That all works fine.
The trouble is, you don't know which of the 3 lines on the screen is line1 or line2 or line3 (unless of course they are labeled) and so you don't know which one you are about to remove until you remove it. What I would really like to do, for example, is to place my mouse over "line2" in the context menu and have line2 in the scene change color or something to indicate that it is the one about to be deleted (before I click the mouse). However, the only event I see supported by MenuItem is the onAction event for when it is clicked. Is there some way to give it onMouseOver functionality? if not, how could this feature be implemented?
Thanks!
Upvotes: 3
Views: 7794
Reputation: 49185
Try this SSCCE:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ContextMenuDemo extends Application {
private DropShadow ds = new DropShadow();
@Override
public void start(Stage primaryStage) {
final Line line1 = new Line(60, 10, 150, 10);
final Line line2 = new Line(60, 30, 150, 50);
final Line line3 = new Line(60, 60, 150, 90);
final ContextMenu cm = new ContextMenu();
cm.getItems().add(getMenuItemForLine("line 1", line1));
cm.getItems().add(getMenuItemForLine("line 2", line2));
cm.getItems().add(getMenuItemForLine("line 3", line3));
final Rectangle rectangle = new Rectangle(70, 70, Color.TAN);
rectangle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
if (e.getButton() == MouseButton.SECONDARY) {
cm.show(rectangle, e.getScreenX(), e.getScreenY());
}
}
});
Group root = new Group();
root.getChildren().addAll(rectangle, line1, line2, line3);
Scene scene = new Scene(root, 300, 250);
// load style of modified paddings for menuitems
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private MenuItem getMenuItemForLine(String menuName, final Line line) {
Label menuLabel = new Label(menuName);
// apply style to occupy larger space for label
menuLabel.setStyle("-fx-padding: 5 10 5 10");
MenuItem mi = new MenuItem();
mi.setGraphic(menuLabel);
line.setStroke(Color.BLUE);
menuLabel.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
line.setStroke(Color.RED);
line.setEffect(ds);
}
});
menuLabel.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
line.setStroke(Color.BLUE);
line.setEffect(null);
}
});
return mi;
}
public static void main(String[] args) {
launch(args);
}
}
with style.css
.menu-item {
/* -fx-skin: "com.sun.javafx.scene.control.skin.MenuItemSkin";*/
-fx-background-color: transparent;
-fx-padding: 0em; /* do not pad for item. we want to ccupy all spaces for graphics only */
}
.menu-item:focused {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-text-fill: -fx-selection-bar-text;
}
.menu-item .graphic-container {
-fx-padding: 0em; /* do not pad for graphics, label graphic pads itself */
}
.menu-item .label {
-fx-padding: 0em; /* do not pad for label, since there is no label text set */
-fx-text-fill: -fx-text-base-color;
}
Screenshot:
Description:
This is somewhat a bug that MenuItem does not work for MenuItem.addEventHandler(MouseEvent.MOUSE_ENTERED, ...)
I think. As a workaround, we define new Label
, register event handlers to it and set it as a graphic of menu item while the text(label) of menuitem intentionally left an empty. But the graphic of menu item does not (by default) occupy all space of menu item, so mouse events are not handled properly at the edges of menu item. To overcome this problem we reset all paddings of menuitem, menuitem's label and graphic through css. You can observe this by commenting out the style loading in the above code.
Upvotes: 2
Reputation: 866
Here is a sample App I just created on an aproach to identify the lines:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class MainTest extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
// TODO Auto-generated method stub
AnchorPane anchorPane = new AnchorPane();
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
Line linea = new Line(0, 0, 50, 50);
linea.setFill(Color.BLACK);
final Tooltip t = new Tooltip("Line 1");
linea.setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Line line = (Line) event.getSource();
line.setStroke(Color.RED);
t.show((Line) event.getSource(), event.getScreenX(),
event.getScreenY());
}
});
linea.setOnMouseExited(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Line line = (Line) event.getSource();
line.setStroke(Color.BLACK);
t.hide();
}
});
anchorPane.getChildren().add(linea);
stage.show();
}
}
Hope it helps!
Upvotes: 0