This discussion is archived
6 Replies Latest reply: Oct 14, 2013 6:49 AM by PavelSafrata RSS

Intercept all events, including PopupWindows

519a426e-09dd-4c32-ae5f-24ef761a1909 Newbie
Currently Being Moderated

Hi,

 

I need to know of *all* events coming into a window (because I need to activate a CDI context for further processing). My first attempt was to have a custom Scene-implementation that overrides buildEventDispatchChain and inserts a custom EventDispatcher to do that. It works fine, except for the PopupWindows used by many components (Menu, ChoiceBox, ...). Those are different windows and therefore have their own event dispatch chain, so my interception doesn't work.

Is there any way I can get to these events, too? For example, when the value of a ChoiceBox is changed, I need to execute some code before the valueProperty-Listeners are called, and some other code afterwards.

 

Thank you for any help!

Philipp

  • 1. Re: Intercept all events, including PopupWindows
    PavelSafrata Journeyer
    Currently Being Moderated

    Hello Philipp,

    custom event dispatcher on the main window (stage) should do the trick. Rather than tweaking the dispatch chain, the following approach is cleaner:

     

    final EventDispatcher origEventDispatcher = stage.getEventDispatcher();
    stage.setEventDispatcher(new EventDispatcher() {
        @Override
        public Event dispatchEvent(Event event, EventDispatchChain chain) {
            // your special handling goes here
            return origEventDispatcher.dispatchEvent(event, chain);
        }
    });

     

    Hope this helps,

    Pavel

  • 2. Re: Intercept all events, including PopupWindows
    519a426e-09dd-4c32-ae5f-24ef761a1909 Newbie
    Currently Being Moderated

    Hi Pavel,

     

    thank you for your help. I tested it and just like my previous approach it does not seem to catch the events of child windows (like the PopupWindow used in ChoiceBoxSkin). I used the following code:

    final EventDispatcher oldDispatcher = newWindow.getEventDispatcher();
    newWindow.setEventDispatcher(new EventDispatcher() {
         @Override
         public Event dispatchEvent(Event event, EventDispatchChain chain) {
              System.out.println("IN:  " + event);
              Event result = oldDispatcher.dispatchEvent(event, chain);
              System.out.println("OUT: " + event);
              return result;
         }
    });

    newWindow is the stage that my Scene is assigned to. The Scene contains a ChoiceBox. Before opening the ChoiceBox, I see a lot of MouseEvents (of course). When moving the mouse over the popup however, no events are logged. Then I select an item, which causes the following output:

    IN:  javafx.event.ActionEvent[source=javafx.event.Event$1@3bf21475]
    OUT: javafx.event.ActionEvent[source=javafx.event.Event$1@3bf21475]
    IN:  com.sun.javafx.stage.FocusUngrabEvent[source=javafx.event.Event$1@3bf21475]
    OUT: com.sun.javafx.stage.FocusUngrabEvent[source=javafx.event.Event$1@3bf21475]

    And unfortunately the ChangeListeners on the property I bound the ChoiceBox to are executed *after* the "OUT", so in the context of the PopupWindow (more precisely the ContextMenu, which extends PopupWindow). I put a break-point to the listener-execution and raised an exception (in order to be able to post the stack trace here):

     

    [... more stack for the listener execution ...]
    at javafx.scene.control.ChoiceBox$ChoiceBoxSelectionModel.select(ChoiceBox.java:417)
      at com.sun.javafx.scene.control.skin.ChoiceBoxSkin$4.handle(ChoiceBoxSkin.java:268)
      at com.sun.javafx.scene.control.skin.ChoiceBoxSkin$4.handle(ChoiceBoxSkin.java:265)
      at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:69)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
      at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
      at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53)
      at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:28)
      at javafx.event.Event.fireEvent(Event.java:171)
      at javafx.scene.control.MenuItem.fire(MenuItem.java:456)
      at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.doSelect(ContextMenuContent.java:1199)
      at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer$6.handle(ContextMenuContent.java:1148)
      at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer$6.handle(ContextMenuContent.java:1146)
      at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:69)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170)
      at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:38)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
      at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
      at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
      at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
      at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53)
      at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:33)
      at javafx.event.Event.fireEvent(Event.java:171)
      at javafx.scene.Scene$MouseHandler.process(Scene.java:3311)
      at javafx.scene.Scene$MouseHandler.process(Scene.java:3151)
      at javafx.scene.Scene$MouseHandler.access$1900(Scene.java:3106)
      at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1563)
      at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2248)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:250)
      [... more com.sun.javafx-Stack ...]
      at java.lang.Thread.run(Thread.java:724)

    The Scene at line 33 has its window-Property set to the ContextMenu-instance. The ContextMenu has its ownerWindow-property set to the main stage (the one with my EventDispatcher), but that doesn't seem to help (?), because I don't know how to intercept the child window in the first place.

    Maybe there is some way to globally hook all windows? Or the ContextMenu/PopupWindow, when it's created, somehow notifies its parent?

     

    Philipp

  • 3. Re: Intercept all events, including PopupWindows
    PavelSafrata Journeyer
    Currently Being Moderated

    Oh, right, some of the events are not routed via the parent window. After a closer look I don't see any way you can get reference to the popup window and therefore I don't think you'll get the events, unfortunately. Although you seem to have quite an unusual use-case, this seems to be an unnecessary limitation of the platform. In ComboBox, there are onShowing and onShown events, but they are just generic events without any additional information. I think those events could be specialized to contain the popup window reference and added also to ChoiceBox. I filed RT-33536 to track this. However, given the current development stage, this is probably not going to happen very soon, so let's try to find another way to solve your problem.

     

    Note that it's intentionally impossible to hook event handlers/dispatchers to "all windows", in some contexts this would constitute a security vulnerability.

     

    So could you for instance register custom selection model on the choicebox, override the select methods and do the pre-property-changed stuff there?

  • 4. Re: Intercept all events, including PopupWindows
    519a426e-09dd-4c32-ae5f-24ef761a1909 Newbie
    Currently Being Moderated

    Thank you for filing the bug report!

     

    The selection-model-approach works. Because I was too lazy to implement the entire selection model (and I can't access the package-private ChoiceBox$ChoiceBoxSelectionModel), I retrieve ChoiceBox.selectedItemListener via reflection to test it. At least this hack works. In order to replace the SelectionModel itself (which would be possible without reflection), would I have to implement my own SingleSelectionModel?  Or is there some way I can get to the ChoiceBox$ChoiceBoxSelectionModel? Proxying/Decorating the default model does not seem like a good idea to me, because SingleSelectionModel is not state-less. Or would you ignore that?

     

    The problem with this workaround is its limitation to ChoiceBoxes. For MenuItems (in normal MenuBars), ComboBoxes, etc. I would have to come up with different workarounds. In fact, I already did (and posted it in some older thread here), so that's not really an issue anymore, but I'd appreciate a more generic and stable solution. The workarounds I have so far tend to crash in special situations (like opening a menu by mouse but then selecting an entry by keyboard, and other special cases), so that I have to debug and fix/extend them quite often.

    The bug report you filed is a little more generic, but it still has the limitation you'd need to hook all the sub-windows manually when they're opened (i.e. write code for ChoiceBoxes, MenuItems, etc. and attach it to each instance).

     

    Looking at PopupWindow.showImpl(Window), I see that PopupWindows register themselves to their owners, but only if the owner is a PopupWindow itself. Couldn't that be extended to all Windows, maybe something like myWindow.setOnPopupShown(new SomeListener<PopupWindow>() { ... });?

     

    My use case may seem pretty unusual, but actually it's not. It results directly from the inversion of control which is done by injection frameworks. Before any code is executed, the framework needs to know (of course) which context it occurs in, in order to activate the context. At least that's the case with frameworks like Weld, using Proxies etc.; I guess Guice wouldn't have the problem because it injects the bean instances itself.

    So I guess anyone trying to integrate CDI (Weld) with JavaFX, like it is integrated with Servlets and HTTP-Sessions today, would run into that problem. And that's most likely why it hasn't been done yet. There is fx-guice, which is fine but not CDI. And there are several approaches with CDI, none of which (as far as I know) offers something like a Window-scope.

     

    Thank you for your help! My code is working now, so I have everything I need now.

    Philipp

  • 5. Re: Intercept all events, including PopupWindows
    PavelSafrata Journeyer
    Currently Being Moderated

    In order to replace the SelectionModel itself (which would be possible without reflection), would I have to implement my own SingleSelectionModel?

    Probably so.

     

    Proxying/Decorating the default model does not seem like a good idea to me, because SingleSelectionModel is not state-less. Or would you ignore that?

    I wouldn't be much worried about the small state, but it is not an interface, so you'd have to keep up with future additions to the abstract superclasses. So proxying it really doesn't seem like a good idea.

     

    The bug report you filed is a little more generic, but it still has the limitation you'd need to hook all the sub-windows manually when they're opened (i.e. write code for ChoiceBoxes, MenuItems, etc. and attach it to each instance).

    You'll need to hook manually, but I hope we could get a common event for all controls showing popups. Then you could have all the handling in one common event handler on the scene. However, see below.

    Looking at PopupWindow.showImpl(Window), I see that PopupWindows register themselves to their owners, but only if the owner is a PopupWindow itself. Couldn't that be extended to all Windows, maybe something like myWindow.setOnPopupShown(new SomeListener<PopupWindow>() { ... });?

    Great idea. Popup window owners have a specific functionality so this concept can't be extended, but we could produce such an event for each shown child window. This should be completely general. I filed RT-33543 and I'm closing the other one in favor of this new one.

  • 6. Re: Intercept all events, including PopupWindows
    PavelSafrata Journeyer
    Currently Being Moderated

    (Don't know why my post is so ugly, I can see four nice quotations in the edit mode)

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points