Eventos transparentes de rolagem de captura de estágio JavaFX, se houver outra janela atrás

8

Eventos de mouse e rolagem se comportam de maneiras diferentes

diagrama

Eventos do mouse:

  1. O evento é capturado pelo mainStage

  2. O evento é capturado pelo mainStage

  3. O evento não é capturado

Eventos de rolagem:

  1. O evento é capturado pelo mainStage

  2. O evento é capturado pelo secondStage

  3. O evento não é capturado

Existe alguma maneira de o secondStage transparente não capturar eventos de rolagem?

Meu código:

Pane mainPane = new Pane(new Label("Main Stage"));
mainPane.setPrefSize(300, 300);
mainStage.setScene(new Scene(mainPane));

Stage secondStage = new Stage();
Pane secondPane = new Pane(new Label("Second Stage"));
secondPane.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
secondPane.setBorder(new Border(
    new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2))));
secondPane.setPrefSize(300, 300);
secondStage.setScene(new Scene(secondPane, Color.TRANSPARENT));
secondStage.initStyle(StageStyle.TRANSPARENT);

mainStage.getScene().setOnScroll(event -> System.out.println("Scroll in main stage"));
secondStage.getScene().setOnScroll(event -> System.out.println("Scroll in second stage"));
mainStage.getScene().setOnMouseClicked(event -> System.out.println("Click in main stage"));
secondStage.getScene().setOnMouseClicked(event -> System.out.println("Click in second stage"));

mainStage.show();
secondStage.show();

Versão Java: 1.8.0_201 (64 bits), Windows 10

editar: o exemplo é uma simplificação com apenas duas janelas. Disparar o evento programaticamente implica descobrir qual estágio é imediatamente mais baixo e esse é outro problema em si.

geh
fonte
Você já tentou setMouseTransparent? Não tenho muita certeza de como isso funcionaria com janelas transparentes.
Avi
@Avi Ele se comporta exatamente o mesmo quando altera a propriedade transparente rato nos painéis
geh
:( Isso é lamentável que provavelmente vai ser incapaz de ajudar porque eu mesmo estou brincar com JFXTextArea de JFoenix para obtê-lo a trabalhar.
Avi
@Avi Estou muito surpreso que o evento possa ser propagado para outras janelas, mas não para janelas do mesmo aplicativo, pode ser um bug. Obrigado de qualquer maneira
geh
Perdoe minha ignorância, mas como você gera um evento de rolagem usando o código de exemplo que você postou?
Abra

Respostas:

1

Pode ser uma grande coincidência o fato de termos também fornecido a mesma solução de janela transparente por não termos o recurso de gerenciar índices z de estágios. E encontramos exatamente o mesmo problema que o seu. ou seja, eventos de rolagem que não se propagam para os estágios subjacentes. Usamos a abordagem abaixo, sem saber se isso pode ajudá-lo:

Primeiramente, construímos uma classe Singleton que mantém uma referência de Node que atualmente é pairada.

Então, quando criamos qualquer estágio normal, incluímos os manipuladores abaixo na cena desse novo estágio. O principal aqui é que, os eventos do mouse ainda são capazes de passar pelo estágio transparente para a janela subjacente, acompanhar o nó que fica embaixo do mouse.

scene.addEventFilter(MouseEvent.MOUSE_EXITED_TARGET, e -> {
    hoverNode.set(null);
});
scene.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
    hoverNode.set(e.getTarget());
});

Na cena da janela transparente, incluímos os manipuladores abaixo para delegar os eventos de rolagem no nó subjacente.

scene.addEventFilter(ScrollEvent.SCROLL, e -> {
    if (hoverNode.get() != null) {
        Event.fireEvent(hoverNode.get(), e);
    }
});
scene.addEventHandler(ScrollEvent.SCROLL, e -> {
    if (hoverNode.get() != null) {
        Event.fireEvent(hoverNode.get(), e);
    }
});

Tenho certeza de que essa não é a maneira mais desejada. Mas isso abordou nosso problema. :)

Abaixo está o código de demonstração rápida do que quero dizer.

import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

import java.util.stream.IntStream;

public class ScrollThroughTransparentStage_Demo extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        stage.setTitle("Main Window");
        VBox root = new VBox(buildScrollPane());
        root.setStyle("-fx-background-color:#888888;");
        root.setSpacing(10);
        root.setPadding(new Insets(10));

        Button normalStageBtn = new Button("Normal Stage");
        normalStageBtn.setOnAction(e -> {
            Stage normalStage = new Stage();
            normalStage.initOwner(stage);
            Scene normalScene = new Scene(buildScrollPane(), 300, 300);
            addHandlers(normalScene);
            normalStage.setScene(normalScene);
            normalStage.show();
        });

        CheckBox allowScrollThrough = new CheckBox("Allow scroll through transparency");
        allowScrollThrough.setSelected(true);

        HBox buttons = new HBox(normalStageBtn);
        buttons.setSpacing(20);
        root.getChildren().addAll(allowScrollThrough,buttons);
        Scene scene = new Scene(root, 600, 600);
        addHandlers(scene);
        stage.setScene(scene);
        stage.show();

        /* Transparent Stage */
        Stage transparentStage = new Stage();
        transparentStage.initOwner(stage);
        transparentStage.initStyle(StageStyle.TRANSPARENT);
        Pane mainRoot = new Pane();
        Pane transparentRoot = new Pane(mainRoot);
        transparentRoot.setStyle("-fx-background-color:transparent;");
        Scene transparentScene = new Scene(transparentRoot, Color.TRANSPARENT);
        transparentStage.setScene(transparentScene);
        transparentScene.addEventFilter(ScrollEvent.SCROLL, e -> {
            if (allowScrollThrough.isSelected() && HoverNodeSingleton.getInstance().getHoverNode() != null) {
                Event.fireEvent(HoverNodeSingleton.getInstance().getHoverNode(), e);
            }
        });
        transparentScene.addEventHandler(ScrollEvent.SCROLL, e -> {
            if (allowScrollThrough.isSelected() && HoverNodeSingleton.getInstance().getHoverNode() != null) {
                Event.fireEvent(HoverNodeSingleton.getInstance().getHoverNode(), e);
            }
        });
        determineStageSize(transparentStage, mainRoot);
        transparentStage.show();

        Button transparentStageBtn = new Button("Transparent Stage");
        transparentStageBtn.setOnAction(e -> {
            MiniStage miniStage = new MiniStage(mainRoot);
            ScrollPane scrollPane = buildScrollPane();
            scrollPane.setPrefSize(300, 300);
            miniStage.setContent(scrollPane);
            miniStage.show();
        });
        buttons.getChildren().add(transparentStageBtn);
    }

    private static void determineStageSize(Stage stage, Node root) {
        DoubleProperty width = new SimpleDoubleProperty();
        DoubleProperty height = new SimpleDoubleProperty();
        DoubleProperty shift = new SimpleDoubleProperty();
        Screen.getScreens().forEach(screen -> {
            Rectangle2D bounds = screen.getVisualBounds();
            width.set(width.get() + bounds.getWidth());

            if (bounds.getHeight() > height.get()) {
                height.set(bounds.getHeight());
            }
            if (bounds.getMinX() < shift.get()) {
                shift.set(bounds.getMinX());
            }
        });
        stage.setX(shift.get());
        stage.setY(0);
        stage.setWidth(width.get());
        stage.setHeight(height.get());
        root.setTranslateX(-1 * shift.get());
    }

    private void addHandlers(Scene scene) {
        scene.addEventFilter(MouseEvent.MOUSE_EXITED_TARGET, e -> {
            HoverNodeSingleton.getInstance().setHoverNode(null);
        });
        scene.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
            HoverNodeSingleton.getInstance().setHoverNode(e.getTarget());
        });
    }

    private ScrollPane buildScrollPane() {
        VBox vb = new VBox();
        vb.setSpacing(10);
        vb.setPadding(new Insets(15));
        IntStream.rangeClosed(1, 100).forEach(i -> vb.getChildren().add(new Label(i + "")));
        ScrollPane scrollPane = new ScrollPane(vb);
        return scrollPane;
    }

    class MiniStage extends Group {
        private Pane parent;
        double sceneX, sceneY, layoutX, layoutY;
        protected BorderPane windowPane;
        private BorderPane windowTitleBar;
        private Label labelTitle;
        private Button buttonClose;

        public MiniStage(Pane parent) {
            this.parent = parent;
            buildRootNode();
            getChildren().add(windowPane);
            addEventHandler(MouseEvent.MOUSE_PRESSED, e -> toFront());
        }

        @Override
        public void toFront() {
            parent.getChildren().remove(this);
            parent.getChildren().add(this);
        }

        public void setContent(Node content) {
            // Computing the bounds of the content before rendering
            Group grp = new Group(content);
            new Scene(grp);
            grp.applyCss();
            grp.requestLayout();
            double width = grp.getLayoutBounds().getWidth();
            double height = grp.getLayoutBounds().getHeight() + 30; // 30 title bar height
            grp.getChildren().clear();

            windowPane.setCenter(content);
            // Centering the stage
            Rectangle2D screenBounds = Screen.getPrimary().getBounds();
            setX(screenBounds.getWidth() / 2 - width / 2);
            setY(screenBounds.getHeight() / 2 - height / 2);
        }

        public Node getContent() {
            return windowPane.getCenter();
        }

        public void setX(double x) {
            setLayoutX(x);
        }

        public void setY(double y) {
            setLayoutY(y);
        }

        public void show() {
            if (!parent.getChildren().contains(this)) {
                parent.getChildren().add(this);
            }
        }

        public void hide() {
            parent.getChildren().remove(this);
        }

        private void buildRootNode() {
            windowPane = new BorderPane();
            windowPane.setStyle("-fx-border-width:2px;-fx-border-color:#444444;");
            labelTitle = new Label("Mini Stage");
            labelTitle.setStyle("-fx-font-weight:bold;");
            labelTitle.setMaxHeight(Double.MAX_VALUE);
            buttonClose = new Button("X");
            buttonClose.setFocusTraversable(false);
            buttonClose.setStyle("-fx-background-color:red;-fx-background-radius:0;-fx-background-insets:0;");
            buttonClose.setOnMouseClicked(evt -> hide());

            windowTitleBar = new BorderPane();
            windowTitleBar.setStyle("-fx-border-width: 0 0 2px 0;-fx-border-color:#444444;-fx-background-color:#BBBBBB");
            windowTitleBar.setLeft(labelTitle);
            windowTitleBar.setRight(buttonClose);
            windowTitleBar.setPadding(new Insets(0, 0, 0, 10));
            windowTitleBar.getStyleClass().add("nonfocus-title-bar");
            windowPane.setTop(windowTitleBar);
            assignTitleBarEvents();
        }

        private void assignTitleBarEvents() {
            windowTitleBar.setOnMousePressed(this::recordWindowLocation);
            windowTitleBar.setOnMouseDragged(this::moveWindow);
            windowTitleBar.setOnMouseReleased(this::resetMousePointer);
        }

        private final void recordWindowLocation(final MouseEvent event) {
            sceneX = event.getSceneX();
            sceneY = event.getSceneY();
            layoutX = getLayoutX();
            layoutY = getLayoutY();
            getScene().setCursor(Cursor.MOVE);
        }

        private final void resetMousePointer(final MouseEvent event) {
            // Updating the new layout positions
            setLayoutX(layoutX + getTranslateX());
            setLayoutY(layoutY + getTranslateY());

            // Resetting the translate positions
            setTranslateX(0);
            setTranslateY(0);
            getScene().setCursor(Cursor.DEFAULT);
        }

        private final void moveWindow(final MouseEvent event) {
            double offsetX = event.getSceneX() - sceneX;
            double offsetY = event.getSceneY() - sceneY;
            setTranslateX(offsetX);
            setTranslateY(offsetY);
            event.consume();
        }
    }
}

/**
 * Singleton class.
 */
class HoverNodeSingleton {
    private static HoverNodeSingleton INSTANCE = new HoverNodeSingleton();
    private EventTarget hoverNode;

    private HoverNodeSingleton() {
    }

    public static HoverNodeSingleton getInstance() {
        return INSTANCE;
    }

    public EventTarget getHoverNode() {
        return hoverNode;
    }

    public void setHoverNode(EventTarget hoverNode) {
        this.hoverNode = hoverNode;
    }
}
Sai Dandem
fonte
Estávamos pensando em uma solução alternativa e essa solução resolve o problema e é provavelmente a opção que escolhemos. Obrigado pela resposta detalhada.
geh
1

Não sei se isso está certo ou não, mas você pode vincular propriedades:

secondStage.getScene().onScrollProperty().bind(mainStage.getScene().onScrollProperty());
dimaMS
fonte
1
Eu não acho que esse código esteja relacionado à pergunta: você está vinculando a propriedade scroll da segunda página à primeira página, para que ele execute o mesmo ouvinte se você rolar a primeira ou a segunda
Ahmed Emad
Como diz @AhmedEmad, este código não está relacionada com a pergunta que faço
geh
0

Você pode criar um distribuidor de eventos personalizado que ignorará os eventos que você não deseja:

public class CustomEventDispatcher extends BasicEventDispatcher {
    @Override
    public Event dispatchEvent(Event event, EventDispatchChain tail) {
        if(event instanceof ScrollEvent) {
            return null;
        } else {
            return super.dispatchEvent(event, tail);
        }
    }
}

Então coloque isso no seu palco:

secondStage.setEventDispatcher(new CustomEventDispatcher());
Steve
fonte
O resultado ainda é inesperado, agora no caso 2, ninguém captura os eventos de rolagem, mas minha intenção é se comportar da mesma forma que os eventos do mouse. Obrigado pela proposta
geh
0

Não sei como isso funciona no contexto de estágios, mas para formas simples, faz diferença se você define a cor de preenchimento como Color.TRANSPARENTjusta ou apenas null. Usando qualquer Colorcaptura de eventos, enquanto nullnão.

mipa
fonte
usando secondPane.getScene().setFill(null)ou new BackgroundFill(null, CornerRadii.EMPTY, Insets.EMPTY)se comporta exatamente neste caso
geh
0

Você pode fazer isso ignorando o evento no segundo estágio usando o dispatcher de eventos usando esta resposta por @Slaw. Você pode entender tudo sobre EventDispatcher
https://stackoverflow.com/a/51015783/5303683.
Em seguida, você pode disparar seu próprio evento usando esta resposta por DVarga https://stackoverflow.com/a/40042513/5303683 Desculpe, não tenho tempo para tentar e fazer um exemplo completo disso

Ahmed Emad
fonte
O que pretendo é que o evento não seja capturado, como é o caso dos eventos do mouse. Capture o evento no secondStage e inicie-o programaticamente no mainStage, conforme proposto por @Abra, se resolver o exemplo simplificado que apresento, mas não um caso real com mais de duas janelas, pois devo descobrir qual janela está logo atrás e outro problema em si.
geh
posso perguntar qual é a razão por trás do que você quer?
Ahmed Emad
Claro @AhmedEmad. Atualmente, temos um aplicativo com vários widgets que você pode posicionar na tela, e cada um deles é um palco. O JavaFX não permite modificar o eixo Z das janelas; portanto, estamos tentando pintar todos os widgets em um único estágio transparente, uma solução que nos satisfaz, exceto pelo problema que explico nesta consulta. Também estamos observando que uma única janela melhora o consumo de memória e desempenho, o que nos empurra para resolver este problema
geh