JavaFX ScrollPane PickOnBounds (частичная прозрачность мыши) - PullRequest
3 голосов
/ 29 января 2020

В настоящее время я работаю над GUI программой для редактирования видео на основе JavaFX. Здесь я столкнулся с проблемой с ScrollPane s. Я не могу понять, как сделать ScrollPane (действительно) pickOnBounds=„false“. Это означает, что я могу нажать ScrollPane на Node ниже. Я думаю, что это может быть проблема с окном просмотра ScrollPane. Ниже я постарался дать чрезвычайно упрощенную версию нашей программы:

Стили. css

.scroll-pane > .viewport {
    -fx-background-color: transparent;
    -fx-background-radius: 4;
}

sample.f xml

<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.ScrollPane?>

<StackPane
  xmlns:fx="http://javafx.com/fxml"
  xmlns="http://javafx.com/javafx"
  fx:controller="sample.Controller" fx:id="myStackPane">
    <Pane onMousePressed="#onMouseInteraction" style="-fx-background-color: red"/>
    <AnchorPane fx:id="markerIconPane" pickOnBounds="false">
        <ScrollPane style="-fx-background-color: transparent" pickOnBounds="false" vbarPolicy="ALWAYS" hbarPolicy="ALWAYS"/>
    </AnchorPane>
</StackPane>

Контроллер. java

package sample;

import javafx.scene.input.MouseEvent;
import javafx.fxml.FXML;

public class Controller implements Initializable {
    //...
    public void onMouseInteraction(MouseEvent mouseEvent) {
        System.out.println(mouseEvent.getX());
    }
}

Main. java

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        root.getStylesheets().add("./Styles.css");
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

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

Теперь вы можете четко видеть полосы прокрутки в левом верхнем углу. Когда вы нажимаете на красный цвет за пределами полос прокрутки, вызывается onMouseInteraction(), но когда вы нажимаете внутри полос прокрутки, ничего не происходит.

Заранее спасибо
Филипп

Ответы [ 2 ]

1 голос
/ 29 января 2020

Предполагая, что элемент Pane является областью просмотра, которую вы упомянули; Вы можете добавить обработчик onMouseInteraction в качестве EventFilter в стек стека, чтобы он находился в правильной цепочке событий. Например,

myStackPane.addEventFilter(Controller::onMouseInteraction) ;

. Таким образом, Pane не требуется в качестве дополнительного слоя событий, хотя недостатком является то, что вы не можете добиться добавления EventFilter только из файла f xml. Однако вы можете добавить оператор в метод init контроллера в качестве альтернативы.

Если вы хотите визуальное наложение в Pane, вы можете установить для его свойства mouseTransparent значение true. Таким образом, он не будет мешать событию (как если бы его не было)

Если вы не можете поместить EventFilter в цепочку событий, на самом деле не существует хорошего решения. В javafx8 я «взломал» систему, распространяя событие с измененным PickResult. Но в более поздних версиях использованные (удаленные) методы стали частными (т.е. не могут использоваться без сильного отражения).


РЕДАКТИРОВАТЬ

После тщательного перечитывания вашего комментарии, я думаю, что я понимаю проблему немного больше. Довольно сложно извлечь что-то подобное, поэтому я создал небольшой тест на то, как бы это решить. Я не уверен, что он пригоден для вас, так как я понятия не имею, как работает подструктура. Итак, здесь мы go:

Идея состоит в том, чтобы создать родительский узел, который сам по себе не имеет размера, а только размещает его дочерние элементы. Таким образом, он не будет блокировать eventPick, но все равно сможет отображать компоненты поверх других (так как отсечение по умолчанию не включено в JavaFX).

Недостатком этого является то, что вам нужно много заботиться самостоятельно, так как родитель должен иметь размер '0'. Таким образом, такие вещи, как скроллинг / дочерний макет, вам нужно сделать самостоятельно.

Ниже я создал пример, в котором используется только переопределенный метод #layoutChildren. Но в некоторых случаях (например, если вам нужна прокрутка), может быть полезно реализовать собственный скин, специально предназначенный для этой задачи. Что бы ни отвечало вашим потребностям.

public class Test extends Application
{

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



    @Override
    public void start(Stage primaryStage) throws Exception
    {
        Scene s = new Scene(this.createContent());
        primaryStage.setScene(s);
        primaryStage.show();
    }



    protected Parent createContent()
    {
        Pane forGround= this.createForGround();
        StackPane root = new StackPane(this.createBackground(), forGround)
        {
            @Override
            protected void layoutChildren()
            {
                // since the foreGround is not managed, you need to trigger the 'child-layout' manually by calling #requestLayout
                super.layoutChildren();
                forGround.requestLayout();
            }
        };

        return root;
    }

    protected Node createBackground()
    {
        GridPane pane = new GridPane();
        pane.setPadding(new Insets(5.0));
        pane.setVgap(5.0);
        pane.setHgap(5.0);

        for(int i = 0; i < 2; i++)
        {
            for(int j = 0; j < 2; j++)
            {
                Button b = new Button("test " + ((i * 2) + j));
                // some layout to make things testable
                GridPane.setHgrow(b, Priority.ALWAYS);
                GridPane.setVgrow(b, Priority.ALWAYS);
                GridPane.setFillHeight(b, true);
                GridPane.setFillWidth(b, true);
                GridPane.setHalignment(b, HPos.CENTER);
                GridPane.setValignment(b, VPos.CENTER);
                b.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
                // let us know when clicked
                b.setOnAction(E -> System.out.println(b.getText()));
                pane.add(b, i, j);
            }
        }

        return pane;
    }

    protected Pane createForGround()
    {
        Circle c1 = new Circle(20.0);
        c1.setFill(Color.BLUE);
        c1.setManaged(false);
        c1.setPickOnBounds(false);
        // let us know when clicked
        c1.setOnMouseClicked(E -> System.out.println("clicked blue"));

        Circle c2 = new Circle(20.0);
        c2.setFill(Color.RED);
        c2.setManaged(false);
        c2.setPickOnBounds(false);
        // let us know when clicked
        c2.setOnMouseClicked(E -> System.out.println("clicked red"));



        ////////////////////////////////////////////////////////////////
        // this is the old/current method
        ////////////////////////////////////////////////////////////////

        // VBox pane = new VBox();
        // pane.setAlignment(Pos.CENTER);
        // pane.setStyle("-fx-background-color:#AAAAAAAA");
        // pane.getChildren().addAll(c1, c2);


        ////////////////////////////////////////////////////////////////
        // this is the new/improved method
        ////////////////////////////////////////////////////////////////

        Pane pane = new Pane()
        {
            protected void layoutChildren()
            {
                // in here you need to layout the children yourself as the default method will not work due to this Pane being size '0'

                // get parent bounds instead of this Pane, as it has size '0'
                double pw = ((Region)this.getParent()).getWidth();
                double ph = ((Region)this.getParent()).getHeight();

                // resize
                c1.setRadius(ph / 4);
                c2.setRadius(ph / 4);

                c1.relocate(((pw - (ph / 2)) / 2), 0);
                c2.relocate(((pw - (ph / 2)) / 2), ph / 2);
            };
        };
        // make sure you set the pickOnBounds to 'false' on the pane as well, otherwise it will still create a(n event pickable)box around its children, even though it is not rendered!
        pane.setPickOnBounds(false);
        // set managed to false, so it will remain size 0
        pane.setManaged(false);
        // set style to make things visible in case of oddities
        pane.setStyle("-fx-background-color:#AAAAAAAA");
        pane.getChildren().addAll(c1, c2);

        return pane;
    }
}

Опять же, я понятия не имею, применимо ли это в вашем случае (так как мне не хватает деталей реализации). Но каким бы способом вы ни повернули его, я подозреваю, что вы не сможете достичь этого без переопределения механики по умолчанию или создания своего собственного родительского типа /skin.

.
0 голосов
/ 31 января 2020

Не знаю, подходит ли вам это решение, но вы можете изменить файл sample.f xml и добавить onMousePressed="#onMouseInteraction" в ScrollPane.

Вот файл sample.f xml с моим предложенным изменением.

<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.ScrollPane?>

<StackPane
  xmlns:fx="http://javafx.com/fxml"
  xmlns="http://javafx.com/javafx"
  fx:controller="guitests.jfxtests.sample.Controller" fx:id="myStackPane">
    <Pane onMousePressed="#onMouseInteraction" style="-fx-background-color: red"/>
    <AnchorPane fx:id="markerIconPane" pickOnBounds="false">
        <ScrollPane onMousePressed="#onMouseInteraction" style="-fx-background-color: transparent" pickOnBounds="false" vbarPolicy="ALWAYS" hbarPolicy="ALWAYS"/>
    </AnchorPane>
</StackPane>

Затем, когда вы щелкаете внутри ScrollPane, метод onMouseInteraction() вызывается так же, как это когда вы нажимаете внутри Pane.

...