简体   繁体   English

如何在Java FX FXML应用程序中的Model和View之间进行通信?

[英]How to communicate between the Model and View in a Java FX FXML application?

Disclaimer: I'm very new to JavaFX and Model-View-Controller design principles. 免责声明:我对JavaFX和模型 - 视图 - 控制器设计原则非常陌生。

Here's my basic situation: 这是我的基本情况:

I have a ScreenManager similar to the one outlined in this tutorial. 我有一个类似于教程中概述的ScreenManager。 Using the screen manager, I switch between several FXML screens using basic action handling on the screen buttons. 使用屏幕管理器,我使用屏幕按钮上的基本操作处理在几个FXML屏幕之间切换。

The problem is that one of these is a game screen with a map. 问题是其中一个是带有地图的游戏屏幕。 I'll be drawing a tile-based map onto this game screen, so I went with a TilePane in my FXML file(if there's a better way to draw tiles to an area of a screen, please let me know). 我将在这个游戏画面上绘制一个基于图块的地图,所以我在我的FXML文件中使用了TilePane(如果有更好的方法将图块绘制到屏幕区域,请告诉我)。

Here's the code where I create the TilePane that I want to draw to the game screen: 这是我创建要绘制到游戏屏幕的TilePane的代码:

public TilePane createRandomMap(boolean centeredRiver)
{

    Image atlas = new Image("resources/textures/tile_atlas.jpg");
    Random rand = new Random();
    int riverPos = 4, 
        tileHeight = (int)atlas.getHeight()/2, 
        tileWidth = (int)atlas.getWidth()/3;
    if(!centeredRiver)
        riverPos = rand.nextInt(10);
    for(int i = 0; i < 5; i++)
    {
        for(int j = 0; j < riverPos; j++)
        {
            int type = rand.nextInt(4);
            Tile tile = new Tile(tileTypes[type], i, j);
            tile.setImage(atlas);
            tile.setViewport(new Rectangle2D((type%3)*tileWidth, //minX
                    tileHeight-(type/3)*tileHeight, //minY
                    tileWidth, tileHeight)); // width, height
            tile.setFitHeight(tilePane.getHeight()/5); //fit to the size of the pane
            tile.setFitWidth(tilePane.getWidth()/9); //fit to the size of the pane
            tile.setPreserveRatio(true);
            tilesToAdd.add(tile); 
        }
        if(i == 2)
        {
            Tile tile = new Tile(tileTypes[5], i, riverPos);
            tile.setImage(atlas);
            tile.setViewport(new Rectangle2D(2*tileWidth, 0, 
                    tileWidth, tileHeight));
            tile.setFitHeight(tilePane.getHeight()/5);
            tile.setFitWidth(tilePane.getWidth()/9);
            tile.setPreserveRatio(true);
            tilesToAdd.add(tile);
        }
        else
        {
            Tile tile = new Tile(tileTypes[4], i, riverPos);
            tile.setImage(atlas);
            tile.setViewport(new Rectangle2D(tileWidth, 0, 
                    tileWidth, tileHeight));
            tile.setFitHeight(tilePane.getHeight()/5);
            tile.setFitWidth(tilePane.getWidth()/9);
            tile.setPreserveRatio(true);
            tilesToAdd.add(tile);
        }
        for(int j = riverPos + 1; j<9; j++)
        {
            int type = rand.nextInt(4);
            Tile tile = new Tile(tileTypes[type], i, j);
            tile.setImage(atlas);
            tile.setViewport(new Rectangle2D((type%3)*tileWidth, //minX
                    tileHeight-(type/3)*tileHeight, //minY
                    tileWidth, tileHeight)); // width, height
            tile.setFitHeight(tilePane.getHeight()/5); //fit to the size of the pane
            tile.setFitWidth(tilePane.getWidth()/9); //fit to the size of the pane
            tile.setPreserveRatio(true);
            tilesToAdd.add(tile); 
        }
    }
    tilePane.getChildren().addAll(tilesToAdd);

    return tilePane;
}

I've been able to access this TilePane in my controller class: 我已经能够在我的控制器类中访问此TilePane:

public class GameScreenController implements Initializable, ControlledScreen {

ScreenManager screenManager;

@FXML
TilePane mapPane

/**
 * Initializes the controller class.
 */
@Override
public void initialize(URL url, ResourceBundle rb) {
    TileEngine engine = new TileEngine();
    mapPane = engine.createRandomMap(true);
}    

In the above instance, I'm setting the TilePane defined in my FXML screen to the TilePane I created in my model, but I get the following error: 在上面的例子中,我将在我的FXML屏幕中定义的TilePane设置为我在模型中创建的TilePane,但是我收到以下错误:

Can not set javafx.scene.layout.TilePane field
screens.gameScreen.GameScreenController.mapPane to 
javafx.scene.layout.AnchorPane

Here's the segment of my FXML file dealing with the TilePane: 这是我处理TilePane的FXML文件的一部分:

<TilePane id="MapPane" fx:id="mapPane" layoutX="0.0" layoutY="0.0" prefColumns="9" prefHeight="560.0" prefTileHeight="112.0" prefTileWidth="112.0" prefWidth="1108.0" visible="true" />

I'm really struggling to wrap my head around JavaFX and game design in general, but very much want to learn. 我真的很难围绕JavaFX和游戏设计,但非常想学习。 I'll be happy to clarify and to take criticism on any part of how this is structured in order to make it better, even things that don't pertain to the question I'm asking. 我很乐意澄清并批评这个结构的任何部分,以使其更好,即使是与我所问的问题无关的事情。

Thank you in advance! 先感谢您!

MVC MVC

Strictly speaking JavaFX does not support MVC (which is dead anyways declared by the inventor himself) but some kind of MVVM (Model-View-ViewModel) 严格来说,JavaFX不支持MVC(无论发明者自己都宣称死了),但某种MVVM(Model-View-ViewModel)

Communication between Model and Controller 模型与控制器之间的通信

I generally use Dependency Injection to do so. 我通常使用依赖注入来执行此操作。 If you want a simple FrameWork I can suggest you afterburner.fx or Google Guice. 如果你想要一个简单的FrameWork,我可以建议你使用afterburner.fx或Google Guice。 Both pretty light weight frameworks which do what you want. 两个非常轻量级的框架,你做你想要的。 Basically they can handle the instantiation of the classes for you and assure if wanted that only one instance of your model is active. 基本上,他们可以为您处理类的实例化,并确保是否只需要模型的一个实例是活动的。

Some Sample Code: 一些示例代码:

public class Presenter {
    // Injects
    @Inject private StateModel stateModel;
}

No need for declaring Constructor or anything. 无需声明构造函数或任何东西。 You need a bit more work to test it if you want so, but overall I don't want to miss this feature again, because generally you just want one StateModel where all your information is derived from and all the views just listen to them or change them. 如果你需要,你需要更多的工作来测试它,但总的来说我不想再次错过这个功能,因为通常你只需要一个StateModel ,其中所有的信息都来自它,所有的视图只是听它们或者改变它们。

Lets give a further example to extend to posted code 让我们举一个例子来扩展到已发布的代码

@Singleton
public class StateModel {
    // Properties
    private final StringProperty name = new SimpleStringProperty();

    // Property Getter
    public StringProperty nameProperty() { return this.name; }
}

If we have a label now declared in our Presenter/Controller, we can listen to the changes in our model like this 如果我们现在在Presenter / Controller中声明了一个标签,我们可以像这样听取模型中的变化

public class Presenter implements Initializable {
    // Nodes
    @FXML private Label nodeLabelName;

    // Injects
    @Inject private StateModel stateModel;

    @Override
    public void initialize(URL location, ResourceBundle resource) {
        this.nodeLabelName.textProperty().bind(this.stateModel.nameProperty());
    }
}

Another suggestion I can give you is Threading . 我可以给你的另一个建议是Threading You generally want your Business Logic to be performed in another Thread else your Application-Thread will start lagging when it is processing 您通常希望您的业务逻辑在另一个线程中执行,否则您的Application-Thread将在处理时开始滞后

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM