首页 > 解决方案 > 当目标节点更改场景时,Ubuntu 上不会发生鼠标释放事件?

问题描述

这个问题涉及跨操作系统的鼠标行为;具体来说,我的代码适用于 Windows 和 Mac OS X,但不适用于 Ubuntu。

最终我要做的是制作一个特殊的窗格子类(“ConvertiblePane”),它存在于主舞台/场景的父窗格中,但在拖动时神奇地转移到自己的临时舞台/场景中,从而变得独立和能够放置在屏幕上的任何位置。当用户释放鼠标按钮时,ConvertiblePane 应返回到其原始父窗格并失去临时阶段。(在我的完整程序中,原始父 Stage 调整大小/重新定位自身以适应 ConvertiblePane 被丢弃的任何位置。)

这让我想到了我的问题。当我在 ConvertiblePane 上按下鼠标时,它会按预期在主场景中触发 MousePress,此时 ConvertiblePane 移动到临时舞台。当我拖动鼠标时,它会在临时场景中触发 MouseDrag 并移动临时舞台。好,很好。

但是,当我释放鼠标按钮时,我在不同的操作系统上会遇到不同的行为。在 Windows (7) 和 Mac OS X (10.12.6) 上,MouseRelease 发生在临时场景中,按预期将窗格发送回主舞台中的原始父级。然而,在 Ubuntu 上,主场景或临时场景中似乎都没有生成 MouseRelease。

以下是作为 MCV 示例的相关代码:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class ConvertibleTest extends Application {           

    @Override
    public void start(Stage primaryStage) {

        // Set up the main stage and scene with a
        // Pane as root and a ConvertiblePane child:
        primaryStage.initStyle(StageStyle.TRANSPARENT);        
        Pane root = new Pane();
        ConvertiblePane conv = new ConvertiblePane();
        root.getChildren().add(conv);        
        Scene scene = new Scene(root, 400, 400, Color.PINK);
        primaryStage.setTitle("Convertible Test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }    
}

class ConvertiblePane extends Pane
{    
    private final Group TEMP_ROOT = new Group();
    private final Stage TEMP_STAGE = new Stage(StageStyle.TRANSPARENT);    
    private final Scene TEMP_SCENE = new Scene(TEMP_ROOT);
    private Pane originalParent = null;
    private double deltaX = 0.0;
    private double deltaY = 0.0;
    private String name = null;

    public void onMousePress(MouseEvent event)
    {
        // Save deltaX/Y for later:
        Point2D delta = this.sceneToLocal(event.getX(), event.getY());
        deltaX = delta.getX();
        deltaY = delta.getY();  

        if (!isIndependent())
        {
            makeIndependent();
        }
    }

    public void onMouseDrag(MouseEvent event)
    {
        // Keep the TEMP_STAGE relative to the original click point:
        TEMP_STAGE.setX(event.getScreenX()-deltaX);
        TEMP_STAGE.setY(event.getScreenY()-deltaY);
    }

    public void onMouseRelease(MouseEvent event)
    {
        if (isIndependent())
        {
            returnToParent();
        }
    }

    public ConvertiblePane()
    {                         
        this.setPrefSize(100, 100);
        this.setBackground(new Background(new BackgroundFill(Color.GREEN, new CornerRadii(10), Insets.EMPTY)));
        this.setVisible(true);

        // Attach event code and report to System.out what is happening:
        this.setOnMousePressed((MouseEvent event) -> {
            if (this.getScene() == TEMP_SCENE)
                System.out.println("Pressed as Independent");
            else
                System.out.println("Pressed as Child");
            onMousePress(event);
        });
        this.setOnMouseDragged((MouseEvent event) -> {
            if (this.getScene() == TEMP_SCENE)
                System.out.println("Dragged as Independent");
            else
                System.out.println("Dragged as Child");
            onMouseDrag(event);
        });
        this.setOnMouseReleased((MouseEvent event) -> {
            if (this.getScene() == TEMP_SCENE)
                System.out.println("Released as Independent");
            else
                System.out.println("Released as Child");
            onMouseRelease(event);
        });
    }

    public boolean isIndependent()
    {
        // Return whether this ConvertiblePane is "independent" (exists in its own temp scene)
        return this.getScene() == TEMP_SCENE;
    }

    public void makeIndependent()
    {                
        // Get the point where this ConvertiblePane appears on screen:
        Point2D screenPt = this.localToScreen(0, 0);

        // Save the originaParent of this ConvertiblePane; we will return to it later:
        originalParent = (Pane)getParent();

        // Remove this ConvertiblePane from its originalParent:
        originalParent.getChildren().remove(this);

        // Set this ConvertiblePane as the root of the TEMP_SCENE on the TEMP_STAGE:
        TEMP_SCENE.setRoot(this);        
        TEMP_STAGE.setScene(TEMP_SCENE);
        System.out.println("Transferred to TEMP.");
        this.relocate(0, 0);

        // Show the TEMP_STAGE in the same location on screen where this ConvertiblePane originally was:
        TEMP_STAGE.setX(screenPt.getX());
        TEMP_STAGE.setY(screenPt.getY());                                                   
        TEMP_STAGE.show();        
    }

    public void returnToParent()
    {
        // Reset deltas:
        deltaX = 0;
        deltaY = 0;

        // Get the location of this ConvertiblePane on screen:
        Point2D screenPt = this.localToScreen(0, 0);

        // Set TEMP_ROOT as the root of TEMP_SCENE; this will allow us to detach
        // this ConvertiblePane from being the scene root (since root cannot == null).
        TEMP_SCENE.setRoot(TEMP_ROOT);

        // Hide the TEMP_STAGE:
        TEMP_STAGE.hide();        

        // Add this ConvertiblePane back to the originalParent:
        originalParent.getChildren().add(this);
        System.out.println("Transferred to MAIN.");

        // Relocate this ConvertiblePane within the originalParent to maintain its position on screen
        Point2D parentPt = originalParent.screenToLocal(screenPt);
        this.relocate(parentPt.getX(), parentPt.getY());          
    }
}

如您所见,事件处理方法中有一些基本的报告;makeIndependent() 和 returnToParent() 方法输出“转移到 TEMP”。和“转移到主要”。分别。

如果我在 ConvertiblePane 上单击鼠标,拖动几个像素,然后释放它,输出如下:

(on Windows or Mac OS X) Pressed as Child Transferred to TEMP. Dragged as Independent Dragged as Independent Dragged as Independent Released as Independent Transferred to MAIN.

(on Ubuntu) Pressed as Child Transferred to TEMP. Dragged as Independent Dragged as Independent Dragged as Independent

我还尝试向两个场景添加事件过滤器;但结果是一样的:MouseRelease 发生在 Win/Mac 上,而不是 Ubuntu 上。

如果有人可以解释这种行为,或者提出一些建议,那就太好了。或者......我可以捕捉到 MouseEvents 的任何“全局”(场景前)创建吗?我的意思是,我并不真正关心鼠标释放的细节;我只是想要一个事件来告诉我何时将 ConvertiblePane 添加回主舞台。

谢谢!

标签: javaubuntujavafxmouseevent

解决方案


在花了几个星期之后,我找不到在 Ubuntu 上针对这种情况触发适当的 MouseReleased 事件的方法;然而,我确实想出了一个足够好的方法来完成这项工作。基本上,我不是在 MouseReleased 发生时收到通知,而是每 10 毫秒检查一次鼠标按钮是否不再按下。

说明:当节点转移到临时场景时,时间轴开始每隔 10 毫秒将鼠标指针“移动”到位。这会触发 MouseDragged 事件(如果鼠标按钮仍然按下)或 MouseMoved 事件(如果鼠标按钮向上);所以我可以模拟 MouseReleased 事件并调用我的程序将节点添加回主阶段。当然,那时我也停止了时间轴。

这是演示这一点的相关代码;也许它对其他人也有用。

// The robot is needed to "move" the mouse in place,
// triggering a MOUSE_MOVED event.
private static Robot robot = null;  
static {
    try {
        robot = new Robot();
    } catch (AWTException ex) {
        Logger.getLogger(ConvertiblePane.class.getName()).log(Level.SEVERE, null, ex);
    }
}

// clickWaiter will move the mouse in place every 10 milliseconds,
// triggering a MOUSE_MOVED event if the mouse is no longer pressed.
private final Timeline clickWaiter = new Timeline(new KeyFrame(Duration.millis(10), (ActionEvent event) -> {
    // Get the mouse position from MouseInfo class.
    Point mouse = MouseInfo.getPointerInfo().getLocation();        
    // "Move" the mouse in place to trigger a mouseMove or mouseDrag event.
    robot.mouseMove(mouse.x, mouse.y);
}));

public ConvertiblePane()
{
    ...

    // MOUSE_MOVED will be triggered by the robot when the mouse button is no longer pressed:
    TEMP_SCENE.addEventFilter(MouseEvent.MOUSE_MOVED, (MouseEvent event) ->
    {   
        if (!event.isPrimaryButtonDown())
        {
            System.out.println("Temp.release (sim)");
            clickWaiter.stop();
            // Simulate MOUSE_RELEASED event.
        }
    });
}

public void makeIndependent()
{
    ...

    // Start the clickWaiter, part of the Linux hack:
    clickWaiter.playFromStart();
}

推荐阅读