Overriden method initialize does not recognize variable value change when called - java

I'm working on a MVC project and have the following issue:
In a view called CentralLayout is the following code:
EventView e = new EventView();
gridSchedule.add(e.createView(5), 1, 1, 1, 5);
where createView method is defined in the following interface:
public interface Creatable {
public Node createView();
public Node createView(int eventDuration);
}
EventView is implemented as:
public class EventView extends BaseViewController implements Initializable, Creatable {
private int PANE_HEIGHT = 10;
final int PANE_WIDTH = 99;
#FXML
private Label lblObjectOne;
#FXML
private Label lblObjectTwo;
#FXML
private Pane eventPane;
#Override
public Node createView() {
return null;
}
#Override
public Node createView(int eventDuration) {
PANE_HEIGHT = eventDuration * 20;
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("eventView.fxml"));
Parent root = null;
try {
root = fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
return root;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(PANE_HEIGHT);
eventPane.setMaxHeight(PANE_HEIGHT);
eventPane.setMaxWidth(PANE_WIDTH);
lblObjectOne.setText("test");
lblObjectTwo.setText("test");
}
}
NOTE: I've created eventPane in SceneBuilder and its properties are defined in eventView.fxml file.
The problem is that the final, drawn, height of eventPane is equals to 10, and not 100. In console value 10 is printed as a PANE_HEIGHT value.
Can anyone tell me, why PANE_HEIGHT haven't changed when initialize is called, when I first called the the createView method on object e and passed the value that multiplied PANE_HEIGHT variable? Thanks.

You create a new controller when you use the 'FXMLLoader'. To use the one already created you have to use the setController method of the FXMLLoader before loading the fxml file to use a controller instance you created yourself:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("eventView.fxml"));
Parent root = null;
fxmlLoader.setController(this);
try {
root = fxmlLoader.load();

You are probably not re-using the same EventView instance (e) when you call initialize(). Only the EventView instance you used to call createView() will have the expected value for PANE_HEIGHT.

Related

How to access a variable from one class controller file to another in JavaFX?

So I have an integer number called "size" saved to a controller class called SettingsStageController.java and I want that variable to be accessed through my other controller class file called GameStageController.java but I can't seem to find out how.
SettingsStageController.java
/* has the int size variable stored in this file */
int size = 5;
public void startGame(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("gameStage.fxml"));
root = loader.load();
stage = (Stage) ((Node) event.getSource()).getScene().getWindow(); // ti ston poutso
scene = new Scene(root);
stage.setTitle("DnB: " + Integer.toString(size) + "x" + Integer.toString(size) + " Game");
stage.setScene(scene);
stage.show();
GameStageController gameStageController = loader.getController();
gameStageController.showPane();
}
GameStageController.java
public class GameStageController implements Initializable {
#FXML
Text testText;
#FXML
AnchorPane twoXtwoPane;
#FXML
AnchorPane threeXthreePane;
#FXML
AnchorPane fourXfourPane;
#FXML
AnchorPane fiveXfivePane;
public void showPane() {
switch (/* I WANT TO PUT THE "SIZE" NUMBER HERE" */) {
case 2:
twoXtwoPane.setDisable(false);
twoXtwoPane.setVisible(true);
break;
case 3:
threeXthreePane.setDisable(false);
threeXthreePane.setVisible(true);
break;
case 4:
fourXfourPane.setDisable(false);
fourXfourPane.setVisible(true);
break;
case 5:
fiveXfivePane.setDisable(false);
fiveXfivePane.setVisible(true);
break;
default:
twoXtwoPane.setDisable(false);
twoXtwoPane.setVisible(true);
break;
}
}
}
If a method needs data to perform its functionality, then that data should be a parameter to the method. You should do:
public class GameStageController implements Initializable {
// ...
public void showPane(int size) {
switch (size) {
// ...
}
}
}
and then of course
private int size = 5;
public void startGame(ActionEvent event) throws IOException {
// ...
GameStageController gameStageController = loader.getController();
gameStageController.showPane(size);
}
If your GameStageController instance needs the size variable later on, you can create an instance variable in that class, and set it in the showPane method to the value passed as the parameter.
You just need to make it static so you can access it using the class name directly and if your controllers are in different packages you need to add public because by default the visibility is package.
So you have to declare size like this :
public static int size = 5;
To access it you do :
SettingsStageController.size

JavaFX: Get data from modal loaded at runtime

I have a modal class:
public class DialogModal
{
private String fxmlURL;
private int width;
private int height;
public DialogModal( String url, int w, int h )
{
fxmlURL = url;
width = w;
height = h;
}
public void showDialogModal(Button root) throws IOException
{
Stage modalDialog = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource( fxmlURL ));
Parent modalDialogRoot = loader.load();
Scene modalScene = new Scene( modalDialogRoot, width, height );
modalScene.getStylesheets().add(InventoryManager.class.getResource("InventoryManager.css").toExternalForm());
modalDialog.initOwner(root.getScene().getWindow());
modalDialog.setScene(modalScene);
modalDialog.setResizable(false);
modalDialog.showAndWait();
}
}
which is then opened thusly (from an FXML controller):
#FXML
private void handleModalButton(ActionEvent e) throws IOException
{
DialogModal modal = new DialogModal("Modal.fxml", 400, 450);
modal.showDialogModal((Button)e.getSource());
}
My question is, how do I get data from the modal (i.e., TextFields) back to my handleModalButton method? This modal can be given different FXML files, so the data that it returns may be different.
Additionally, how do (or should) I send data to the modal (e.g., to populate TextFields)?
Thanks!
You can make DialogModal.showDialogModal() return the controller of the spawned modal dialog window.
public <T> T showDialogModal(Button root) throws IOException
{
Stage modalDialog = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource( fxmlURL ));
Parent modalDialogRoot = loader.load();
T controller = loader.getController(); // Retrieve the controller
Scene modalScene = new Scene( modalDialogRoot, width, height );
modalScene.getStylesheets().add(InventoryManager.class.getResource("InventoryManager.css").toExternalForm());
modalDialog.initOwner(root.getScene().getWindow());
modalDialog.setScene(modalScene);
modalDialog.setResizable(false);
// You need Platform.runLater() so that this method doesn't get blocked
Platform.runLater(() -> modalDialog.showAndWait());
return controller; // Return the controller back to caller
}
Then in your calling method:
#FXML
private void handleModalButton(ActionEvent e) throws IOException
{
DialogModal modal = new DialogModal("Modal.fxml", 400, 450);
FooController controller = modal.showDialogModal((Button)e.getSource());
String data1 = controller.getTextField1Data();
// ...
}
You need to know exactly the class of the controller in handleModalButton(), otherwise you are going to get a ClassCastException. Of course, you need to have public getters in the controller that exposes the necessary values. You can keep things like nodes and setters private though.
If you have multiple methods similar to handleModalButton(), and for all of them, you need get a similar set of values, then you can consider creating an interface, which all your controller classes can implement. The interface will include getter methods that you can get the data from. Then showDialogModal() can return the interface type, and the calling method can get the references of the controller objects via the interface type.

All my #FXML references are null

I'm trying to display playing cards in a FlowPane. I have a main layout and a nested layout. For some reason when I debug IntelliJ reports that all fields, on both controllers, annotated with #FXML are null.
Here's a shortened version of what I've got thus far. Full Code on GitHub:
MainWindow.fxml
<BorderPane fx:controller="controller.MainWindowController">
<center>
<fx:include fx:id="tableScene" source="TableScene.fxml"/>
</center>
</BorderPane>
MainWindowController.java
public class MainWindowController implements Initializable {
#FXML
MenuBar menuBar;
#FXML
Menu fileMenu;
[...] more fields
#Override
public void initialize(URL location, ResourceBundle resources) {
// nothing here in my code
}
}
TableScene.fxml
<AnchorPane fx:controller="controller.TableSceneController">
<children>
<FlowPane fx:id="dealerHandFlowPane"></FlowPane>
<FlowPane fx:id="playerHandFlowPane"></FlowPane>
</children>
</AnchorPane>
TableSceneController
public class TableSceneController implements Initializable {
#FXML
private FlowPane dealerHandFlowPane;
#FXML
private FlowPane playerHandFlowPane;
public void displayInitialHand(Player player) {
var cards = new ArrayList<>(player.getHand().getCards());
for (BlackjackCard card : cards) {
if(player.getName().equals("Dealer")) {
dealerHandFlowPane.getChildren().add(new ImageView(getCardFace(card)));
} else {
playerHandFlowPane.getChildren().add(new ImageView(getCardFace(card)));
}
}
}
public void displayHand(Player player) {
var cards = new ArrayList<>(player.getHand().getCards());
}
public Image getCardFace(BlackjackCard card) {
return new Image("/images/cards/" + card.getRank().getLetter()
+ card.getSuit().getLetter() + ".png");
}
public Image getCardBack() {
String color[] = {"blue","red"};
String design = "123";
return new Image("/images/backs/" + color[0] + design.charAt(2));
}
#Override
public void initialize(URL location, ResourceBundle resources) {
// nothing here in my code either
}
}
BlackjackMain
public class BlackjackMain extends Application {
private final String MAIN_WINDOW_PATH = "/fxml/MainWindow.fxml";
private final String ICON_PATH = "/images/blackjack_icon.png";
private final String MAIN_STYLE_PATH = "/css/MainWindow.css";
private final String TABLE_STYLE_PATH = "/css/TableScene.css";
private final Image MAIN_ICON = new Image(getClass().getResourceAsStream(ICON_PATH));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Blackjack");
// close the app gracefully when the 'X' is clicked
primaryStage.setOnCloseRequest(e -> Platform.exit());
primaryStage.centerOnScreen();
primaryStage.setResizable(false);
initializeMainWindow(primaryStage);
primaryStage.getIcons().add(MAIN_ICON);
primaryStage.show();
primaryStage.toFront();
initializeGame();
}
public void initializeMainWindow(Stage primaryStage) {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource(MAIN_WINDOW_PATH));
try {
Parent mainWindow = loader.load();
Scene scene = new Scene(mainWindow,600,600);
scene.getStylesheets().add(TABLE_STYLE_PATH);
primaryStage.setScene(scene);
} catch (IOException e) {
System.err.println("There was a problem loading /fxml/MainWindow.fxml");
e.printStackTrace();
}
}
public void initializeGame() {
var tableSceneController = new TableSceneController();
var mainWindowController = new MainWindowController();
Dealer dealer = new Dealer();
List<Player> allPlayers = new ArrayList<>();
var playerName = tableSceneController.getPlayerName();
allPlayers.add(new BlackjackPlayer(playerName));
BlackjackGame game = new BlackjackGame(dealer, allPlayers,
mainWindowController, tableSceneController);
game.playGame();
}
}
BlackjackGame.java
public class BlackjackGame implements BlackjackGameRules {
private List<Player> playerList;
private Deck deck;
private Shoe shoe;
private final TableSceneController tableSceneController;
private final MainWindowController mainWindowController;
public BlackjackGame(Dealer dealer, List<Player> players,
final MainWindowController mainWindowController,
final TableSceneController tableSceneController) {
Objects.requireNonNull(dealer,
"You must provide a dealer to begin the game.");
Objects.requireNonNull(players,
"You must provide a list of players to begin the game.");
playerList = new ArrayList<>();
this.tableSceneController = tableSceneController;
this.mainWindowController = mainWindowController;
// add dealer first for easier future access
playerList.add(dealer);
playerList.addAll(players);
deck = new Deck(BlackjackGameRules.NUMBER_OF_DECKS);
// place the shuffled deck in the shoe
shoe = new Shoe(deck.getDeck());
}
public void dealInitialCards() {
for (Player player : playerList) {
player.getHand().addCard(shoe.dealCard());
player.getHand().addCard(shoe.dealCard());
}
}
public boolean hasValidNumberOfPlayers() {
// this number includes the dealer
var numPlayers = playerList.size();
return numPlayers >= BlackjackGameRules.MIN_PLAYERS &&
numPlayers <= BlackjackGameRules.MAX_PLAYERS;
}
public List<Player> getPlayers() {
return new ArrayList<>(playerList);
}
public Shoe getShoe() {
return shoe;
}
public void playGame() {
dealInitialCards();
for(Player player: playerList) {
tableSceneController.displayInitialHand(player);
}
}
}
I get a NullPointerException on displayIntitialHand in TableSceneController. Here's the brief stacktrace:
Caused by: java.lang.NullPointerException
at blackjack.controller.TableSceneController.displayInitialHand(TableSceneController.java:35)
at blackjack.model.BlackjackGame.playGame(BlackjackGame.java:139)
at blackjack.controller.BlackjackMain.initializeGame(BlackjackMain.java:70)
at blackjack.controller.BlackjackMain.start(BlackjackMain.java:44)
For the life of me I cannot figure this one out. Where have I gone wrong? I have double checked that I've set the names of the controllers in the fx:controller attribues in the *.fxml files. I have also double checked that I have the fx:id attributes correct in the components and that they also match the #FXML annotations in the controller correctly.
My understanding of the process of JavaFX is:
that load() is supposed to load the *.fxml file
instantiate the controller (specified by the fx:controller attribute in the .fxml file)
Calls the no-arg constructor on the controller
Sets the #FXML values (by injection)
Registers any event handlers
Calls initialize on each controller
Is the problem with my nested fxml files? If this was the case I would think that the #FXML fields in MainWindowController.java would not also be null. I'm s truggling to figure this out. I could use another set of eyes and someone smarter than myself.
Thanks in advance.
Took a while to figure out, but when you create the controllers for your scenes, within initializeGame() you do:
var tableSceneController = new TableSceneController();
var mainWindowController = new MainWindowController();
What this means is you are creating a new instance of the controller, not the instance that is created when you load your FXML files within initializeMainWindow.
To remedy this, I'd suggest creating a class variable to hold each of your controllers, and then assign them when you load the FXML files.
So, in BlackJackMain.java, declare class variables
private TableSceneController tableSceneController;
private MainWindowController mainWindowController;
then when you load them, I can see you load the main window in initializeMainWindow, so add
mainWindowController = loader.getController();
to the try block, just after the loader.load line.
This resolves your null pointers for this scene, but I cannot figure out where or if you load the table scene FXML, and thus you don't have an instance of the controller to pass into your method. If you do load the file, apply the same logic to it to get an instance of that controller too.

JavaFX label won't update

I am trying to learn Java and JavaFX, I followed this tutorial and everything worked out fine.
Now I wante to make things a little more complicated and started a password book app.
The main fxml file contains a split pane and one of the two halves is a ListView, the other half is loaded dynamically, so I can change the labels to textFields to edit records in the window.
The problem is that when I click a listView item it loads the .fxml fine but doesn't update the labels. If I add a listener and log the updates it records as if it was changed, but it's not. This is the method to change the label.
public void showLoginDetails(String username)
{
usernameLabel.setText(username);
}
Which is called by this method in the parent controller
private void showLoginDetails(Login login)
{
mainApp.showLayout("LoginOverview.fxml");
mainApp.getLoginOverviewController().showLoginDetails(login.getName());;
}
I get no errors but the result is this:
I am sure that the name of the label is set correctly in SceneBuilder.
What code do you need to see be able to help me?
Edit:
MainApp getLoginOverViewController()
public LoginOverviewController getLoginOverviewController()
{
if(loginOverviewController == null)
{
this.loginOverviewController = (LoginOverviewController) this.getController("LoginOverview.fxml");
this.loginOverviewController.setMainApp(this);
}
return this.loginOverviewController;
}
MainApp.getController()
private Object getController(String layout)
{
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/" + layout));
loader.load();
return loader.getController();
} catch (IOException e)
{
e.printStackTrace();
return null;
}
}
The fxmlLoader
public void showLayout(String layout)
{
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/" + layout));
AnchorPane content = (AnchorPane) loader.load();
listContainerController.setContent(content);
} catch (IOException e)
{
e.printStackTrace();
}
}
listContainerController.setContent()
public void setContent(AnchorPane layout)
{
content.getChildren().clear();
content.getChildren().add(layout);
}
Here is where the labels are declared
#FXML
private Label usernameLabel;
The label variable is not accessed in the initialize method.
Edit2
As pointed out by James_D I have to store the controller from the ShowLayout method.
public void showLayout(String layout)
{
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/" + layout));
AnchorPane content = (AnchorPane) loader.load();
listContainerController.setContent(content);
this.activeController = loader.getController();
} catch (IOException e)
{
e.printStackTrace();
}
}
The variable.
private Object activeController;
the getter.
public Object getActiveController()
{
return activeController;
}
Is the code as it stands fine? It works but I'd like to learn how to write acceptable code.
This is how I updated the code

JavaFX8 TableView with custom control

I want to use a custom control (ClientControl in the code) in my TableView. Therefore I created a class ClientCell:
public class NewClientCell extends TableCell<Client, Client> {
private final ClientControl cc;
public NewClientCell(ObservableList<Client> suggestions) {
cc = new ClientControl(this.getItem(), suggestions);
this.setAlignment(Pos.CENTER_LEFT);
this.setGraphic(cc);
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Client c, boolean empty) {
super.updateItem(c, empty);
if(!empty){
setGraphic(cc);
}
}
}
In the main program I use the following code to fill the table:
TableColumn<Client, Client> clmClients = new TableColumn<>("Klient");
clmClients.setCellFactory(new Callback<TableColumn<Client, Client>, TableCell<Client, Client>>() {
#Override
public TableCell<Client, Client> call(TableColumn<Client, Client> p) {
return new NewClientCell(suggestions);
};
});
clmClients.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Client, Client>, ObservableValue<Client>>() {
#Override
public ObservableValue<Client> call(TableColumn.CellDataFeatures<Client, Client> p) {
return new SimpleObjectProperty<Client>(p.getValue());
}
});
getColumns().add(clmClients);
The data in the table comes from an ObservableList and is initialized correct.
My problem now is that the custom control needs an Client-Object which it should get out of the ObservableList, but "this.getItem()" always returns null.
How do I get the Client objects correctly into the custom control?
Thanks!
EDIT
Here's the constructor of ClientControl:
public ClientControl(Client client, ObservableList<Client> suggestions) {
setClient(client);
setSuggestions(suggestions);
FXMLLoader loader = new FXMLLoader(getClass().getResource("ClientControl.fxml"));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
initTextField();
setLabelText(client.toString());
}
The method setClient is a simple setter method (this.client = client;). The variables client and suggestions are this simple defined:
private ObservableList<Client> suggestions;
private Client client;
AFAIK, you should instantiate any controls in the constructor as you did, so that they are only created once (remember that cells get reused for different locations).
But then you need to override one or more of the other methods such as updateItem to get the data from the current item to render.
EDIT
Well, you're assigning the same control without changing it over and over again. Rather than setting the graphics in the updateItem method, set the item property of the client control:
#Override
protected void updateItem(Client c, boolean empty) {
super.updateItem(c, empty);
if(!empty){
cc.setClient(c);
} else {
cc.setClient(null);
}
}
Edit 2
The ClientControl should provide the client item as a property instead of a constructor argument and set it in the updateItem method, not in the constructor.
E.g. something like this (untested!):
private final ObjectProperty<Client> client = new SimpleObjectProperty<>(this, "client");
public final Client getClient(){
return clientProperty().get();
}
public final void setClient(Client client){
clientProperty().set(client);
}
public ObjectProperty<Client> clientProperty(){
return client;
}
And in the constructor: listen for changes of this property to set the labelText etc.
You also might want to provide a constructor without a client argument, as it is not available when you instantiate it in the TableCell constructor.
So I found the solution for my problem. Thank you so much Puce for your help! :-)
Now I set Client via the property like that:
private ObjectProperty<Client> clientProperty = new SimpleObjectProperty<Client>();
Additionally I added a ChangeListener in the constructor of ClientControl:
public ClientControl(ObservableList<Client> suggestions) {
clientProperty.addListener(new ChangeListener<Client>() {
#Override
public void changed(ObservableValue<? extends Client> observable, Client oldValue,
ClientnewValue) {
if(newValue != null) {
setLabelText(newValue.toString());
}
}
});
setSuggestions(suggestions);
FXMLLoader loader = new FXMLLoader(getClass().getResource("ClientControl.fxml"));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
initTextField();
}
My ClientCell class needed only some simple changes because of the changes in ClientControl:
public class NewClientCell extends TableCell<Client, Client> {
private final ClientControl cc;
public NewClientCell(ObservableList<Client> suggestions) {
cc = new ClientControl(suggestions);
this.setAlignment(Pos.CENTER_LEFT);
this.setGraphic(cc);
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Client c, boolean empty) {
super.updateItem(c, empty);
if(!empty){
cc.setClient(c);
}
}
}
In the main program nothing changed.
In conclusion I would like to thank Puce one more time, I stuck at this problem for many days...

Categories