在 JavaFX (Linux) 中使用上下文菜单后的事件检测问题

问题描述 投票:0回答:1

我遇到过在 Linux 上运行的 JavaFX 应用程序的奇怪行为。 应用程序是一个简单的图形编辑器。它的主要区域由带有两个滚动条的 ScrollPane 和滚动窗格的内容 AnchorPane 表示,其子级是两层 - 图形层(放置所有对象的位置)和选择层(用于实现选择框)。 图形层还有上下文菜单。

选择框是通过检测拖动事件来实现的。按下鼠标按钮并创建选择框,然后移动鼠标并根据鼠标 X 和 Y 坐标重新绘制矩形。当您释放按钮时,矩形内的所有对象都将被选中,然后矩形将被删除。此行为的关键时刻是,每当我将鼠标拖动到舞台外时,仍然会检测到鼠标拖动事件,因此矩形的大小会增加,滚动窗格的滚动条也会相应地减小,这表明整体图形区域大小增加了。这是正常行为。

问题是,打开上下文菜单后,当我尝试使用选择框选择对象时,选择框无法将其自身增加到滚动窗格的可见区域之外,导致在舞台之外不再检测到拖动事件。检测到鼠标按下,仅在滚动窗格的可见区域内检测到鼠标拖动。但是,如果我打开上下文菜单,然后在图形层上按鼠标按钮,然后使用选择框,则一切正常。另一种恢复正常行为的方法是按 ESC 键。

此问题仅存在于 Linux 上,在 Windows 上一切正常。从带有内置 JavaFX 的 Java 8 迁移到带有 JavaFX 21 的 Java 17 后,也出现了此问题。

我想出了几个解决这个问题的方法,例如使用Robot API在隐藏上下文菜单时按ESC键,但我想以更干净、更合适的方式解决这个问题。

我将不胜感激任何帮助。

最小的、可重复的示例

package org.example;

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
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);
    }

    public void start(Stage primaryStage) {
        AnchorPane mainPane = new AnchorPane();

        //Creating scroll pane
        ScrollPane scroll = new ScrollPane();
        scroll.setContent(mainPane);
        scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        scroll.setPrefWidth(1024);
        scroll.setPrefHeight(768);

        //Creating selection layer to place selection frame
        AnchorPane selectionLayer = new AnchorPane();
        selectionLayer.setMouseTransparent(true);

        //Creating graphic layer
        BorderPane graphicLayer = new BorderPane();
        graphicLayer.setMinHeight(768);
        graphicLayer.setMinWidth(1024);
        graphicLayer.setFocusTraversable(false);

        //Creating context menu
        ContextMenu contextMenu = new ContextMenu();
        contextMenu.getItems().add(new MenuItem("test1"));
        contextMenu.getItems().add(new MenuItem("test2"));

        graphicLayer.setOnContextMenuRequested(e -> contextMenu.show(graphicLayer, e.getScreenX(), e.getScreenY()));

        SelectionController controller = new SelectionController(selectionLayer);

        graphicLayer.setOnMousePressed(event -> {
            System.err.println("PRESSED");
            if (contextMenu.isShowing()) {
                contextMenu.hide();
            }
            controller.startSelection(event);
        });

        graphicLayer.setOnMouseDragged(event -> {
            System.err.println("DRAGGED");
            controller.setPosition(event);
        });

        graphicLayer.setOnMouseReleased(event -> {
            System.err.println("RELEASED");
            controller.finishSelection(event);
        });

        mainPane.getChildren().addAll(graphicLayer, selectionLayer);

        Scene scene = new Scene(scroll);
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    class SelectionController {
        AnchorPane selectionLayer;
        Point2D startingPoint;
        Rectangle selectionFrame;

        SelectionController(AnchorPane selectionLayer) {
            this.selectionLayer = selectionLayer;
        }

        void startSelection(MouseEvent event) {
            startingPoint = new Point2D(event.getX(), event.getY());

            selectionFrame = new Rectangle();
            selectionFrame.setId("selection_frame");
            selectionFrame.setFill(Color.TRANSPARENT);
            selectionFrame.getStrokeDashArray().add(10.0);
            selectionFrame.setStrokeWidth(2.0);
            selectionFrame.setStroke(Color.LIGHTSKYBLUE);
            selectionFrame.xProperty().set(startingPoint.getX());
            selectionFrame.yProperty().set(startingPoint.getY());

            selectionLayer.getChildren().add(selectionFrame);
        }

        void setPosition(MouseEvent event) {
            double x = event.getX();
            double y = event.getY();

            if (x < startingPoint.getX()) {
                selectionFrame.xProperty().set(x);
                selectionFrame.widthProperty().set(startingPoint.getX() - x);
            } else {
                selectionFrame.widthProperty().set(x - startingPoint.getX());
            }

            if (y < startingPoint.getY()) {
                selectionFrame.yProperty().set(y);
                selectionFrame.heightProperty().set(startingPoint.getY() - y);
            } else {
                selectionFrame.heightProperty().set(y - startingPoint.getY());
            }
        }

        void finishSelection(MouseEvent event) {
            // To some logic to filter objects on graphic layer inside selection frame

            selectionLayer.getChildren().clear();
        }
    }
}

重现动作:

  1. 启动应用程序
  2. 尝试使用选择框,左上角在舞台内部,然后将右下角拖动到舞台可见边界之外,可以看到拖动事件仍然被触发
  3. 按右键调用上下文菜单
  4. 当菜单仍然显示时,尝试重复步骤 2,您可以看到选择框不会超出舞台边界,尽管鼠标指针位于舞台之外。而且拖动事件不会在舞台外触发
  5. 重复步骤 3。现在,如果您在创建选择框之前单击舞台内部或按 ESC 按钮,选择框将按其应有的方式运行
java linux javafx event-handling controls
1个回答
0
投票

在尝试了很多不同的选项之后,我找到了一个可以接受的解决方法。 ContextMenu 的自动隐藏机制似乎对底层窗格有一些影响,因此:

contextMenu.setAutoHide(false);

解决问题。

当然,现在我必须手动处理菜单隐藏,但无论如何我会坚持使用这个解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.