Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8189677

RadioMenuItem fires extra NULL value in property

    XMLWordPrintable

Details

    • generic
    • generic

    Backports

      Description

        FULL PRODUCT VERSION :
        java version "1.8.0_152"
        Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
        Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)

        ADDITIONAL OS VERSION INFORMATION :
        Microsoft Windows [Version 10.0.15063]

        A DESCRIPTION OF THE PROBLEM :
        The RadioButton class and the RadioMenuItem class are very similar in that they are toggle buttons in toggle groups. However, they interact with the ToggleGroup differently, in a way that I believe is likely unintentional. ToggleButtons call ToggleGroup.clearSelectedToggle(), but RadioMenuItem calls ToggleGroup.selectToggle(null). The latter creates an extra event that affects the contract of the ToggleGroups "selectedToggleProperty"

        If someone is trying to use the oldValue / newValue in a ChangeListener, this will really complicate the code since the actual old value would be lost when the new value was set. I found someone asking about this on Stack Overflow:
        https://stackoverflow.com/questions/44403887/togglegroup-with-a-changelistenertoggle-always-throws-a-null-value-to-the-oldv

        I believe ToggleButton uses ToggleGroup.clearSelectedToggle() as intended, while RadioMenuItem uses ToggleGroup.selectToggle(null) which causes the null.

        From ToggleButton.java
            public final BooleanProperty selectedProperty() {
                if (selected == null) {
                    selected = new BooleanPropertyBase() {
                        @Override protected void invalidated() {
                            final boolean selected = get();
                            final ToggleGroup tg = getToggleGroup();
                            // Note: these changes need to be done before selectToggle/clearSelectedToggle since
                            // those operations change properties and can execute user code, possibly modifying selected property again
                            pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, selected);
                            notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTED);
                            if (tg != null) {
                                if (selected) {
                                    tg.selectToggle(ToggleButton.this);
                                } else if (tg.getSelectedToggle() == ToggleButton.this) {
                                    tg.clearSelectedToggle();
                                }
                            }
                        }

                        @Override
                        public Object getBean() {
                            return ToggleButton.this;
                        }

                        @Override
                        public String getName() {
                            return "selected";
                        }
                    };
                }
                return selected;
            }

        From RadioMenuItem.java
        @Override public final BooleanProperty selectedProperty() {
                if (selected == null) {
                    selected = new BooleanPropertyBase() {
                        @Override protected void invalidated() {
                            if (getToggleGroup() != null) {
                                if (get()) {
                                    getToggleGroup().selectToggle(RadioMenuItem.this);
                                } else if (getToggleGroup().getSelectedToggle() == RadioMenuItem.this) {
                                    getToggleGroup().selectToggle(null);
                                }
                            }

                            if (isSelected()) {
                                getStyleClass().add(STYLE_CLASS_SELECTED);
                            } else {
                                getStyleClass().remove(STYLE_CLASS_SELECTED);
                            }
                        }

                        @Override
                        public Object getBean() {
                            return RadioMenuItem.this;
                        }

                        @Override
                        public String getName() {
                            return "selected";
                        }
                    };
                }
                return selected;
            }



        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        Use the controls in the application. Changing the RadioButtons fires property change events that proceed from one state to the other, while the RadioMenuItems pass through an extra NULL state.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        That the toggle group would work the same in both of these cases
        ACTUAL -
        The RadioMenuItem would behave the same as the RadioButton and not fire the extra NULL event.

        REPRODUCIBILITY :
        This bug can be reproduced always.

        ---------- BEGIN SOURCE ----------

        import javafx.application.Application;
        import javafx.beans.binding.Bindings;
        import javafx.beans.binding.StringBinding;
        import javafx.scene.Scene;
        import javafx.scene.control.Label;
        import javafx.scene.control.MenuButton;
        import javafx.scene.control.RadioButton;
        import javafx.scene.control.RadioMenuItem;
        import javafx.scene.control.Toggle;
        import javafx.scene.control.ToggleGroup;
        import javafx.scene.layout.VBox;
        import javafx.stage.Stage;

        public class ToggleMenuItem extends Application {
        public static void main(String[] args) {
        Application.launch(ToggleMenuItem.class, args);
        }

        @Override
        public void start(Stage stage) {
        stage.setTitle("Toggle Test");
        VBox box = new VBox();
        MenuButton menuButton = new MenuButton();
        RadioMenuItem b1 = new RadioMenuItem("BUTTON ONE");
        RadioMenuItem b2 = new RadioMenuItem("BUTTON TWO");

        menuButton.getItems().addAll(b1, b2);
        ToggleGroup group = new ToggleGroup();
        group.getToggles().addAll(b1, b2);
        group.selectToggle(b1);

        Label label = new Label("BASE");
        box.getChildren().add(menuButton);
        box.getChildren().add(label);

        Label radioLabel = new Label("RADIO BASE");
        RadioButton rb1 = new RadioButton("RADIO ONE");
        RadioButton rb2 = new RadioButton("RADIO TWO");
        ToggleGroup radioGroup = new ToggleGroup();
        radioGroup.getToggles().addAll(rb1, rb2);
        radioGroup.selectToggle(rb2);

        box.getChildren().add(rb1);
        box.getChildren().add(rb2);
        box.getChildren().add(radioLabel);

        createBinding("RadioMenuItem", group, label);
        createBinding("RadioButton", radioGroup, radioLabel);

        System.out.println(System.getProperty("java.runtime.version"));

        final Scene scene = new Scene(box, 400, 400);
        stage.setScene(scene);
        stage.show();
        }
        private void createBinding(String groupName, ToggleGroup group, Label label) {
        StringBinding binding = Bindings.createStringBinding(() -> {
        Toggle selectedToggle = group.getSelectedToggle();
        if (selectedToggle == null) {
        System.out.println(String.format("--- %15s is NULL ---", groupName));
        return "NULL";
        }
        else {
        String text = (selectedToggle instanceof RadioMenuItem) ? ((RadioMenuItem) selectedToggle).getText() : ((RadioButton) selectedToggle).getText();
        System.out.println(String.format("%15s is not NULL (%s)", groupName, text));
        return text;
        }
        }, group.selectedToggleProperty());
        label.textProperty().bind(binding);
        }
        }

        ---------- END SOURCE ----------

        Attachments

          Issue Links

            Activity

              People

                aghaisas Ajit Ghaisas
                webbuggrp Webbug Group
                Votes:
                0 Vote for this issue
                Watchers:
                8 Start watching this issue

                Dates

                  Created:
                  Updated:
                  Resolved: