Transparente JavaFX Stage Capture-Bildlaufereignisse, wenn sich dahinter ein anderes Fenster befindet


8

Mausereignisse und Bildlaufereignisse verhalten sich unterschiedlich

Diagramm

Mausereignisse:

  1. Das Ereignis wird von mainStage erfasst

  2. Das Ereignis wird von mainStage erfasst

  3. Das Ereignis wird nicht erfasst

Bildlaufereignisse:

  1. Das Ereignis wird von mainStage erfasst

  2. Das Ereignis wird von secondStage erfasst

  3. Das Ereignis wird nicht erfasst

Gibt es eine Möglichkeit, dass transparente secondStage keine Bildlaufereignisse erfasst?

Mein Code:

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

Java-Version: 1.8.0_201 (64 Bit), Windows 10

Bearbeiten: Das Beispiel ist eine Vereinfachung mit nur zwei Fenstern. Wenn Sie das Ereignis programmgesteuert auslösen, müssen Sie feststellen, welche Stufe unmittelbar niedriger ist, und das ist ein weiteres Problem an sich.


Hast du es versucht setMouseTransparent? Ich bin mir jedoch nicht ganz sicher, wie es mit transparenten Fenstern funktionieren würde.
Avi

@Avi Es verhält sich genauso, wenn die transparente Eigenschaft der Maus in den Panels geändert wird
geh

:( Das ist unglücklich. Ich werde wahrscheinlich nicht helfen können, weil ich selbst mit JFoenix 'JFXTextArea herumspiele, um es zum Laufen zu bringen
Avi

@Avi Ich bin sehr überrascht, dass das Ereignis auf andere Fenster übertragen werden kann, aber nicht auf Fenster derselben Anwendung. Es kann ein Fehler sein. Trotzdem danke
geh

Verzeihen Sie meine Unwissenheit, aber wie generieren Sie ein Bildlaufereignis mit dem von Ihnen geposteten Beispielcode?
Abra

Antworten:


1

Es könnte ein großer Zufall sein, dass wir auch die gleiche Lösung für transparente Fenster erhalten haben, da wir nicht über die Funktion verfügen, den Z-Index von Stufen zu verwalten. Und wir sind auf genau das gleiche Problem gestoßen wie Sie. dh Bildlaufereignisse, die sich nicht auf zugrunde liegende Stufen ausbreiten. Wir haben den folgenden Ansatz verwendet und sind uns nicht sicher, ob dies Ihnen helfen kann:

Erstens haben wir eine Singleton-Klasse erstellt, die eine Referenz des Knotens enthält, auf dem sich derzeit befindet.

Wenn wir dann eine normale Bühne erstellen, fügen wir die folgenden Handler in die Szene dieser neuen Bühne ein. Das Wichtigste dabei ist, dass die Mausereignisse immer noch durch die transparente Phase zum zugrunde liegenden Fenster gelangen und den Knoten verfolgen können, der sich unter der Maus befindet.

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

In die Szene des transparenten Fensters haben wir die folgenden Handler eingefügt, um die Bildlaufereignisse an den zugrunde liegenden Knoten zu delegieren.

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

Ich bin mir ziemlich sicher, dass dies nicht der am meisten gewünschte Weg ist. Damit war unser Problem behoben. :) :)

Unten ist der schnelle Demo-Code von dem, was ich meine.

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

Wir haben über eine Problemumgehung nachgedacht und diese Lösung löst das Problem und ist wahrscheinlich die Option, die wir wählen. Vielen Dank für die ausführliche Antwort.
geh

1

Ich weiß nicht, ob das richtig ist oder nicht, aber Sie können Eigenschaften binden:

secondStage.getScene().onScrollProperty().bind(mainStage.getScene().onScrollProperty());

1
Ich glaube nicht, dass dieser Code mit der Frage zusammenhängt, ob Sie die Scroll-Eigenschaft der zweiten Seite an die erste Seite binden, sodass derselbe Listener ausgeführt wird, wenn Sie entweder die erste oder die zweite scrollen
Ahmed Emad

Wie @AhmedEmad sagt, ist dieser Code nicht mit der Frage verbunden, die ich stelle
geh

0

Sie können einen benutzerdefinierten Ereignis-Dispatcher erstellen, der nicht gewünschte Ereignisse ignoriert:

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

Dann stellen Sie das auf Ihre Bühne:

secondStage.setEventDispatcher(new CustomEventDispatcher());

Das Ergebnis ist immer noch unerwartet. In Fall 2 erfasst niemand die Bildlaufereignisse, aber ich beabsichtige, mich genauso zu verhalten wie die Mausereignisse. Danke für den Vorschlag
geh

0

Ich weiß nicht, wie dies im Kontext von Stufen funktioniert, aber bei einfachen Formen macht es einen Unterschied, ob Sie die Füllfarbe auf Color.TRANSPARENToder nur einstellen null. Die Verwendung von ColorCatch-Ereignissen ist nullnicht der Fall.


Verwenden secondPane.getScene().setFill(null)oder new BackgroundFill(null, CornerRadii.EMPTY, Insets.EMPTY)verhält sich in diesem Fall genau gleich
geh

0

Sie können dies tun, indem Sie das Ereignis in der zweiten Phase mithilfe des Ereignis-Dispatchers ignorieren. Verwenden Sie dazu diese Antwort von @Slaw. Sie können alles über EventDispatcher
https://stackoverflow.com/a/51015783/5303683 verstehen.
Anschließend können Sie Ihr eigenes Ereignis mit dieser Antwort von auslösen DVarga https://stackoverflow.com/a/40042513/5303683 Entschuldigung, ich habe keine Zeit, ein vollständiges Beispiel dafür zu erstellen


Ich beabsichtige, dass das Ereignis nicht erfasst wird, wie dies bei Mausereignissen der Fall ist. Erfassen Sie das Ereignis in secondStage und starten Sie es programmgesteuert in mainStage, wie von @Abra vorgeschlagen, wenn es das vereinfachte Beispiel löst, das ich präsentiere, aber kein realer Fall mit mehr als zwei Fenstern, da ich herausfinden sollte, welches Fenster sich direkt dahinter befindet, und das ist ein weiteres Problem an sich.
geh

Kann ich fragen, was der Grund für das ist, was Sie wollen?
Ahmed Emad

Sicher @AhmedEmad. Derzeit haben wir eine Anwendung mit mehreren Widgets, die Sie auf dem Bildschirm positionieren können, und jedes davon ist eine Stufe. In JavaFX ist es nicht möglich, die Z-Achse der Fenster zu ändern. Daher versuchen wir, alle Widgets auf einer einzigen transparenten Stufe zu malen. Diese Lösung erfüllt uns mit Ausnahme des Problems, das ich in dieser Abfrage erläutere. Wir beobachten auch, dass ein einzelnes Fenster den Speicherverbrauch und die Leistung verbessert, was uns dazu drängt, dieses Problem zu lösen
geh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.