Eventos de desplazamiento de captura de etapa JavaFX transparentes si hay otra ventana detrás

8

Los eventos del mouse y los eventos de desplazamiento se comportan de diferentes maneras

diagrama

Eventos del mouse:

  1. El evento es capturado por mainStage

  2. El evento es capturado por mainStage

  3. El evento no se captura

Eventos de desplazamiento:

  1. El evento es capturado por mainStage

  2. El evento es capturado por secondStage

  3. El evento no se captura

¿Hay alguna forma de que secondStage transparente no capture eventos de desplazamiento?

Mi 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();

Versión de Java: 1.8.0_201 (64 bits), Windows 10

editar: El ejemplo es una simplificación con solo dos ventanas. Activar el evento mediante programación implica descubrir qué etapa es inmediatamente inferior y ese es otro problema en sí mismo.

geh
fuente
¿Lo has intentado setMouseTransparent? Sin embargo, no estoy completamente seguro de cómo funcionaría con ventanas transparentes.
Avi
@Avi Se comporta exactamente igual cuando se cambia la propiedad transparente del mouse en los paneles
geh
:( Eso es lamentable. Probablemente no podré ayudar porque yo mismo estoy jugando con JFXTextArea de JFoenix para que funcione
Avi
@Avi Estoy muy sorprendido de que el evento se pueda propagar a otras ventanas pero no a las ventanas de la misma aplicación, puede ser un error. Gracias de todos modos
geh
Disculpa mi ignorancia, pero ¿cómo generas un evento de desplazamiento utilizando el código de muestra que publicó?
Abra

Respuestas:

1

Puede ser una gran coincidencia, que también vinimos con la misma solución de ventana transparente debido a que no tenemos la función de administrar el índice z de etapas. Y encontramos exactamente el mismo problema que el tuyo. es decir, los eventos de desplazamiento no se propagan a las Etapas subyacentes. Utilizamos el siguiente enfoque, sin saber si esto puede ayudarlo:

En primer lugar, construimos una clase Singleton que mantiene una referencia de nodo en la que se encuentra actualmente.

Luego, cuando creamos cualquier etapa normal, incluimos los siguientes controladores a la escena de esa nueva etapa. La clave aquí es que, los eventos del mouse todavía pueden pasar a través de la etapa transparente a la ventana subyacente, realizar un seguimiento del nodo que se encuentra debajo del mouse.

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

En la escena de la ventana transparente, incluimos los siguientes controladores para delegar los eventos de desplazamiento al nodo subyacente.

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);
    }
});

Estoy bastante seguro de que esta no es la forma más deseada. Pero esto abordó nuestro problema. :)

A continuación se muestra el código de demostración rápida de lo que quiero decir.

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
fuente
Estábamos pensando en una solución alternativa y esta solución resuelve el problema y es probablemente la opción que elegimos. Gracias por la respuesta detallada.
geh
1

No sé si es correcto o no, pero puede vincular propiedades:

secondStage.getScene().onScrollProperty().bind(mainStage.getScene().onScrollProperty());
dimaMS
fuente
1
No creo que este código esté relacionado con la pregunta en absoluto, está vinculando la propiedad de desplazamiento de la segunda página a la primera página, por lo que se ejecutará el mismo oyente si se desplaza ya sea la primera o la segunda
Ahmed Emad
Como dice @AhmedEmad, este código no está relacionado con la pregunta que hago
geh
0

Puede crear un despachador de eventos personalizado que ignore los eventos que no desea:

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);
        }
    }
}

Luego ponlo en tu escenario:

secondStage.setEventDispatcher(new CustomEventDispatcher());
Steve
fuente
El resultado sigue siendo inesperado, ahora en el caso 2, nadie captura los eventos de desplazamiento, pero mi intención es comportarme igual que los eventos del mouse. Gracias por la propuesta
geh
0

No sé cómo funciona esto en el contexto de las etapas, pero para formas simples hace la diferencia si configuras el color de relleno Color.TRANSPARENTo simplemente null. Usar cualquier Colorevento de captura, mientras nullque no.

mipa
fuente
usando secondPane.getScene().setFill(null)o se new BackgroundFill(null, CornerRadii.EMPTY, Insets.EMPTY)comporta exactamente igual en este caso
geh
0

Puede hacerlo ignorando el evento en la segunda etapa usando el despachador de eventos usando esta respuesta de @Slaw, puede entender todo sobre EventDispatcher
https://stackoverflow.com/a/51015783/5303683.
Luego puede disparar su propio evento usando esta respuesta al DVarga https://stackoverflow.com/a/40042513/5303683 Lo siento, no tengo tiempo para intentar hacer un ejemplo completo de esto

Ahmed Emad
fuente
Lo que pretendo es que el evento no se capture, como es el caso con los eventos del mouse. Capture el evento en secondStage y ejecútelo en mainStage programáticamente según lo propuesto por @Abra si resuelve el ejemplo simplificado que presento, pero no un caso real con más de dos ventanas, ya que debería descubrir qué ventana está justo detrás, y eso es Otro problema en sí mismo.
geh
¿Puedo preguntar cuál es la razón detrás de lo que quieres?
Ahmed Emad
Claro @AhmedEmad. Actualmente tenemos una aplicación con múltiples widgets que puedes colocar en la pantalla, y cada uno de ellos es un escenario. JavaFX no permite modificar el eje Z de las ventanas, por lo que estamos tratando de pintar todos los widgets en una sola etapa transparente, una solución que nos satisface a excepción del problema que explico en esta consulta. También estamos observando que una sola ventana mejora el consumo de memoria y el rendimiento, lo que nos empuja a resolver este problema
geh