JavaFX combobox hides as soon as mouse moves

I wasn’t able to get the skin to work (but very happy for someone with more JavaFx knowledge to take a look at it) so I’m going to settle for what I have. I’ve put a PR in if people would like the code added to jme-jfx-11 https://github.com/jayfella/jme-jfx-11/pull/7

Alternatively, I’ve produced a (far less clean, reflection filled) stand alone version that will work without changes to jme-jfx-11

import com.jayfella.jme.jfx.JavaFxUI;
import com.jme3.app.Application;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;

import java.lang.reflect.Field;

/**
 * This is a JME alternative for the ComboBox.
 *
 * It behaves broadly the same way as the core ComboBox, but is not feature complete.
 *
 * In particular:
 * It doesn't fully support keyboard selection (a mouse click is needed to close it)
 * It doesn't support sizing based on number of entries, only by pixels
 * It can go "offscreen" when it tries to open near the bottom of the screen
 *
 * @param <T>
 */
public class ComboBoxJMEBackCompat<T> extends ComboBox<T> {

    /**
     * This is for any comboBox
     */
    private static Runnable removeExistingPopup = () -> {};

    /**
     * This is for this combobox
     */
    Runnable removeListPopup;

    int maxHeight = 200;

    @Override
    public void show() {
        Bounds boundsInScene = localToScene(getBoundsInLocal());

        ListView<T> items = new ListView<>();
        items.setItems(this.getItems());
        items.setMinWidth(boundsInScene.getWidth());
        items.setMaxHeight(maxHeight);

        removeListPopup = attachPopup(items, boundsInScene.getMinX(), boundsInScene.getMaxY());

        items.setOnMousePressed(event -> {
            getSelectionModel().select(items.getSelectionModel().getSelectedItem());
            removeListPopup.run();
        });
    }

    @Override
    public void hide() {
        //do nothing, we're handling our own open/close (although keyboard based selection might need this?)
    }

    /**
     * Sets the height of the combobox when it opens (in pixels)
     * @param height
     */
    public void setListHeight(int height){
        this.maxHeight = height;
    }

    private static Runnable attachPopup(javafx.scene.Node node, double x, double y){
        Application app = (Application)getJavaFxUIPrivateMember("app");
        Group group = (Group)getJavaFxUIPrivateMember("group");

        removeExistingPopup.run();

        AnchorPane popupOverlay = new AnchorPane();
        popupOverlay.setMinWidth(app.getCamera().getWidth());
        popupOverlay.setMinHeight(app.getCamera().getHeight());
        popupOverlay.getChildren().add(node);

        node.setTranslateX(x);
        node.setTranslateY(y);

        group.getChildren().add(popupOverlay);

        removeExistingPopup = () -> {
            group.getChildren().remove(popupOverlay);
            removeExistingPopup = () -> {};
        };

        popupOverlay.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
            removeExistingPopup.run();
        });

        return removeExistingPopup;
    }

    private static Object getJavaFxUIPrivateMember(String memberName){

        try {
            JavaFxUI instance = JavaFxUI.getInstance();
            Field declaredField = instance.getClass().getDeclaredField(memberName);
            declaredField.setAccessible(true);
            return declaredField.get(instance);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

}

(If anyone wants to use this please consider it in the public domain)

2 Likes