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

ComboBoxPopupControl: custom subclasses throw NPE

    Details

    • Subcomponent:
    • Understanding:
      Cause Known

      Description

      There are two variants, one happens on hiding the popup the other on moving the mouse over the arrow. Below is a minimal example to reproduce both:

      Variant A:
      - run the example as-is
      - open popup
      - hide popup and see the NPE

      top of stacktrace:

      Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
      at javafx.scene.control.skin.ComboBoxPopupControl.lambda$createPopup$913(ComboBoxPopupControl.java:464)
      at javafx.stage.PopupWindow.doAutoHide(PopupWindow.java:840)

      Variant B:
      - uncomment the line setting editable to true, compile and run
      - move mouse over arrow and see NPE

      Top of stacktrace:

      Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
      at javafx.scene.control.skin.ComboBoxBaseSkin.lambda$new$900(ComboBoxBaseSkin.java:108)
      at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)

      The technical reason for both is the access to the behavior via getBehavior which is package-private (thus can't be overridden) and defaults to null.

      A fix might be tricky: can't widen the method scope because Behavior is private api which must not leak into public api (I assume those are the rules?)

      A working hack is twofold:
      - create our own popup (c&p core, replacing getBehavior by our own) and reflectively replace super's field value
      - make sure we set editability only after the skin is installed

      The example:

      public class ComboCustomSkinNPE_Report extends Application {

          /**
           * Minimal custom skin and behavior.
           */
          private static class CustomComboSkin<T> extends ComboBoxPopupControl<T> {

              private Pane content;
              private Label display;
              private ComboBoxBaseBehavior<T> behavior;
       
              public CustomComboSkin(ComboBoxBase<T> control) {
                  super(control);
                  // most basic behavior
                  behavior = new CustomComboBehavior<>(control);
                  getPopupContent().setManaged(false);
                  getChildren().add(getPopupContent());
                  getChildren().add(getDisplayNode());
              }
              
              //---------- implement abstract methods
              @Override
              protected Node getPopupContent() {
                  if (content == null) {
                      content = new VBox(10);
                      for (int i = 0; i <5; i++) {
                          content.getChildren().add(new Label("dummy-item " + i));
                      }
                      
                  }
                  return content;
              }

              @Override
              protected TextField getEditor() {
                  return null;
              }

              @Override
              protected StringConverter<T> getConverter() {
                  return null;
              }

              @Override
              public Node getDisplayNode() {
                  if (display == null) {
                      display = new Label("nothing real");
                  }
                  return display;
              }
              
          }
          
          private static class CustomComboBehavior<T> extends ComboBoxBaseBehavior<T> {

              public CustomComboBehavior(ComboBoxBase<T> comboBox) {
                  super(comboBox);
              }
              
          }

          private Parent getContent() {
              ComboBox customFromStart = new ComboBox(FXCollections.observableArrayList("one", "two", "three")) {

                  @Override
                  protected Skin createDefaultSkin() {
                      return new CustomComboSkin<>(this);
                  }
                  
              };
              // if editable _before_ installing skin, throws NPE on moving mouse over arrow
              // customFromStart.setEditable(true);
              Pane coreLane = new HBox(10, new Label("combo core with custom skin"), customFromStart);
              
              return new VBox(10, coreLane);
          }
          
          @Override
          public void start(Stage primaryStage) throws Exception {
              primaryStage.setScene(new Scene(getContent()));
              primaryStage.show();
          }

          public static void main(String[] args) {
              launch(args);
          }
      }

        Attachments

        1. 8150951.patch
          8 kB
        2. 8150951.patch
          6 kB
        3. 8150951.patch
          5 kB
        4. 8150951.patch
          10 kB

          Activity

            People

            • Assignee:
              aghaisas Ajit Ghaisas
              Reporter:
              fastegal Jeanette Winzenburg
            • Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated: