2016-12-13 34 views
3

感谢您对我上一篇文章的帮助(这也是我的第一篇文章)。我是新的stackoverflow。我希望我早些时候加入这个组织。人们在这里非常有礼貌和乐于助人。为什么默认的按钮鼠标事件处理程序会消耗一个事件?

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

the oracle documents开始,位于页面底部,它声明“请注意,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); 
    } 

} 
+1

问题是“为什么事件不会冒泡组件层次结构?” –

+0

我认为你需要改写你的问题...... – MeltingDog

+0

@Juan和MeltingDog在oracle文档中,它是这样陈述的:“在达到事件目标并且所有已注册的过滤器处理完事件后,事件将沿着调度链从目标到根节点“。我想我应该改写胡安建议的问题。 – Tania

回答

5

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

btn.setSkin(new ButtonSkin(btn) { 
    { 
     this.consumeMouseEvents(false); 
    } 
}); 

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

btn mouse pressed... 
layout mouse pressed... 
root mouse pressed... 
scene mouse pressed... 

为什么它的工作原理是这样的,我也说不清楚。我的猜测是,如果皮肤消耗事件,那么这有助于防止来自控件的鼠标事件的不希望的传播,例如当它们被分层或堆叠在彼此之上时。这可能是您几乎所有时间都需要的行为,因此会进行合理的默认设置。

+0

@James和jewelsea你们都回答了我的问题。 – Tania

+0

非常感谢你! – Tania

+0

我想这就是为什么在文档中它说“默认处理程序......消耗大部分输入事件”如果不是为了让jewelsea向我展示皮肤实现和James解释,我仍然会抓挠我的头。 Imho我认为太阳应该处理他们的文件。 – Tania

4

链接文档中的“输入事件”是指javafx.scene.input软件包中的事件子类。这些是“低级”事件,例如MouseEventKeyEvent。通常情况下,对于控件而言,您对这些事件不感兴趣,但对较高级别的“语义”事件(例如ActionEvent)不感兴趣。

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

因此,为了让您轻松编码,该按钮封装了所有这些不同的行为,并将它们重新打包为“操作”。它通过为低级事件注册侦听器(鼠标按键和按键等)来实现这一点。如果出现这些情况,那么按钮本身会处理它们并触发一个动作事件。由于低级事件现在被认为是被处理的,所以它被消耗,防止它在场景图层级上冒泡。

所以通常你应该寻找“高级别”或如一个按钮控制“语义”事件:

btn.setOnAction(e -> System.out.println("Action performed on button")); 

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

如果你真的需要监听容器上的鼠标点击,即使他们在该容器中装有控制实际发生,你可以使用事件过滤器来处理他们才到达控制之前:

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