背後に別のウィンドウがある場合、透過的なJavaFXステージがスクロールイベントをキャプチャします


8

マウスイベントとスクロールイベントの動作は異なります

図

マウスイベント:

  1. イベントはmainStageによってキャプチャされます

  2. イベントはmainStageによってキャプチャされます

  3. イベントはキャプチャされません

スクロールイベント:

  1. イベントはmainStageによってキャプチャされます

  2. イベントは、secondStageによってキャプチャされます。

  3. イベントはキャプチャされません

透過的なsecondStageがスクロールイベントをキャプチャしない方法はありますか?

私のコード:

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バージョン:1.8.0_201(64ビット)、Windows 10

編集:この例は、2つのウィンドウのみの簡略化です。イベントをプログラムで発生させると、どのステージがすぐ下にあるかを発見することになりますが、それはそれ自体が別の問題です。


試しましたsetMouseTransparentか?しかし、透明なウィンドウでどのように機能するかは完全にはわかりません。
Avi

@Aviパネルでマウスの透明プロパティを変更した場合とまったく同じように動作します
ゲー

:(それは残念です。私がJFoenixのJFXTextAreaをいじって動作させるため、私はおそらく手伝うことができないでしょう
Avi

@Aviイベントが他のウィンドウに伝播できるが、同じアプリケーションのウィンドウには伝播できないことに非常に驚いています。バグの可能性があります。とにかくありがとう
ge

無知ですが、投稿したサンプルコードを使用してスクロールイベントを生成するにはどうすればよいですか。
Abra、

回答:


1

ステージのz-indexを管理する機能がないため、透明なウィンドウの同じソリューションが付属したことは、非常に偶然かもしれません。そして、あなたとまったく同じ問題が発生しました。つまり、基になるステージに伝播しないスクロールイベント。以下のアプローチを使用しましたが、これがあなたに役立つかどうかはわかりません:

まず、現在ホバーされているノードの参照を保持するシングルトンクラスを作成しました。

次に、通常のステージを作成するときに、その新しいステージのシーンに以下のハンドラーを含めます。ここで重要なのは、マウスイベントが透明なステージを通過して下にあるウィンドウに到達し、マウスの下にあるノードを追跡できることです。

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

透明なウィンドウのシーンには、以下のハンドラーを含めて、スクロールイベントを基になるノードに委任しました。

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

私はこれが最も望ましい方法ではないと確信しています。しかし、これは私たちの問題に対処しました。:)

以下は、私が意味することの簡単なデモコードです。

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

私たちは回避策を考えていましたが、このソリューションは問題を解決し、おそらく私たちが選択するオプションです。詳しい回答ありがとうございます。
ゲー

1

それが正しいかどうかはわかりませんが、プロパティをバインドできます。

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

1
私はこのコードが2番目のページのスクロールプロパティを最初のページにバインドしている質問にまったく関連していないと思うので、最初または2番目のいずれかをスクロールした場合に同じリスナーを実行します
Ahmed Emad

@AhmedEmadが言うように、このコードは私が尋ねる質問とは関係がありません
geh

0

不要なイベントを無視するカスタムイベントディスパッチャーを作成できます。

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

次に、それをステージに設定します。

secondStage.setEventDispatcher(new CustomEventDispatcher());

結果はまだ予想外ですが、今の場合、ケース2では誰もスクロールイベントをキャプチャしませんが、私の意図はマウスイベントと同じように動作することです。提案をありがとう
ge

0

ステージのコンテキストでこれがどのように機能するかはわかりませんが、単純な形状の場合、塗りつぶしの色をに設定するColor.TRANSPARENTか、単にに設定するだけで違いが生じますnullColorキャッチイベントを使用すると、キャッチされnullません。


この場合の使用secondPane.getScene().setFill(null)またはnew BackgroundFill(null, CornerRadii.EMPTY, Insets.EMPTY)動作はまったく同じ
geh

0

@Slawによるこの回答を使用してイベントディスパッチャーを使用して、第2ステージのイベントを無視することでこれを行うことができますEventDispatcher
。https: //stackoverflow.com/a/51015783/5303683に関するすべてを理解できます。
次に、 DVarga https://stackoverflow.com/a/40042513/5303683 申し訳ありませんが、完全な例を作成する時間はありません


私が意図しているのは、マウスイベントの場合のように、イベントがキャプチャされないことです。@Abraが提案するように、secondStageでイベントをキャプチャし、mainStageでプログラム的に起動します。これにより、提示する簡略化された例が解決されますが、2つを超えるウィンドウがある実際のケースではありません。それ自体別の問題。
GEH

あなたが欲しいものの背後にある理由は何ですか?
アーメドEmad

確かに@AhmedEmad。現在、画面上に配置できる複数のウィジェットを備えたアプリケーションがあり、それぞれがステージです。JavaFXではウィンドウのZ軸を変更できないため、このクエリで説明する問題を除いて、すべてのウィジェットを単一の透明なステージにペイントしようとしています。我々はまた、単一のウィンドウには、この問題を解決するために私たちをプッシュするメモリ消費とパフォーマンス、改善することが観察されている
GEH
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.