JavaFX: Làm thế nào để lấy giai đoạn từ bộ điều khiển trong quá trình khởi tạo?


91

Tôi muốn xử lý các sự kiện giai đoạn (tức là ẩn) khỏi lớp bộ điều khiển của tôi. Vì vậy, tất cả những gì tôi phải làm là thêm người nghe qua

((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);

nhưng vấn đề là quá trình khởi tạo bắt đầu ngay sau

Parent root = FXMLLoader.load(getClass().getResource("MyGui.fxml"));

va trươc đây

Scene scene = new Scene(root);
stage.setScene(scene);

do đó .getScene () trả về null.

Cách giải quyết duy nhất mà tôi tự tìm ra là thêm một trình nghe vào myPane.sceneProperty () và khi nó không trở thành null, tôi nhận được cảnh, hãy thêm nó vào .windowProperty () my! Goddamn! xử lý người nghe mà tôi cuối cùng truy xuất giai đoạn. Và tất cả kết thúc bằng việc đưa người nghe mong muốn vào các sự kiện trên sân khấu. Tôi nghĩ rằng có quá nhiều người nghe. Nó có phải là cách duy nhất để giải quyết vấn đề của tôi?

Câu trả lời:


119

Bạn có thể lấy thể hiện của bộ điều khiển từ FXMLLoadersau khi khởi tạo thông qua getController(), nhưng bạn cần phải khởi tạo một FXMLLoaderthay vì sử dụng các phương thức tĩnh sau đó.

Tôi sẽ vượt qua giai đoạn sau khi gọi load()trực tiếp cho bộ điều khiển sau đó:

FXMLLoader loader = new FXMLLoader(getClass().getResource("MyGui.fxml"));
Parent root = (Parent)loader.load();
MyController controller = (MyController)loader.getController();
controller.setStageAndSetupListeners(stage); // or what you want to do

1
Bạn nghĩ rằng bạn đang thiếu một diễn viên? Parent root = (Parent) loader.load ();
Paul Eden

12
Đây thực sự là cách tốt nhất để làm điều này? Không có gì được cung cấp bởi khung công tác JavaFX để lưu trữ cái này?
Hannes

1
Vì một số lý do, điều này không hoạt động nếu bạn sử dụng hàm tạo không có tham số và nhập URL cho load()phương thức. (Ngoài ra, javadoc trên getControllercó vẻ như nó nên được bật setController.)
Bombe

4
@Bombe điều này là do phương thức load () với tham số URL là một phương thức tĩnh không thiết lập bất kỳ thứ gì trên phiên bản mà bạn đang gọi nó.
zhujik

@Sebastian, ouch, thực sự. Lỗi của tôi.
Bombe

112

Tất cả những gì bạn cần là cung cấp AnchorPanemột ID, và sau đó bạn có thể nhận được Stagetừ đó.

@FXML private AnchorPane ap;
Stage stage = (Stage) ap.getScene().getWindow();

Từ đây, bạn có thể thêm những Listenerthứ bạn cần.

Chỉnh sửa: Như đã nêu bởi EarthMind bên dưới, nó không phải là AnchorPanephần tử; nó có thể là bất kỳ phần tử nào bạn đã xác định.


2
Ngắn và ngọt. Cảm ơn
Sedrick

7
Lưu ý rằng elementcó thể là bất kỳ phần tử với một fx:idtrong cửa sổ: Stage stage = (Stage) element.getScene().getWindow();. Ví dụ: nếu bạn chỉ có một Buttonvới một fx:idtrong cửa sổ của mình, hãy sử dụng nó để có được sân khấu.
EarthMind

29
getScene()vẫn trở lại null.
m0skit0

3
Đây là câu trả lời, gọn gàng!
destan

12
Trước khi quá trình khởi tạo hoàn tất (ví dụ: trong phương thức khởi tạo bộ điều khiển), điều này sẽ không hoạt động vì getScene () trả về null. Áp phích ban đầu ngụ ý đây là tình huống của anh ta. Trong trường hợp này, phương án 1 do Utku Özdemir đưa ra dưới đây là tốt hơn. Nếu bạn không cần giai đoạn sau đó một người biết lắng nghe là đủ, tôi sử dụng này trong initialize () để thực hiện một căng ImageView:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Georgie

32

Tôi biết đó không phải là câu trả lời bạn muốn, nhưng IMO các giải pháp được đề xuất không tốt (và theo cách của riêng bạn). Tại sao? Bởi vì chúng phụ thuộc vào trạng thái ứng dụng. Trong JavaFX, một điều khiển, một cảnh và một giai đoạn không phụ thuộc vào nhau. Điều này có nghĩa là một điều khiển có thể tồn tại mà không cần thêm vào một cảnh và một cảnh có thể tồn tại mà không cần gắn vào một màn. Và sau đó, tại thời điểm tức thời t1, điều khiển có thể được gắn vào một cảnh và tại thời điểm t2, cảnh đó có thể được thêm vào một màn (và điều đó giải thích tại sao chúng là các thuộc tính có thể quan sát được của nhau).

Vì vậy, cách tiếp cận đề xuất nhận tham chiếu bộ điều khiển và gọi một phương thức, chuyển giai đoạn cho nó sẽ thêm một trạng thái vào ứng dụng của bạn. Điều này có nghĩa là bạn cần gọi phương thức đó vào đúng thời điểm, ngay sau khi giai đoạn được tạo. Nói cách khác, bạn cần làm theo thứ tự ngay bây giờ: 1- Tạo giai đoạn 2- Chuyển giai đoạn đã tạo này cho bộ điều khiển thông qua một phương thức.

Bạn không thể (hoặc không nên) thay đổi thứ tự này trong cách tiếp cận này. Vì vậy, bạn đã mất vô quốc tịch. Và trong phần mềm, nói chung, trạng thái là xấu. Tốt nhất, các phương thức không nên yêu cầu bất kỳ lệnh gọi nào.

Vậy đâu là giải pháp phù hợp? Có hai lựa chọn thay thế:

1- Cách tiếp cận của bạn, trong thuộc tính lắng nghe bộ điều khiển để có được giai đoạn. Tôi nghĩ đây là cách tiếp cận đúng đắn. Như thế này:

pane.sceneProperty().addListener((observableScene, oldScene, newScene) -> {
    if (oldScene == null && newScene != null) {
        // scene is set for the first time. Now its the time to listen stage changes.
        newScene.windowProperty().addListener((observableWindow, oldWindow, newWindow) -> {
            if (oldWindow == null && newWindow != null) {
                // stage is set. now is the right time to do whatever we need to the stage in the controller.
                ((Stage) newWindow).maximizedProperty().addListener((a, b, c) -> {
                    if (c) {
                        System.out.println("I am maximized!");
                    }
                });
            }
        });
    }
});

2- Bạn làm những gì bạn cần làm ở nơi bạn tạo Stage(và đó không phải là những gì bạn muốn):

Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> {
            if (c) {
                System.out.println("I am maximized!");
            }
        });
stage.setScene(someScene);
...

1
Đây là tham chiếu JavaFX ngắn nhất trên hành tinh !! Cảm ơn, Utku!
Aram Paronikyan

Cách nhìn thú vị về nó. Cảm ơn :)
Utku Özdemir

Tôi đang cố gắng sử dụng cách tiếp cận này để điền TableView và hiển thị ProgressIndicator tập trung vào TableView nhưng hóa ra rằng bên trong các giá trị sự kiện cho getX () và getY () không giống như thể bạn sử dụng sau khi khởi tạo kết thúc quá trình (bên trong sự kiện nhấp chuột trong một nút) hoặc cho cảnh và sân khấu, thậm chí cả giai đoạn getX () và getY () trả về NaN, bạn có ý kiến ​​gì không?
leobelizquierdo

@leobelizquierdo, có vấn đề getY () vào lúc này, bạn có thấy lo lắng không?
JonasAnon

nếu tôi thêm panevào một cảnh đã được gắn vào một sân khấu, điều này sẽ không hoạt động, phải không? Không nên kiểm tra trình scenePropertynghe và chỉ thêm stagePropertyngười nghe nếu giai đoạn vẫn trống? Nếu không, chỉ cần làm những gì tôi cần phải làm ngay lập tức?
ngày mai

14

Cách đơn giản nhất để lấy đối tượng sân khấu trong bộ điều khiển là:

  1. Thêm một phương thức bổ sung trong lớp bộ điều khiển được tạo riêng như (nó sẽ là một phương thức setter để thiết lập giai đoạn trong lớp bộ điều khiển),

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
    
  2. Nhận bộ điều khiển trong phương pháp bắt đầu và thiết lập giai đoạn

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
    
  3. Bây giờ bạn có thể truy cập giai đoạn trong bộ điều khiển


Đây là người chiến thắng cho tôi. Câu trả lời của Robert Martin lúc đầu có hiệu quả, nhưng sau đó bắt đầu gặp lỗi mà tôi đã giải quyết bằng cách nhận được stagenhư thế này.
Dammeul 21/1218

1

Gán fx: id hoặc khai báo biến cho / của bất kỳ nút nào: anchorrpane, button, v.v. Sau đó, thêm trình xử lý sự kiện vào nó và bên trong trình xử lý sự kiện đó, hãy chèn mã đã cho bên dưới:

Stage stage = (Stage)((Node)((EventObject) eventVariable).getSource()).getScene().getWindow();

Hy vọng, điều này làm việc cho bạn !!


1

Platform.runLater hoạt động để ngăn chặn việc thực thi cho đến khi quá trình khởi tạo hoàn tất. Trong trường hợp này, tôi muốn làm mới chế độ xem danh sách mỗi khi tôi thay đổi kích thước chiều rộng cửa sổ.

Platform.runLater(() -> {
    ((Stage) listView.getScene().getWindow()).widthProperty().addListener((obs, oldVal, newVal) -> {
        listView.refresh();
    });
});

trong trường hợp của bạn

Platform.runLater(()->{
    ((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);
});

-3

Bạn có thể nhận được với node.getScene, nếu bạn không gọi từ Platform.runLater, kết quả là giá trị null.

ví dụ giá trị null:

node.getScene();

ví dụ không có giá trị null:

Platform.runLater(() -> {
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
               //your event
     });
});
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.