<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body style="overflow-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;">I didn’t intend to re-open all of these debates. I just wanted to point out that JavaFX in general uses dispatchers to process events so there’s no existing concept of default handlers outside of Control. If Control had implemented InputMap using a dispatcher we probably wouldn’t be having this conversation.<div><br></div><div>I still believe this is a local problem for Control and it can craft its own solution. It doesn’t even have to involve handlers; look at how Scene and Menu handle accelerators. Whatever Control wants to do almost all of the tools are there. The only obvious gap in the public API is that there’s no way for a handler or filter to communicate with the dispatcher that invoked it. Rather than add a specialized bit like this PR does I thought it might be worth considering a more generalized solution (I can think of a few) but I’m actually fine with preventDefault() since it’s based on an existing standard.</div><div><br></div><div>Martin<br><div><div><br><blockquote type="cite"><div>On Jan 13, 2026, at 4:52 AM, John Hendrikx <john.hendrikx@gmail.com> wrote:</div><br class="Apple-interchange-newline"><div>

  
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  
  <div><p><br>
    </p>
    <div class="moz-cite-prefix">On 13/01/2026 00:43, Andy Goryachev
      wrote:<br>
    </div>
    <blockquote type="cite" cite="mid:CY8PR10MB7265BC10117E5E2F5FB22E83E581A@CY8PR10MB7265.namprd10.prod.outlook.com">
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <div dir="ltr" style="font-family: "Iosevka Fixed SS16", Arial, Helvetica, sans-serif; font-size: 12pt;">
        <br>
      </div>
      <div dir="ltr" style="font-family: "Iosevka Fixed SS16", Arial, Helvetica, sans-serif; font-size: 12pt;">
        The reason I mentioned #2 is that it is somewhat relevant to the
        discussion, as in "why do we need to write custom dispatchers at
        all?"  There should be only two methods, in my opinion, one that
        dispatches an event that bubbles up (with filters and handlers),
        and one that sends an event to a single target Node and nothing
        else.  <rant>Somehow, Swing got the Events right - it
        manages to dispatch one (1) event in total, and the dispatching
        stops once the event is consumed.  The FX decided it needed to
        reinvent the wheel and leave multiple booby traps in the
        process.</rant></div>
    </blockquote><p>Although I agree that how FX solved events is sub-optimal, there
      is a real need here to communicate to the EventHandler on which
      object it resides.  EventHandler instances are expensive when you
      need to attach one to every Cell in a TableView, and so to re-use
      a single instance, you need to know which Cell the event applies
      to.  The source field (which is supposed to be constant) has been
      abused for this, making events non-constant requiring cloning
      before they can be dispatched to their final target.  This cloning
      then caused the "isConsumed" problem.  Perhaps we should just make
      the source field mutable as well, so the cloning isn't needed.<br>
    </p><p>The solution to this problem at the time should not have been to
      modify events, but to have made event handlers be BiConsumers,
      with the Event **and** Node being passed to the callback (and a
      "convenience" method that delegates to the BiConsumer variant that
      accepts only Consumer<Event> -- we may be able to still do
      this...)<br>
    </p>
    <blockquote type="cite" cite="mid:CY8PR10MB7265BC10117E5E2F5FB22E83E581A@CY8PR10MB7265.namprd10.prod.outlook.com">
      <div dir="ltr" style="font-family: "Iosevka Fixed SS16", Arial, Helvetica, sans-serif; font-size: 12pt;">
        <br>
      </div>
      <div dir="ltr" style="font-family: "Iosevka Fixed SS16", Arial, Helvetica, sans-serif; font-size: 12pt;">
        This isn't exactly rocket science, we should be able to figure
        something out.  Maybe there is another option that will satisfy
        everyone?</div>
    </blockquote><p>I think the issue isn't so much in event dispatching, but in the
      Skin/Behavior system itself.  Skin/Behaviors in FX is like giving
      root access to every user on your system.  Sure it is convenient
      that everyone can do whatever they want, and as long as everyone
      behaves, everything works great.  However one malicious user can
      interfere with others or leave behind hooks that later come to
      bite you.</p><p>Controls are HOSTS for Skins and Behaviors.  Skins and Behaviors
      are clients.  They should be restricted to a very specific subset
      of functionality that benefits the host and is predictable for the
      host:</p><p>- Skins get ownership of the children list of the Control; while
      a Skin is installed, the host should not be allowed to make
      modifications<br>
      - Skins can monitor properties for changes but this should never
      lead to a direct observable change on the main control that a
      subsequent installed listener may observe; in other words,
      listener order should be irrelevant for what the Skin does in
      order to share the listener infrastructure without interference. 
      Skins are free to directly take action on the children (which they
      own exclusively), just not on the main control; such actions
      should instead be deferred, usually by requesting a layout (this
      is usually already the case, but it is good to make this explicit
      so we can decide what a Skin is doing is a bug or not).<br>
      - Behaviors can react to events at the lowest precedence, and
      exclusively only take action when receiving an event; this means
      that blocking all events will automatically mean the Behavior no
      longer does anything, but also that selectively blocking events
      allows some control over Behaviors<br>
      - Behaviors can co-modify properties on the Control, but this
      should be clearly documented; controls are free to restrict this
      set (ie. a Behavior has no business modifying the "wrapText"
      property, or things like layout properties -- most often they do
      their work through pseudo class changes and modifying the value a
      control represents).<br>
    </p><p>That should really be all that is needed for a functioning
      Skin/Behavior system; no need for root access.</p><p>Of course, root access to the Control is a ship that has sailed a
      long time ago; but that doesn't mean we can't introduce a client
      API for Skins/Behaviors.  All that really takes is passing an
      object to the Skin/Behavior when it is installed. This object is
      an interface with which the Skin/Behavior can do their work.
      Should they choose to not circumvent this API, and do all their
      work through this API, they can remove all their clean-up code, as
      the Control can now do this automatically.  This will greatly
      simplify skins, and remove a whole avenue of potential bugs.</p><p>All work done through this API can be monitored by the Control.
      The control can:<br>
      - Track what is installed (for later clean-up)<br>
      - Reject installation of listeners/handlers it doesn't want to
      expose<br>
      - Ensure that event handlers are installed at lowest precedence. 
      This can be kept internal, so many solutions are possible:
      separate lists, default event handlers (internal API), priorities,
      etc.<br>
    </p><p>Everything you'd expect a host Control to be able to do,
      including forcefully removing all traces of a previously installed
      Skin, and disallowing it further access should it attempt to use
      the API again after a new Skin is installed. That's however not a
      requirement; all we'd need is that interface, and encourage
      Skins/Behaviors to use it.  Correctly behaved Skins/Behaviors then
      get all the benefits, and will stop interfering with user code. 
      This means of course modifications to existing skins, but it is
      mostly in their registration logic (which I think we modified like
      5 times already).</p><p>The minimum API needed can be fairly small, and does not need to
      include accessors for every property and handler with some smart
      signatures.  For example:</p><p>    <span style="background-color:#ffffff;padding:0px 0px 0px 2px;"><span style="background-color: rgb(255, 255, 255); font-family: Consolas; font-size: 11pt; white-space: pre;"><span style=""><T, P </span><span style="color:#0000a0;font-weight:bold;">extends</span><span style=""> ReadOnlyProperty<T>> </span><span style="color:#0000a0;font-weight:bold;">void</span><span style=""> addListener(Function<C, P> supplier, Consumer<T> subscriber)</span></span></span></p><p>Allows installation of a listener by doing:<br>
    </p><p>    <span style="background-color:#ffffff;padding:0px 0px 0px 2px;"><span style="background-color: rgb(255, 255, 255); font-family: Consolas; font-size: 11pt; white-space: pre;">api<span style="">.addListener(Slider::minProperty, v -> { ... });</span></span></span><br>
      <br>
      In this way we can isolate and track what Skins/Behaviors are
      doing, ensure they don't interfere with user operations on the
      Control and also ensure guaranteed clean-up (if they refrain from
      accessing the Control directly).<br>
    </p><p>--John</p><p><br>
    </p>
  </div>

</div></blockquote></div><br></div></div></body></html>