JavaFX: จะรับสเตจจากคอนโทรลเลอร์ระหว่างการเริ่มต้นได้อย่างไร?


94

ฉันต้องการจัดการกับเหตุการณ์บนเวที (เช่นการซ่อน) จากคลาสคอนโทรลเลอร์ของฉัน ดังนั้นสิ่งที่ฉันต้องทำคือเพิ่มผู้ฟังผ่านทาง

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

แต่ปัญหาคือการเริ่มต้นจะเริ่มขึ้นทันที

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

และก่อนหน้านี้

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

ดังนั้น. getScene () จะคืนค่า null

วิธีแก้ปัญหาเดียวที่ฉันพบด้วยตัวเองคือการเพิ่ม Listener ใน myPane.sceneProperty () และเมื่อมันไม่เป็นโมฆะฉันจะได้ฉากให้เพิ่มเป็น. windowProperty () my! goddamn! การจัดการผู้ฟังซึ่งในที่สุดฉันก็เรียกเวทีได้ และทุกอย่างจบลงด้วยการตั้งค่าผู้ฟังที่ต้องการให้เข้าร่วมกิจกรรมบนเวที ผมคิดว่ามีผู้ฟังมากเกินไป เป็นวิธีเดียวที่จะแก้ปัญหาของฉันได้หรือไม่?

คำตอบ:


121

คุณสามารถรับอินสแตนซ์ของคอนโทรลเลอร์ได้จากFXMLLoaderafter initialization ผ่านgetController()แต่คุณต้องสร้างอินสแตนซ์FXMLLoaderแทนการใช้วิธีการแบบคงที่

ฉันจะผ่านด่านหลังจากโทรload()ไปยังคอนโทรลเลอร์โดยตรงในภายหลัง:

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
คิดว่าคุณขาดนักแสดงหรือไม่? รูทพาเรนต์ = (พาเรนต์) loader.load ();
Paul Eden

12
นี่เป็นวิธีที่ดีที่สุดจริงๆหรือ? ไม่มีสิ่งใดที่เฟรมเวิร์ก JavaFX จัดเตรียมไว้เพื่อเก็บถาวร
Hannes

1
ด้วยเหตุผลบางประการสิ่งนี้ใช้ไม่ได้หากคุณใช้ตัวสร้างโดยไม่มีพารามิเตอร์และส่ง URL ไปยังload()เมธอด (นอกจากนี้ javadoc ยังgetControllerฟังดูเหมือนจะเปิดอยู่setController)
Bombe

4
@Bombe นี่เป็นเพราะเมธอด load () ที่มีพารามิเตอร์ URL เป็นเมธอดแบบคงที่ที่ไม่ได้ตั้งค่าอะไรบนอินสแตนซ์ที่คุณเรียกใช้
zhujik

controller.setStageAndSetupListeners(stage);ไม่มีวิธีการดังกล่าว ฉันควรทำอย่างไรดี?
Martin Erlic

113

สิ่งที่คุณต้องมีคือให้AnchorPaneID จากนั้นคุณจะได้รับStageจากสิ่งนั้น

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

จากตรงนี้คุณสามารถเพิ่มสิ่งListenerที่คุณต้องการได้

แก้ไข: ตามที่ระบุโดย EarthMind ด้านล่างไม่จำเป็นต้องเป็นAnchorPaneองค์ประกอบ อาจเป็นองค์ประกอบใดก็ได้ที่คุณกำหนดไว้


7
โปรดทราบว่าelementสามารถเป็นองค์ประกอบใด ๆ ที่มีในหน้าต่างที่:fx:id Stage stage = (Stage) element.getScene().getWindow();ตัวอย่างเช่นถ้าคุณมีเพียงButtonที่มีfx:idในหน้าต่างของคุณใช้ว่าจะได้รับเวที
EarthMind

29
getScene()ยังคงกลับnullมา
m0skit0

12
ก่อนที่การเตรียมใช้งานจะเสร็จสมบูรณ์ (เช่นในวิธีการเริ่มต้นตัวควบคุม) สิ่งนี้จะไม่ทำงานเนื่องจาก getScene () ส่งคืนค่า null โปสเตอร์ต้นฉบับบอกเป็นนัยว่านี่คือสถานการณ์ของเขา ในกรณีนี้ทางเลือก 1 ที่ให้โดย Utku Özdemirด้านล่างดีกว่า หากคุณไม่ต้องการเวทีแสดงว่ามีผู้ฟังเพียงคนเดียวฉันใช้สิ่งนี้ในการเริ่มต้น () เพื่อขยาย ImageView:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Georgie

มันช่วยแก้ปัญหาของฉันได้และฉันเชื่อว่านี่เป็นวิธีที่ดีที่สุด ฉันคิดว่าควรสังเกตว่า 'ap' คือ ID ของ AnchorPane ที่กำหนดในฟิลด์ fx: id ในไฟล์. fxml ของคุณ (หากคุณกำลังทำงานกับการออกแบบ UI กับ FXML) หรือชื่อที่คุณกำหนดให้กับวัตถุ AnchorPane โดยทั่วไปจะทำงานได้ดีสำหรับวัตถุ XXXPane ทั้งหมด
Aaron Chen

32

ฉันรู้ว่ามันไม่ใช่คำตอบที่คุณต้องการ แต่ IMO วิธีแก้ปัญหาที่เสนอนั้นไม่ดี (และวิธีของคุณเองก็คือ) ทำไม? เนื่องจากขึ้นอยู่กับสถานะแอปพลิเคชัน ใน JavaFX ตัวควบคุมฉากและสเตจไม่ขึ้นอยู่กับกันและกัน ซึ่งหมายความว่าการควบคุมสามารถดำรงอยู่ได้โดยไม่ต้องถูกเพิ่มเข้าไปในฉากและฉากสามารถดำรงอยู่ได้โดยไม่ต้องยึดติดกับพื้นที่ จากนั้นในเวลา t1 ทันทีการควบคุมสามารถติดเข้ากับฉากและในทันที t2 ฉากนั้นสามารถเพิ่มลงในพื้นที่งานได้ (และนั่นอธิบายว่าเหตุใดจึงเป็นคุณสมบัติที่สังเกตได้ของกันและกัน)

ดังนั้นวิธีการที่แนะนำให้รับการอ้างอิงคอนโทรลเลอร์และเรียกใช้เมธอดการส่งผ่านขั้นตอนไปจะเพิ่มสถานะให้กับแอปพลิเคชัน ซึ่งหมายความว่าคุณต้องเรียกใช้เมธอดนั้นในเวลาที่เหมาะสมหลังจากสร้างขั้นตอนแล้ว กล่าวอีกนัยหนึ่งคุณต้องทำตามคำสั่งทันที: 1- สร้างขั้นตอนที่ 2- ส่งผ่านขั้นตอนที่สร้างขึ้นนี้ไปยังคอนโทรลเลอร์โดยใช้วิธีการ

คุณไม่สามารถ (หรือไม่ควร) เปลี่ยนลำดับนี้ในแนวทางนี้ คุณจึงสูญเสียคนไร้สัญชาติ และในซอฟต์แวร์โดยทั่วไปสถานะเป็นสิ่งชั่วร้าย ตามหลักการแล้ววิธีการไม่ควรต้องมีคำสั่งโทรใด ๆ

แล้วทางออกที่ถูกต้องคืออะไร? มีสองทางเลือก:

1- แนวทางของคุณในคุณสมบัติการฟังของคอนโทรลเลอร์เพื่อรับเวที ฉันคิดว่านี่เป็นแนวทางที่ถูกต้อง แบบนี้:

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- คุณทำในสิ่งที่คุณต้องทำเมื่อคุณสร้างStage(และนั่นไม่ใช่สิ่งที่คุณต้องการ):

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

ฉันกำลังพยายามใช้วิธีนี้ในการเติมข้อมูล TableView และแสดง ProgressIndicator เป็นศูนย์กลางของ TableView แต่ปรากฎว่าภายในค่าเหตุการณ์สำหรับ getX () และ getY () ไม่เหมือนกับที่คุณใช้หลังจากการเริ่มต้นทั้งหมด สิ้นสุดกระบวนการ (ภายในเหตุการณ์คลิกในปุ่ม) ทั้งสำหรับฉากและสเตจแม้กระทั่งสเตจ getX () และ getY () ส่งคืน NaN ความคิดใด ๆ
leobelizquierdo

@leobelizquierdo มีปัญหา getY () ในขณะนี้คุณพบวิธีแก้ปัญหาหรือไม่?
JonasAnon

ถ้าฉันเพิ่มpaneฉากที่ติดกับเวทีแล้วมันจะใช้ไม่ได้ใช่ไหม? ไม่ควรมีการตรวจสอบscenePropertyผู้ฟังและเพิ่มเฉพาะstagePropertyผู้ฟังหากเวทียังคงเป็นโมฆะ? ไม่งั้นก็ทำในสิ่งที่ต้องทำทันที?
พรุ่งนี้

14

วิธีที่ง่ายที่สุดในการรับวัตถุสเตจในคอนโทรลเลอร์คือ:

  1. เพิ่มเมธอดพิเศษในคลาสคอนโทรลเลอร์ที่สร้างขึ้นเองเช่น (จะเป็นเมธอด setter เพื่อตั้งค่าสเตจในคลาสคอนโทรลเลอร์)

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
    
  2. รับตัวควบคุมในวิธีเริ่มต้นและตั้งค่าขั้นตอน

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
    
  3. ตอนนี้คุณสามารถเข้าถึงเวทีในคอนโทรลเลอร์ได้แล้ว


นี่คือผู้ชนะสำหรับฉัน คำตอบของโรเบิร์ตมาร์ตินได้ผลในตอนแรก แต่จากนั้นก็เริ่มมีข้อผิดพลาดซึ่งฉันแก้ไขได้ด้วยการทำstageเช่นนี้
Dammeul

1

กำหนดfx: idหรือประกาศตัวแปรให้กับ / ของโหนดใด ๆ : anchorpane ปุ่ม ฯลฯ จากนั้นเพิ่มตัวจัดการเหตุการณ์เข้าไปและภายในตัวจัดการเหตุการณ์นั้นให้ใส่โค้ดที่ระบุด้านล่าง:

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

หวังว่าจะได้ผลสำหรับคุณ !!


1

Platform.runLater ทำงานเพื่อป้องกันการดำเนินการจนกว่าการเตรียมใช้งานจะเสร็จสมบูรณ์ ในกรณีนี้ฉันต้องการรีเฟรชมุมมองรายการทุกครั้งที่ปรับขนาดความกว้างของหน้าต่าง

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

ในกรณีของคุณ

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

-2

คุณจะได้รับnode.getSceneหากคุณไม่โทรจากPlatform.runLaterผลลัพธ์จะเป็นค่าว่าง

ตัวอย่างค่า null:

node.getScene();

ตัวอย่างไม่มีค่า null:

Platform.runLater(() -> {
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
               //your event
     });
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.