s_dolan
s_dolan

Reputation: 1276

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.

Here's my basic situation:

I have a ScreenManager similar to the one outlined in this tutorial. Using the screen manager, I switch between several FXML screens using basic action handling on the screen buttons.

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).

Here's the code where I create the TilePane that I want to draw to the game screen:

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:

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:

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 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. 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!

Upvotes: 1

Views: 2968

Answers (1)

thatsIch
thatsIch

Reputation: 848

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)

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. 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.

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

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. You generally want your Business Logic to be performed in another Thread else your Application-Thread will start lagging when it is processing

Upvotes: 3

Related Questions