3

I'm building a GUI application with javafx that needs PannableProperty from the ScrollPane to work when the user drag the content of it from anywhere.

In oracle docs they say about the "pannableProperty":

Specifies whether the user should be able to pan the viewport by using the mouse. If mouse events reach the ScrollPane (that is, if mouse events are not blocked by the contained node or one of its children) then pannable is consulted to determine if the events should be used for panning.

So my problem is the mouse event cannot reach the ScrollPane..

Anyone has a clue how to make it possible?

this is a simple code to test it:

        ScrollPane root = new ScrollPane();
        root.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        root.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        root.setPannable(true);

        VBox v = new VBox(10);

        TitledPane c1 = new TitledPane("test: ", new HBox(new Label("test: "), new TextField()));
        HBox c2 = new HBox(new Label("we are just in HBox  "), new TextField());
        Label c3 = new Label("I'm just a label and pannableProperty works here");
        TitledPane c4 = new TitledPane("test4", new HBox(new Label("test: "), new TextField()));
        AnchorPane c5 = new AnchorPane();
        c5.setPrefSize(100, 100);

        v.getChildren().addAll(c1, c2, c3, c4, c5);

        root.setContent(v);

        Scene scene = new Scene(root, 300, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
Hany Alom
  • 67
  • 5
  • I tried you sample code and panning is working. Fyi: Panning the scrollpane with the mouse is done by dragging with the right mouse button. – eckig Dec 20 '14 at 13:02
  • it works only when you drag from the labels out side the titledpane, but when you try it from titledpane content the titledpane will be set as focused and the scrollpane wont handle the event – Hany Alom Dec 20 '14 at 13:14

1 Answers1

1

Another tricky one :-)

The default Skin implementation of the TitledPane is a subclass of SkinBase and the default constructor (which gets invoked by TitledPaneSkin) does this (shortened version):

protected SkinBase(final C control) {       
    // Default behavior for controls is to consume all mouse events
    consumeMouseEvents(true);
}

So we need to reverse this, unfortunately you have to do reflection for this:

    TitledPane c1 = new TitledPane("test: ", new HBox(new Label("test: "), new TextField()));
    c1.skinProperty().addListener((w,o,n)-> {
        if(n instanceof SkinBase) {
            SkinBase<?> skinbase = (SkinBase<?>) n;
            try {
                Method m = SkinBase.class.getDeclaredMethod("consumeMouseEvents", Boolean.TYPE);

                AccessController.doPrivileged((PrivilegedAction<Void>) () -> {

                    boolean wasAccessible = m.isAccessible();
                    try {
                        m.setAccessible(true);
                        m.invoke(skinbase, false);
                        return null;
                    }
                    catch(ReflectiveOperationException e) { throw new IllegalStateException(e); }
                    finally {
                        m.setAccessible(wasAccessible);
                    }
                });
            } catch (ReflectiveOperationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });

And now it should work, at least it does in my test application.


EDIT #1:

Doing this resets the focus during mouse operations, which renders the TitledPane somewhat unusable. So we are now messing with the focus system:

ScrollPane root = new ScrollPane();
root.setFocusTraversable(false);

Scene scene = new Scene(root, 300, 300);
scene.focusOwnerProperty().addListener((w,o,n)->
    if(n == root && o != null) {
        o.requestFocus();
    }
});

Basically what we are doing here is that we re-focus the previously focussed component if the newly focused element is the ScrollPane.

eckig
  • 10,964
  • 4
  • 38
  • 52
  • that is a nice tricky way you used there, but with it you removed every event on the titledpane including the collapse on mouse click on the tittle... – Hany Alom Dec 20 '14 at 15:18
  • yep it works now, but now i have to do this trick to all titledpanes i have in the scene even if they was loaded from FXML file. if there is any general way to fix it, it will be better. – Hany Alom Dec 20 '14 at 16:00
  • The "only" thing `TitledPane` specific is the reflection part which you can move to a utility class or your own custom TitledPane subclass. But I can understand you, this solution is not ideal. – eckig Dec 20 '14 at 16:02