JavaFX 2 -- Setting the defaultButton property: mutually exclusive?


Question

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.

1
1
5/6/2013 3:04:22 AM

Accepted Answer

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.

2
5/5/2013 3:42:40 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon