I am creating an application where I'm combining FXML and regular javaFX to create an application. I'm, however, experiencing problems writing EventHandlers for a Stage-subclass called AddItemWindow that generates custom windows.
The application shows a list of items (keys and weapons) in an inventory. The user can add items, and needs to press a button to add the item of his choice (Add Key or Add Weapon).
A new window then pops up, where the user has to input the relevant data. It will generate a GridPane with the TextFields where the user can input the data. This will be a custom GridPane, depending on the ItemType. It will then load the FXML and add the GridPane.
With the below code, I am getting LoadExceptions for my SetOnAction-code for the buttons cancelling the window or confirming the new item.
Would any-one know where I'm making an error?
/* StartUp Class*/
package main;
//imports from javafx and java
import domain.DomainController;
import gui.OverviewWindow;
public class StartUpGUI extends Application {
#Override
public void start(Stage primaryStage) {
Parent root = new OverviewWindow(new DomainController());
Scene scene = new Scene(root);
primaryStage.setTitle("Weapons and Keys");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String... args) {
Application.launch(StartUpGUI.class, args);
}
}
/* OverviewWindow, subclass of BorderPane */
package gui;
//imports from javafx and java
import domain.DomainController;
import domain.ItemType;
public class OverviewWindow extends BorderPane{
#FXML
private Button btnAddWeapon;
#FXML
private Button btnAddKey;
#FXML
private TextArea txaOverview;
private DomainController dc;
this.dc = dc;
FXMLLoader loader = new FXMLLoader(getClass().getResource("OverviewWindow.fxml"));
loader.setRoot(this);
loader.setController(this);
try{
loader.load();
txaOverview.setText(dc.showOverview()); // showOverview returns a String containing a toString of all items in the inventory
}
catch (IOException ex){
throw new RuntimeException(ex);
}
}
#FXML
public void btnAddWeaponOnAction(ActionEvent event){
try{
add(ItemType.WEAPON); // ItemType is an Enum where all the properties of the items are defined; for Weapon: name, weight, level, power, used(boolean)
}
catch (Exception e){
throw e;
}
}
#FXML
public void btnAddKeyOnAction(ActionEvent event){
try{
add(ItemType.SLEUTEL); // ItemType is an Enum where all the properties of the items are defined; for Key: name, weight, level, door
}
catch (Exception e){
throw e;
}
}
private void add(ItemType itemType){
Stage stage = new Stage();
stage.setTitle(itemType== VoorwerpSoort.WEAPON ? "Add Weapon" : "Add Key");
AddItem addItem = new AddItem(dc,itemType,this);
addItem.setOnHiding(new EventHandler<WindowEvent>(){
#Override
public void handle(WindowEvent e){
txaOverview.setText(dc.showOverview()); // when we close the AddItemWindow, we will update the item overview by having the domain controller get this data from the repository
}
});
addItem.show();
}
}
/* AddItemWindow, a subclass of Stage*/
package gui;
// import relevant javafx and java classes
import domain.DomainController;
import domain.ItemType;
public class AddItemWindow extends Stage {
#FXML
private BorderPane addRoot;
#FXML
private Button btnOK;
#FXML
private Button btnCancel;
private DomainController dc;
private ItemType itemType;
private Parent parent;
private TextField[] txfAttributes;
public AddItemWindow(DomainController dc, ItemType itemType, OverviewWindow overviewWindow){
this.dc = dc;
this.itemType = itemType;
this.parent = overviewWindow;
this.setScene(buildGUI(dc,itemType,overviewWindow));
}
private Scene buildGUI(DomeinController dc, VoorwerpSoort vwps, OverzichtSchermController ovsController){
Parent root = new BorderPane();
GridPane properties = new GridPane();
properties.setPadding(new Insets(10));
properties.setHgap(10);
properties.setVgap(10);
ColumnConstraints col1 = new ColumnConstraints();
col1.setHalignment(HPos.RIGHT);
ColumnConstraints col2 = new ColumnConstraints();
properties.getColumnConstraints().addAll(col1, col2);
String[] attributes = itemType.attributeNames();
txfAttributes = new TextField[attributes.length];
for(int i = 0; i<attributes.length; i++){
properties.add(new Label(attributes[i]),0,i);
properties.add(txfAttributes[i] = new TextField(),1,i);
}
((BorderPane) root).setCenter(properties);
FXMLLoader loader = new FXMLLoader(getClass().getResource("AddItemWindow.fxml"));
loader.setRoot(root);
loader.setController(root);
try{
loader.load();
return new Scene(root);
}
catch(IOException e){
throw new RuntimeException(e);
}
}
// NOT WORKING
#FXML
public void btnOKOnAction(){
addItem();
}
// NOT WORKING
#FXML
public void btnCancelOnAction(ActionEvent event){
hide();
}
private void voorwerpToevoegen(){ // we're calling the domaincontroller to add the new item to the repository
switch (itemType)
{
// for the item, add an item by getting the value of each TextField, which are the
// parameters for a constructor of the new item
case WEAPON:
dc.addWeapon(txfAttributes[0].getText(),
Double.parseDouble(txfAttributes[1].getText()),
Integer.parseInt(txfAttributes[2].getText()),
Integer.parseInt(txfAttributes[3].getText()),
Boolean.parseBoolean(txfAttributes[4].getText()));
break;
case KEY:
dc.addKey(txfAttributes[0].getText(),
Double.parseDouble(txfAttributes[1].getText()),
Integer.parseInt(txfAttributes[2].getText()),
Integer.parseInt(txfAttributes[3].getText()));
break;
}
hide();
}
}
At the top you have #FXML private Button btnOK; which is good.
To specify the button's action in Java code, you can use this Java 8 syntax in your buildGUI() method (see JavaFX 8 Event Handling Examples):
btnOK.setOnAction((event) -> addItem());
If you're not using Java 8, see UI Control Sample.
You don't need the methods annotated with #FXML.
Related
Currently I have a main controller which controls adding elements to a Border Pane's left pane for navigation and center pane for content. I am also using fx:root to load new FXML layouts, each with a separate controller into the center pane of the Border Pane. Trying to call the setCenter method of the MainController from a center pane controller my center pane is not updated. I'm guessing the newly created controller is not being associated with the mainPane in the main Controller. How do I associate them?
Main Ctonroller
This loads both the center and left panes with the appropriate views and associated controllers using either button.
public class MainController {
#FXML
BorderPane mainPane;
#FXML
Button orderBtn, adminBtn;
#FXML
static
Label statusTxt;
public void initialize(){
orderBtn.setOnAction(event -> {
setCenter(new ControllerConnector("/view/OrderView.fxml"));
setNav(new ControllerConnector("/view/OrderNav.fxml"));
});
adminBtn.setOnAction(event -> {
setCenter(new ControllerConnector("/view/ProductView.fxml"));
setNav(new ControllerConnector("/view/AdminNav.fxml"));
});
public static void updateStatus(String string) {
statusTxt.setText(string);
}
public void setCenter(ControllerConnector connector){
this.mainPane.setCenter(connector);
}
public void setNav(ControllerConnector connector){
this.mainPane.setLeft(connector);
}
}
Product Controller
The search function works in this controller which is loaded from the associated view so I know the controller was initialized. When the newBtn is clicked nothing happens.
public class ProductController {
#FXML
TableView<Product> productTable;
#FXML
TableColumn prodCol, sizeCol, categoryCol, priceCol;
#FXML
TextField searchTxt;
#FXML
Button searchBtn, newBtn, editBtn, deleteBtn;
public void initialize() throws SQLException {
ObservableList<Product> products = ProductDAO.getProducts();
prodCol.setCellValueFactory(new PropertyValueFactory<Product, String>("name"));
sizeCol.setCellValueFactory(new PropertyValueFactory<Product, String>("size"));
priceCol.setCellValueFactory(new PropertyValueFactory<Product, Double>("price"));
categoryCol.setCellValueFactory(new PropertyValueFactory<Product, String>("category"));
productTable.setItems(products);
searchBtn.setOnAction(event -> {
ObservableList<Product> searchProducts = FXCollections.observableArrayList();
String searchString = searchTxt.getText();
for (Product product : products) {
if(product.getName().contains(searchString) || product.getSize().contains(searchString)){
searchProducts.add(product);
}
}
products.removeAll(products);
products.addAll(searchProducts);
});
searchTxt.setOnMouseClicked(event -> {
products.removeAll(products);
try {
products.addAll(ProductDAO.getProducts());
} catch (SQLException exception) {
exception.printStackTrace();
}
});
newBtn.setOnAction(event -> {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/MainView.fxml"));
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
MainController mainController = loader.getController();
mainController.setCenter(new ControllerConnector("/view/NewProductView.fxml"));
});
}
}
ControllerConnector
Using this class to create a Node to pass into the Border Pane set mehtods.
public class ControllerConnector extends GridPane {
public ControllerConnector(String fxmlPath){
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlPath));
fxmlLoader.setRoot(this);
try{
fxmlLoader.load();
}catch (IOException e){
e.printStackTrace();
}
}
}
I'm making an application with JavaFX and Scene Builder.
I have two controllers:Controller and FontController
I have Main class that launch my program and open Stage with first fontroller (Controller)
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("/card/card.fxml"));
Scene scene = new Scene(root, 1600, 600);
primaryStage.setScene(scene);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setMaximized(true);
primaryStage.setResizable(true);
primaryStage.getIcons().add(new Image("card/resources/logo-icon.png"));
primaryStage.show();
//adding resize and drag primary stage
ResizeHelper.addResizeListener(primaryStage);
//assign ALT+ENTER to maximize window
final KeyCombination kb = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.CONTROL_DOWN);
scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (kb.match(event)) {
primaryStage.setMaximized(!primaryStage.isMaximized());
primaryStage.setResizable(true);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
There is a label and a button in Controller. When I click on the button a method is called and new window with second controller appears(FontController):
#FXML private Button btnFont;
#FXML private Label category1
#FXML
void changeFont(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new
FXMLLoader(getClass().getResource("font.fxml"));
Parent rootFont = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("Select Font");
stage.setScene(new Scene(rootFont));
stage.show();
} catch (Exception e) {
System.out.println("can't load new window");
}
}
There is the button "OK" and label in FontCOntroller:
#FXML private Label fontLabel;
#FXML private Button btnFontOk;
Please tell me, what should I do to send and apply text from label in FontController when I click on the burtton "OK" to label in Controller?
SOLUTION FOUND:
I created class "Context" in my project directory to make all controllers communicate each other. You can add as many controllers as you want there.
Here it looks like:
package card;
public class Context {
private final static Context instance = new Context();
public static Context getInstance() {
return instance;
}
private Controller controller;
public void setController(Controller controller) {
this.controller=controller;
}
public Controller getController() {
return controller;
}
private FontController fontController;
public void setFontController(FontController fontController) {
this.fontController=fontController;
}
public FontController getFontController() {
return fontController;
}
}
Controller:
I created getters and setters (ALT + Insert in IDEA) for Label that I wanna change
public Label getCategory1() {
return category1;
}
public void setCategory1(Label category1) {
this.category1 = category1;
}
To get FontController variables and methods through Context class I placed line of code
//getting FontController through Context Class
FontController fontCont = Context.getInstance().getFontController();
I registered Controller in Context class through my initialize method (my class implements Initializable)
#FXML
public void initialize(URL location, ResourceBundle resources) {
//register Controller in Context Class
Context.getInstance().setController(this);
}
FontController:
to get Controller variables and methods I placed this code:
//getting Controller variables and methods through Context class
Controller cont = Context.getInstance().getController();
I also registered FontController in Context class through initialize method:
#Override
public void initialize(URL location, ResourceBundle resources) {
//register FontController in Context Class
Context.getInstance().setFontController(this);
}
Method that send text and text color from label in this FontController to label in Controller when I click on button:
#FXML
void applyFont(ActionEvent event) {
cont.getCategory1().setText(fontLabel.getText());
cont.getCategory1().setTextFill(fontLabel.getTextFill());
}
*By creating Context class you can make controllers communicate each other and create as many controllers as you want there. Controllers see variables and methods of each other
I have searched again and again on this to no avail. I have a JavaFX FXML window which is connected to a Controller; this window is open. Clicking a button on the window triggers the opening of another FXML file, linked to its respective controller.
The second window (optionsUI.fxml and optionsController) has a few radio buttons. When one is clicked, I want the location of an image/button to change in the mainUI window. How do I go about doing that?
mainController:
public void assetPressed(MouseEvent event) {
//Get the source of Handler
HUDButton button = (HUDButton) event.getSource();
//Check if an asset is already selected
//----do a thing
//Open stage
openStage(currentAsset);
} else {
//if the current asset selected and the new asset clicked are the same
//----do something
closeStage();
}
//if the current asset selected and the new asset clicked are different
else {
//----do something else
assetIsSelected = true;
openStage(currentAsset);
}
}
}
//opening optionsUI.fxml
public void openStage(Asset asset) {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("optionsUI.fxml"));
Parent root = null;
try {
root = fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
optionsController controller = fxmlLoader.getController();
Scene scene = new Scene(root, 300, 450);
stage.setScene(scene);
if (alreadyExecuted == false) {
stage.initStyle(StageStyle.UNDECORATED);
stage.initOwner(stageControls); //Making the mainUI the owner of the optionsUI
stage.setTitle("HUDEdit Version 3.0.0");
alreadyExecuted = true;
}
The main issue I am having is adding an event handler on the radio buttons which will change a property of the Button that was pressed (currentButton). I searched on this issue, but what I got was what I have already done: to open a new stage with the new values present in the other FXML file.
You can do something like this in your OptionsController (I am going to rename things to conform to standard naming conventions, btw.)
The basic idea here is just to expose a property representing what the user has selected via the radio buttons.
public class OptionsController {
#FXML
private RadioButton radioButton1 ;
#FXML
private RadioButton radioButton2 ;
private SomeType someValue1 = new SomeType();
private SomeType someValue2 = new SomeType();
private final ReadOnlyObjectWrapper<SomeType> selectedThing = new ReadOnlyObjectWrapper<>();
public ReadOnlyObjectProperty<SomeType> selectedThingProperty() {
return selectedThing.getReadOnlyProperty() ;
}
public final SomeType getSelectedThing() {
return selectedThingProperty().get();
}
public void initialize() {
radioButton1.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
selectedThing.set(someValue1);
}
});
radioButton2.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
selectedThing.set(someValue2);
}
});
}
// ...
}
And now when you load Options.fxml you can just observe that property and do whatever you need when it's value changes:
public void openStage(Asset asset) {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("optionsUI.fxml"));
Parent root = null;
try {
root = fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
OptionsController controller = fxmlLoader.getController();
controller.selectedThingProperty().addListener((obs, oldSelection, newSelection) -> {
// do whatever you need with newSelection....
});
// etc...
}
I am creating a board game with JavaFX. I have two controllers for my project and would like to access variables and methods from the main controller. My main controller has most of the game logic and UI but I would like to have a popup window for special cases. Right now I have the popup window, but would like to use variables from the main controller in the popup window controller. I was able to get variables from the secondary controller to the main controller but not the other way around. Any help would be appreciated.
Here is the code in the main controller that opens the new popup window:
public void newWindow() {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("player.fxml"));
//Parent root = fxmlLoader.load();
Stage playerStage = new Stage();
playerStage.setTitle("Pick your move");
playerStage.initStyle(StageStyle.UNDECORATED);
playerStage.setScene(new Scene((Pane) fxmlLoader.load()));
playerStage.show();
ControllerPopup controller =
fxmlLoader.<ControllerPopup>getController();
this.controller = controller;
if(currentCard.getMoves() == 10){
controller.handleTen();
}
else if(currentCard.getMoves() == 11){
controller.handleEleven();
}
else{
controller.handleSeven();
}
disablePawns(bluePawns);
} catch (Exception e) {
e.printStackTrace();
}
}
And here is the secondary controller:
public class ControllerPopup implements Initializable{
#FXML
private RadioButton button1,button2,button3,button4;
#FXML
private Button closeButton;
private RadioButton selectedButton;
private ToggleGroup group;
Controller controller;
#Override
public void initialize(URL location, ResourceBundle resources) {
group = new ToggleGroup();
button1.setToggleGroup(group);
button2.setToggleGroup(group);
button3.setToggleGroup(group);
button4.setToggleGroup(group);
}
public void handleTen(){
button1.setSelected(true);
button1.setText("Move forward 10");
button2.setText("Move backwards 1");
button3.setDisable(true);
button4.setDisable(true);
}
public void handleEleven(){
button1.setSelected(true);
button1.setText("Swap with another pawn");
button2.setText("Move forward 11");
button3.setDisable(true);
button4.setDisable(true);
}
public void handleSeven(){
button1.setSelected(true);
button1.setText("Move forward 1 and 6");
button2.setText("Move forward 2 and 5");
button3.setText("Move forward 3 and 4");
button4.setText("Move forward 7");
}
#FXML
public void handleCloseButtonAction(ActionEvent event) {
selectedButton = (RadioButton) group.getSelectedToggle();
Stage stage = (Stage) closeButton.getScene().getWindow();
stage.close();
}
public RadioButton getSelectedButton(){
return selectedButton;
}
You can just create the appropriate set methods in the "secondary" controller and pass the values to them from the main controller:
controller.setXXX(...);
If we have a Stage then Scene includes 2 Panes
the 1st Pane contains Button and the 2nd Pane is empty
could we load other fxml file inside this 2nd Pane?
fxml1: VBox
|_Pane1-->Button
|_Pane2
///////////////
fxml2: Pane--> Welcome to fxml 2
"when we click the button load the fxml2 inside Pane2 of fxml1"
Then after click
====I finally found this works after trying !====Thank you guys
#FXML Pane secPane;
public void loadFxml (ActionEvent event) {
Pane newLoadedPane = FXMLLoader.load(getClass().getResource("/application/fxml2.fxml"));
secPane.getChildren().add(newLoadedPane);
}
I finally found this works after trying !
#FXML Pane secPane;
public void loadFxml (ActionEvent event) {
Pane newLoadedPane = FXMLLoader.load(getClass().getResource("/application/fxml2.fxml"));
secPane.getChildren().add(newLoadedPane);
}
Just replacing the field in your controller class won't change the scene graph.
secPane is just a reference to a node in the scene graph.
If secPane is just a placeholder, you could replace it in the parent's child list:
public void loadFxml (ActionEvent event) {
// load new pane
Pane newPane = FXMLLoader.load(getClass().getResource("/application/Login2.fxml"));
// get children of parent of secPane (the VBox)
List<Node> parentChildren = ((Pane)secPane.getParent()).getChildren();
// replace the child that contained the old secPane
parentChildren.set(parentChildren.indexOf(secPane), newPane);
// store the new pane in the secPane field to allow replacing it the same way later
secPane = newPane;
}
This assumes of course, that getClass().getResource("/application/Login2.fxml") yields the correct resource and does not return null (which happens if no resource with the given name is available)
You can implement something like this :
public void start(Stage primaryStage) throws IOException {
primaryStage.setTitle("Title");
primaryStage.setScene(createScene(loadMainPane("path_of_your_fxml")));
primaryStage.show();
}
private Pane loadMainPane(String path) throws IOException {
FXMLLoader loader = new FXMLLoader();
Pane mainPane = (Pane) loader.load(
getClass().getResourceAsStream(path));
return mainPane;
}
private Scene createScene(Pane mainPane) {
Scene scene = new Scene(mainPane);
return scene;
}
Then you can create a separate class call Navigation to store all your fxml paths:
public class Navigator {
private final String P1;
private final String P2;
//then you can implement getters...
public String getP1() {
return P1;
}
public String getP2() {
return p2;
}
private static FxmlController Controller;
public static void loadPane(String fxml) {
try {
FxmlController.setPane(
(Node) FXMLLoader.load(Navigator.class.getResource(fxml)));
} catch (IOException e) {
e.printStackTrace();
}
}
public Navigator() throws IOException {
this.P1 = "p1.fxml";
this.P2 = "p2.fxml";}
Then you can load your pane in your button like below:
#FXML
private void btnAction(ActionEvent event) throws IOException {
Navigator.load(new Navigator().getP1());
..
.