1

In Visual Basic 6, if you set the DefaultButton property of a form button control to true, the same property on all the other button controls in the form was set to false, i.e. the property was mutually exclusive (like a radio button).

I am getting behavior on my GUI that seems to indicate that more than one button in JavaFX may have the defaultButton property set to true, and that more than one button will receive VK_Enter button events from the system.

The JavaFX 2.2 documentation for the setDefaultButton() method and defaultButton property does not clarify this issue.

I have one AnchorPanel with a bunch of controls and another with a separate bunch of controls. These are shown on the same stage, and which one is setVisible(true) to the user depends on what information he's working with.

I'm wondering if I need to iterate through the button controls on my active panes and set the defaultButton property to false for all of them before I try to set one to true in order to avoid odd behavior resulting from more than one button receiving Enter Key events.

EDIT 05/05/2013 -- Here is the ChangeListener code I have. I had originally written it as an anonymous inner class that I was attaching directly to the focusedProperty of the TextField control txtDx. However, I thought that maybe I could fix my error by removing the Listener during those times when I didn't want it firing (i.e., when its pane was invisitble or disabled). So I moved the Listener to an inner class and have an instance of it referenced to by an instance variable. Having the reference allows me to add and remove the Listener depending on which pane is displayed.

Anyway, here is the Listener class:

private class FocusPropertyChangeListener implements ChangeListener<Boolean> {

    FocusPropertyChangeListener() { System.out.println("New FPCL instance"); }

    @Override
    public void changed(ObservableValue<? extends Boolean> ov, 
        Boolean oldb, Boolean newb) {
        System.out.println("Focus change triggered");

        if (ancEncEditor.isVisible() && !ancEncEditor.isDisabled()) {
            boolean b = (newb != null && newb.booleanValue() == true);
            System.out.println("txtDx focus change event triggered: DxAdd = " + b);

            btnWindowCommit.setDefaultButton(!b);
            btnWindowClose.setCancelButton(true);
            btnDxAdd.setDefaultButton(b);
        }
    }
}

As you can see, in the event handler I make two (presumably concurrent) calls to setDefaultButton. The intent is to use btnDxAdd if I am editing in txtDx, otherwise use btnWindowCommit. I'll try getting rid of the presumably unneeded calls to setDefaultButton and see if I still get ConcurrentModificationExceptions.

EDIT 05/05/2013 -- Adding the stack trace. Here goes...

java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:894)
at java.util.HashMap$EntryIterator.next(HashMap.java:934)
at java.util.HashMap$EntryIterator.next(HashMap.java:932)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:560)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:548)
at com.sun.javafx.scene.KeyboardShortcutsHandler.processAccelerators(KeyboardShortcutsHandler.java:286)
at com.sun.javafx.scene.KeyboardShortcutsHandler.dispatchBubblingEvent(KeyboardShortcutsHandler.java:119)
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.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.Scene$KeyHandler.process(Scene.java:3513)
at javafx.scene.Scene$KeyHandler.access$2300(Scene.java:3472)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:1904)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2270)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:136)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:100)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:163)
at com.sun.glass.ui.View.handleKeyEvent(View.java:518)
at com.sun.glass.ui.View.notifyKey(View.java:951)
at com.sun.glass.ui.win.WinApplication._enterNestedEventLoop(Native Method)
at com.sun.glass.ui.Application.enterNestedEventLoop(Application.java:383)
at com.sun.glass.ui.EventLoop.enter(EventLoop.java:83)
at com.sun.javafx.tk.quantum.QuantumToolkit.enterNestedEventLoop(QuantumToolkit.java:520)
at javafx.stage.Stage.showAndWait(Stage.java:397)
at org.kls.md.censusassistant.DialogController.showAndWait(DialogController.java:94)
at org.kls.md.censusassistant.DCMainEditor.handleEncDetails(DCMainEditor.java:287)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:75)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:279)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1435)
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:28)
at javafx.event.Event.fireEvent(Event.java:171)
at javafx.scene.Node.fireEvent(Node.java:6863)
at javafx.scene.control.Button.fire(Button.java:179)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:193)
at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:336)
at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:329)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:64)
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.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:3328)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3168)
at javafx.scene.Scene$MouseHandler.access$1900(Scene.java:3123)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1563)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2265)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:250)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:173)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:292)
at com.sun.glass.ui.View.handleMouseEvent(View.java:528)
at com.sun.glass.ui.View.notifyMouse(View.java:922)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$100(WinApplication.java:29)
at com.sun.glass.ui.win.WinApplication$3$1.run(WinApplication.java:73)
at java.lang.Thread.run(Thread.java:722)

EDIT 05/05/2013 -- I altered my code so that I would no longer be clearing former settings of defaultButton, i.e. just one call to setDefaultButton(true). The idea was that since the backing code performs this operation using a Runnable, -perhaps- this was the cause of the ConcurrentModificationExceptions I was getting.

So, now it does appear that in fact I am no longer able to trigger ConcurrentModificationExceptions in my code when I attempt to change between btnDxAdd and btnWindowCommit as the default button.

However ...

I am also no longer able to get the behavior I want. When I am editing in the txtDx control now, I can press the Enter key all day and the button will not fire. I have confirmed that my focusProperty ChangeListener fired and that a single call to txtDx.setDefaultButton(true) was made. Regardless, I am not able to get the behavior I want.

scottb
  • 9,908
  • 3
  • 40
  • 56

1 Answers1

2

Bug about javadoc clarification : javafx-jira.kenai.com/browse/RT-30200

Bug about implementation : https://javafx-jira.kenai.com/browse/RT-30206

I watched code from skin class, which is observable from OpenJFX:

What is happening, when you setDefault(true/false):

Runnable defaultButtonRunnable = new Runnable() {
        public void run() {
            if (!getSkinnable().isDisabled()) {
                getSkinnable().fire();
            }
        }
    };

private void setDefaultButton(boolean value) {

    KeyCode acceleratorCode = KeyCode.ENTER;
    defaultAcceleratorKeyCodeCombination = 
            new KeyCodeCombination(acceleratorCode);

    if (! value) {
        /*
        ** first check of there's a default button already
        */
        Runnable oldDefault = getSkinnable().getParent().getScene().getAccelerators().get(defaultAcceleratorKeyCodeCombination);
        if (!defaultButtonRunnable.equals(oldDefault)) {
            /*
            ** is it us?
            */
            getSkinnable().getParent().getScene().getAccelerators().remove(defaultAcceleratorKeyCodeCombination);
        }
    }
    getSkinnable().getParent().getScene().getAccelerators().put(defaultAcceleratorKeyCodeCombination, defaultButtonRunnable);
}

How it works : when you set new button as default, it finds the existing default button, and removes acelerator on ENTER key press from acelerators list stored at scene. And adds itself as a default button. So you don't need to setDefault on other buttons.

Alexander Kirov
  • 3,624
  • 1
  • 19
  • 23
  • Thanks for sharing your research. I'm getting ConcurrentModificationExceptions whenever I try to run code in my app that changes setDefaultButton() and setCancelButton() values in response to events on a ChangeListener() attached to a TextField's FocusedProperty. Since I have no concurrency, am not iterating over collections, and don't even have the HashTable in my app that generates the exception, I must conclude that JavaFX is bugged in some way. I've had to remove this code from my app for now (it works fine without the ChangeListener). This is why I really hate UI design. – scottb May 05 '13 at 15:50
  • Seems, this modification exception arises from some kind of iteration over collection, from which we try to remove smth. – Alexander Kirov May 05 '13 at 15:55
  • I cannot reproduce the issue using 1 button, 1 text field, and manipulation on defaultness in change listener. Could you attach a piece of code, or file an issue on that? I used 8.0b87 – Alexander Kirov May 05 '13 at 16:00
  • Agreed. It's nothing I have control over so I can't fix it ... I've wasted hours of my life assuring that I wasn't getting reentrant event calls and that the errors weren't related to setting properties on disabled or invisible controls. I'll try changing my code to do just one setDefaultButton(true) instead of trying to reset the others to false first .... Based on your research, it is possible that maybe I am causing the errors by creating multiple property setting threads in the API. I don't think this is the case, though. – scottb May 05 '13 at 16:02
  • I'd love to provide some code. My app uses one stage with two panes. The pane which is displayed depends on context, and each has its own set of defaultButtons. However, on one pane there is a TextField for which I would like a different defaultButton while editing (the rest of the pane uses its base defaultButtons); hence the FocusedProperty event. If I can figure a way to show the design concisely with some code, I'll post it. – scottb May 05 '13 at 16:07
  • The stack trace is quite long ... hesitate to post it since I'm not sure how much of it is helpful. But I'll add it to the post. – scottb May 05 '13 at 16:59