为什么默认按钮鼠标事件处理程序消耗一个事件?

Tan*_*nia 3 java events javafx

感谢上一篇文章的帮助(这也是我的第一篇文章).我是stackoverflow的新手.我希望我早些时候加入这个小组.人们非常有礼貌,乐于助人.

无论如何,我一直在努力更好地理解javafx事件.对你们中的一些人来说,这似乎是另一个简单或"愚蠢"的问题.为什么默认按钮鼠标事件处理程序似乎消耗一个事件?

oracle文档开始,在页面底部,它指出"请注意,JavaFX UI控件的默认处理程序通常会占用大部分输入事件." 它是一个附加到按钮的默认处理程序,我不知道吗?为什么我必须在目标节点上显式触发事件才能使事件调度链冒泡?

再次,任何回复将不胜感激!:)

public class MouseEventTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World");

        Group root = new Group();

        Scene scene = new Scene(root, 300, 250);

        Button btn = new Button();
        btn.setText("Hello World");
        btn.setPrefSize(100, 100);


        BorderPane layout = new BorderPane(btn);
        layout.setPrefSize(300, 250);

        root.getChildren().add(layout);

        //This is the event dispatch chain
        //primaryStage -> scene -> root -> layout -> btn  (capturing phrase)
        //btn -> layout -> root -> scene -> primaryStage  (bubbling phrase)
        btn.setOnMousePressed(e -> { 
            System.out.println("btn mouse pressed...");
            //Why do I need to fire the mouse pressed event 
            //in order for the event to bubble up the chain?
            //It seems like by default that the button setOnMousePressed event hanlder
            //has consumed the event. Am I right?
            //layout.fireEvent(e); 
        });

        layout.setOnMousePressed(e -> { System.out.println("layout mouse pressed...");});
        root.setOnMousePressed(e -> { System.out.println("root mouse pressed...");});
        scene.setOnMousePressed(e -> { System.out.println("scene mouse pressed...");}); 

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}
Run Code Online (Sandbox Code Playgroud)

jew*_*sea 5

默认按钮外观实现调用consumeMouseEvents(true).如果您不想要此行为,请覆盖默认外观并将值设置为false.

btn.setSkin(new ButtonSkin(btn) {
    {
        this.consumeMouseEvents(false);
    }
});
Run Code Online (Sandbox Code Playgroud)

然后单击示例应用程序按钮的输出将是:

btn mouse pressed...
layout mouse pressed...
root mouse pressed...
scene mouse pressed...
Run Code Online (Sandbox Code Playgroud)

为什么它这样工作,我不能说.我的猜测是,如果皮肤消耗了事件,那么这有助于防止鼠标事件从控件中发生不必要的传播,例如当它们分层或堆叠在一起时.这可能是你几乎所有时间都想要的行为,因此是一个合理的默认行为.


Jam*_*s_D 5

您链接的文档中的"输入事件"是指javafx.scene.input包中的事件子类.这些是"低级"事件,例如MouseEventKeyEvent.通常,对于控件,您不会对诸如此类的事件感兴趣,而是对诸如此类的更高级别的"语义"事件感兴趣ActionEvent.

使用该按钮作为示例,您通常编写当用户打算使用按钮提交"操作"时调用的代码.这实际上可能是用户用鼠标单击按钮,或者当按钮具有键盘焦点时按空格键,或者如果按钮是默认按钮则按Enter键,或者按下与按钮相关联的助记符的某些击键.在所有这些示例中,用户通过"物理"动作意图具有相同的含义:这是与按钮相关联的"动作".

因此,为了使您能够轻松编写代码,该按钮会封装所有这些不同的行为,并将它们重新打包为"操作".它通过为低级事件(鼠标按下和按键等)注册监听器来完成此操作.如果发生这些,则按钮本身会处理它们并触发一个动作事件.由于现在认为低级事件被处理,因此它被消耗,从而防止它冒出场景图层次结构.

因此,通常您应该在控件(如按钮)上查找"高级"或"语义"事件:

btn.setOnAction(e -> System.out.println("Action performed on button"));
Run Code Online (Sandbox Code Playgroud)

如果用户点击一个按钮,它(显然)被认为是该按钮上的"动作",而不是点击任何容器按住按钮.我无法完全证明为什么要做出这个设计决定,但通常情况下,鼠标点击事件不会被按钮的父级触发.

如果你确实需要在容器上监听鼠标点击,即使它们实际发生在容器所持有的控件上,你也可以使用事件过滤器在它们到达控件之前处理它们:

layout.addEventFilter(MouseEvent.MOUSE_PRESSED, 
    e -> { System.out.println("layout mouse pressed...");});
Run Code Online (Sandbox Code Playgroud)