JavaFX combobox hides as soon as mouse moves

It’s best to just use lists. Combo boxes have several related issues such as not appearing on full screen and even rendering outside the window bounds.

They act like a kind of pop out layer that JME has no knowledge of.

Saying that I guess one could be made using the regular z-based hierarchy. I’m playing around with a project that uses jfx. If I need one I’ll publish it in a separate repo for jfx controls. A default alert dialog would be useful, too. I also have things like memory and fps graphs lying around that might be useful to people.

2 Likes

Those would be helpful.
My current project has some very complex UIs for the gamemasters, and make heavy use of ComboBoxes. I will add to my todo list to look at why the mouse moving causes them to hide. I have not had any issues with the drop down not showing, but then again, I have do not run my applications in full screen.

Unfortunately I have not gotten very far on my todo list these two weeks home, and I go back to work on the 3rd. So some things will have to wait until I get home again. :sob:

Hello,

I have here a similar problem with a ColorPicker
(Which is basically the same like a ComboBox)
Whenever the drop down appears below
of the picker, I have no chance to select something
because it will disapear on mouse movement.
But when I align the window in a way, that the
selection area will be on top of the picker like here:


I am able to select a element without problems.
Seems to be odd.

1 Like

I just was testing the color picker and had the same issue. I did not attempt to align it though! Perhaps it is an alignment issue?


Interestingly context menus work. But also interesting when I do not base the context menu off of cursor position, and force it to be centered in the application, it is way off

x = -1;
y = -1;
if (x == -1 || y == -1) {
    x = rootNode.getScene().getWindow().getX() + rootNode.getScene().getHeight() / 2;
    y = rootNode.getScene().getWindow().getY() + rootNode.getScene().getWidth() / 2;
}
menu.show(rootNode, x, y);

Also interesting, when using alt-tab to select another window, the combobox or colorchooser will stay open and let you move the mouse to select something.

This circles around the same issue. They are drawn in a layer that JME doesn’t know about - which is why they are even drawn outside of JME itself. You will also notice that if you record your game they won’t appear.

I’m not sure as to why - or what layer they are drawn on. If someone - or I - can figure that out we can solve the whole issue - else a custom control or alternative will need to be made.

They are displayed as a PopupControl implemented as a object inside of: javafx.scene.control.skin.ComboBoxPopupControl
Which is an instance of a PopupWindow, which utilizes

Scene scene = SceneHelper.createPopupScene(popupRoot);

to create a new scene in its own window… this might get complicated

An actual window with no frame? Really?

Yep.

https://docs.oracle.com/javase/8/javafx/api/javafx/stage/PopupWindow.html

A PopupWindow is a secondary window which has no window decorations or title bar. It doesn’t show up in the OS as a top-level window. It is typically used for tool tip like notification, drop down boxes, menus, and so forth.

That is what it looks like, I am trying to find the underlying implementation right now. But that would make sense as to why it stays ontop of other windows even when the parent window is behind it:
image

EDIT: The SceneHelper being referenced is just a wrapper around the SceneAccessor interface, which is private to SceneHelper. Somehow the logic gets filled in, but I am not sure where… Perhaps in a platform specific jar.

EDIT 2: It is inside the javafx-graphics module, which has variants for Windows, Linux, OSX.
I am not sure what is going on in the source for each variant, I cannot find the specific implementations in the reop. The file in question is here: https://github.com/openjdk/jfx/blob/master/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java

@jayfella any ideas on if we can force these inside jme?

*looking into it now.

I’m not entirely sure it’s possible. At least at this stage I can’t find anything that would help. It creates a new window - which isn’t cool as far as JME is concerned. Let me think about it. I’m going to need some of this functionality, so at some point I’m going to be confronted by it.

1 Like

OK, so here is an idea.
We can get the contents of the popup by using:

@FXML
ComboBox box;

//... in some method
ComboBoxListViewSkin skin = box.getSkin();
Node display = skin.getPopupContent();

The display node is the contents of the popup.
This gives us access to the popup’s scene, and idk if it would be possible, but we could just change its parent, but that might break the listeners on it.
But more possibly, we could get the scene and render it inside of jme instead of the new window?

@jayfella So this is interesting, there was a similar issue with jfx in osx.

I am now wondering how does a fullscreen jfx scene work?

1 Like

I’ve had a play around and I’ve created a JME alternative for the ComboBox (not feature complete but working at a basic level).

Suggestions/Criticism very welcome. (I’ve had to reach out statically to JavaFxUI.getInstance(), if that can be avoid I’d certainly be happy)

I can’t help agreeing with tlf30 though, does JavaFX have a full screen mode it can be put into. In core JavaFx it looks like its Stage#setFullScreen but in JME we don’t have a stage

3 Likes

I just took a look at the code, it looks good. I will do some tests with it. I think the approach can also be used with context menus.

@richtea I played with your approach a bit. It works good, but will be a lot of work re-implementing all the features of every component this way. Instead, inside the show method, get the skinnable from the superclass ComboBoxBaseSkin, which is where the ComboBoxPopupControl gets the contents of the combobox from, then set that skin as the skin for the anchor pane. This approach can be applied to other components as well without having to re-build the functionality of each one.

The only issue with this approach is non of the show and hide events work correctly, but this could be fixed by adding the triggers into the overridden show and hide methods.

Good job! I look forward to seeing where you go with this. If you can get this figured out , it would be awesome!

I can see where the skin can be obtained

    ComboBoxListViewSkin<?> skin = (ComboBoxListViewSkin<?>) getSkin();

But I’m not sure what you mean by set that skin as the skin for the anchor pane (I’ve not really worked with skins before), although I can set it as the skin for a ListView (although it seems to just produce a blank listview if I do). I can see that the skin also has a getPopupContent() method but that also seems blank

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

I’ll try to find the time to take a look, I’m just finishing off a small project rn, but the fact that you made the first leap is promising :slight_smile:

At the very least it’s something we can work from and develop over time. Perhaps a Proto project would be of use.