Các sự kiện cuộn trong giai đoạn JavaFX trong suốt nếu có một cửa sổ khác phía sau


8

Các sự kiện chuột và các sự kiện cuộn hoạt động theo những cách khác nhau

biểu đồ

Sự kiện chuột:

  1. Sự kiện này được ghi lại bởi mainStage

  2. Sự kiện này được ghi lại bởi mainStage

  3. Sự kiện không được nắm bắt

Sự kiện cuộn:

  1. Sự kiện này được ghi lại bởi mainStage

  2. Sự kiện này được chụp bởi secondStage

  3. Sự kiện không được nắm bắt

Có cách nào mà secondStage trong suốt không chụp các sự kiện cuộn không?

Mã của tôi:

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

Phiên bản Java: 1.8.0_201 (64 bit), Windows 10

chỉnh sửa: Ví dụ là một đơn giản hóa chỉ với hai cửa sổ. Bắn sự kiện theo chương trình ngụ ý khám phá giai đoạn nào ngay lập tức thấp hơn và đó là một vấn đề khác.


Bạn đã thử setMouseTransparentchưa Không hoàn toàn chắc chắn làm thế nào nó sẽ làm việc với các cửa sổ trong suốt.
Avi

@Avi Nó hoạt động giống hệt nhau khi thay đổi thuộc tính trong suốt của chuột trong các bảng
geh

:( Điều đó thật đáng tiếc. Có lẽ tôi sẽ không thể giúp được vì bản thân tôi đang loay hoay với JFXTextArea của JFoenix để khiến nó hoạt động
Avi

@Avi Tôi rất ngạc nhiên rằng sự kiện này có thể được truyền đến các cửa sổ khác nhưng không phải cho các cửa sổ của cùng một ứng dụng, nó có thể là một lỗi. Cảm ơn bạn anyway
GEH

Xin tha thứ cho sự thiếu hiểu biết của tôi, nhưng làm thế nào để bạn tạo ra một sự kiện cuộn bằng mã mẫu mà bạn đã đăng?
Abra

Câu trả lời:


1

Có thể là một sự trùng hợp tuyệt vời, chúng tôi cũng đã đưa ra cùng một giải pháp cửa sổ trong suốt vì không có tính năng quản lý chỉ số z của các giai đoạn. Và chúng tôi đã gặp vấn đề chính xác như của bạn. tức là, các sự kiện cuộn không truyền đến các Giai đoạn cơ bản. Chúng tôi đã sử dụng cách tiếp cận dưới đây, không chắc liệu điều này có thể giúp bạn:

Đầu tiên, chúng tôi đã xây dựng một lớp Singleton để giữ một tham chiếu về Node hiện đang được di chuột vào.

Sau đó, khi chúng tôi tạo bất kỳ giai đoạn bình thường nào, chúng tôi bao gồm các trình xử lý bên dưới cho cảnh của giai đoạn mới đó. Điều quan trọng ở đây là, các sự kiện chuột vẫn có thể đi qua giai đoạn trong suốt đến cửa sổ bên dưới, theo dõi nút nằm dưới chuột.

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

Trong cảnh của cửa sổ trong suốt, chúng tôi đã bao gồm các trình xử lý bên dưới để ủy quyền các sự kiện cuộn cho nút bên dưới.

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

Tôi khá chắc chắn rằng đây không phải là cách mong muốn nhất. Nhưng điều này giải quyết vấn đề của chúng tôi. :)

Dưới đây là mã demo nhanh của những gì tôi muốn nói.

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

Chúng tôi đã nghĩ đến một cách giải quyết và giải pháp này giải quyết vấn đề và có lẽ là lựa chọn chúng tôi chọn. Cảm ơn bạn đã trả lời chi tiết.
GEH

1

Tôi không biết điều đó đúng hay không, nhưng bạn có thể ràng buộc các thuộc tính:

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

1
Tôi không nghĩ mã này có liên quan đến câu hỏi của bạn, tất cả các bạn đang ràng buộc thuộc tính cuộn của trang thứ hai với trang đầu tiên để nó chạy cùng một trình nghe nếu bạn cuộn đầu tiên hoặc thứ hai
Ahmed Emad

Như @AhmedEmad nói, mã này không liên quan đến câu hỏi tôi hỏi
geh

0

Bạn có thể tạo một bộ điều phối sự kiện tùy chỉnh sẽ bỏ qua các sự kiện bạn không muốn:

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

Sau đó đặt nó trên sân khấu của bạn:

secondStage.setEventDispatcher(new CustomEventDispatcher());

Kết quả vẫn bất ngờ, bây giờ trong trường hợp 2, không ai nắm bắt được các sự kiện cuộn, nhưng ý định của tôi là hành xử giống như các sự kiện chuột. Cảm ơn lời đề nghị
geh

0

Tôi không biết làm thế nào điều này hoạt động trong bối cảnh của các giai đoạn nhưng đối với các hình dạng đơn giản, nó tạo ra sự khác biệt cho dù bạn đặt màu tô thành Color.TRANSPARENThay chỉ null. Sử dụng bất kỳ Colorsự kiện bắt, trong khi nullkhông.


sử dụng secondPane.getScene().setFill(null)hoặc new BackgroundFill(null, CornerRadii.EMPTY, Insets.EMPTY)hành xử giống hệt nhau trong trường hợp này
geh

0

Bạn có thể làm như vậy bằng cách bỏ qua sự kiện ở giai đoạn thứ hai bằng cách sử dụng bộ điều phối sự kiện bằng câu trả lời này bởi @Slaw bạn có thể hiểu mọi thứ về EventDispatcher
https://stackoverflow.com/a/51015783/5303683
Sau đó, bạn có thể kích hoạt sự kiện của mình bằng câu trả lời này bằng cách trả lời DVarga https://stackoverflow.com/a/40042513/5303683 Xin lỗi tôi không có thời gian để thử và làm một ví dụ đầy đủ về nó


Những gì tôi dự định là sự kiện này không được ghi lại, như trường hợp với các sự kiện chuột. Nắm bắt sự kiện trong secondStage và khởi chạy nó trong mainStage theo chương trình như @Abra đề xuất nếu nó giải quyết ví dụ đơn giản mà tôi trình bày, nhưng không phải là trường hợp thực tế có nhiều hơn hai cửa sổ, vì tôi nên khám phá cửa sổ nào ở phía sau và đó là một vấn đề khác trong chính nó.
GEH

Tôi có thể hỏi lý do đằng sau những gì bạn muốn là gì?
Ahmed Emad

Chắc chắn @AHmedEmad. Hiện tại chúng tôi có một ứng dụng với nhiều widget mà bạn có thể định vị trên màn hình và mỗi ứng dụng là một giai đoạn. JavaFX không cho phép sửa đổi trục Z của các cửa sổ, vì vậy chúng tôi đang cố gắng vẽ tất cả các tiện ích trên một giai đoạn trong suốt duy nhất, một giải pháp thỏa mãn chúng tôi ngoại trừ vấn đề tôi giải thích trong truy vấn này. Chúng tôi cũng đang quan sát rằng một cửa sổ duy nhất cải thiện hiệu suất và mức tiêu thụ bộ nhớ, điều này thúc đẩy chúng tôi giải quyết vấn đề này
geh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.