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.
Related
I was hoping to make a SceneManager class to provide limited control over a single active scene with methods like setActiveScene(Scenes newScene) where Scenes is an enum, updateScene(double time) which wraps the update method of an active Scene, and getActiveScene() to retrieve certain components of the active scene. The hope was that only the SceneManager could create new Scenes, and that adding a new possible scene would be as simple as adding to the Scenes enum with which class to instantiate. What I had was something like this:
public class SceneManager {
private static Scene activeScene = null;
private static void setActiveScene(Scenes newScene) {
activeScene = newScene.load();
}
public enum Scenes {
START(new StartScene()), MENU(new MenuScene());
private final Scene scene;
Scenes(Scene scene) {
this.scene = scene;
}
protected Scene load() { return scene; }
}
}
It sorta worked except that I need the Scene to only be initialized when it's set. This setup initializes all the scenes right away and that won't work. I was hoping to find a cleaner solution than a switch statement but I'm not sure how else to make it work now. I tried messing with Constructors but that was getting even messier than the switch and probably far less efficient too.
Two options:
Don't initialise the scenes in their constructors, but with an init method:
interface Scene {
void init();
...
}
...
private static void setActiveScene(Scenes newScene) {
activeScene = newScene.load();
activeScene.init();
}
Don't create the scenes when the enum is initialised, only when you load it:
public enum Scenes {
START(StartScene.class), MENU(MenuScene.class);
private final Class<Scene> sceneClass;
...
protected Scene load() {
try {
return sceneClass.getDeclaredConstructor().newInstance();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
I want the title of my notes-program to change whenever my list (notes) changes. To achieve this I wanted to bind an IntegerProperty to the size of my list, but it says:
The method bind(ObservableValue<? extends Number>) in the type Property is not applicable for the arguments (int)
Does this mean I should cast the size from int to a Number (tried it but there was another problem) or is there an even easier solution?
public class Notes extends Stage {
ObservableList<String> notes = FXCollections.observableArrayList();
public Notes() {
this.setup();
}
private void setup() {
IntegerProperty size = new SimpleIntegerProperty();
size.bind(this.notes.size());
this.setTitle(String.format("Notes (%d)", size.getValue()));
final Scene scene = new Scene(this.createRootPane());
this.setScene(scene);
}
}
Just bind your title property to the list size’s asString binding:
titleProperty().bind(Bindings.size(notes).asString("Notes (%d)"));
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.
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.
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