当目标节点更改场景时,Ubuntu上不会发生鼠标释放事件?

时间:2018-05-08 18:26:12

标签: java ubuntu javafx mouseevent

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

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

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

然而,当我释放鼠标按钮时,我在不同的操作系统上会遇到不同的行为。在Windows(7)和Mac OS X(10.12.6)上,鼠标释放发生在临时场景中,按预期将窗格发送回主阶段中的原始父级。但是,在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”。和“转移到MAIN。”分别

如果我在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添加回主舞台。

谢谢!

1 个答案:

答案 0 :(得分:0)

花了几个星期之后,我找不到办法在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();
}