Let's say I have a button in a nested (child) fxml file, and in the child's controller I have created an action event that fires on button click. From that method I want to disable or enable certain controls (for instance some tabs in a tabpane) in my main (parent) fxml.
How can I achieve this?
This is the closest thread I found, which discussed how to do it the other way around: JavaFX - Access fx:id from nested FXML
Any help is greatly appreciated!
Define an observable property in the nested controller, and observe it from the surrounding controller:
public class ChildController {
private final BooleanProperty stuffShouldBeDisabled = new SimpleBooleanProperty();
public BooleanProperty stuffShouldBeDisabledProperty() {
return stuffShouldBeDisabled ;
}
public final boolean getStuffShouldBeDisabled() {
return stuffShouldBeDisabledProperty().get();
}
#FXML
private void handleButtonClick(ActionEvent event) {
stuffShouldBeDisabled.set( ! stufShouldBeDisabled.get() );
}
// ...
}
and then in the "surrounding" (Parent) controller (i.e. the controller for the FXML file with the <fx:include> tag):
public class MainController {
#FXML
private ChildController childController ; // injected via <fx:include fx:id="child" ... />
#FXML
private Tab someTab ;
public void initialize() {
childController.stuffShouldBeDisabledProperty().addListener((obs, wasDisabled, isNowDisabled) -> {
someTab.setDisable(isNowDisabled);
}
}
// ...
}
Related
I have two controller class Fxml1.java and Fxml2.java. In Fxml1.java I hava label Label l1, and in Fxml2.java I have text field t1. I have some text in text field t1 and I want to set this text to Label l1. i want to do some thing like below. Below is a just dummy code for understanding consider both class have separate fxml files. Here i receive NullPointerException.
//class Fxml1
public class Fxml1{
public label l1;
l1.setText("hello");
}
//class Fxml2
public class Fxml2{
public TextField t1;
public Button b1;
public Fxml1 ob;
public void onButtonSubmit(ActionEvent event){
ob = new Fxml1();
ob.l1.setText(t1.getText());
}
public void initialize(URL url, ResourceBundle rb){
t1.setText("This is textfield text");
}
}
In your Fxml2 controller you are creating a new, completely separate instance of Fxml1 that is not linked to your fxml graph file, so it cannot update your required label since it has no access to it or any knowledge that it exists.
You should handle this through the use of events. A basic tutorial is can be found on Vogella's site.
Inject the event broker into both controllers:
#Inject IEventBroker broker;
In your Fxml2 controller you should post this string to the event broker when the text is updated:
public void onButtonSubmit(ActionEvent event) {
broker.post("SomeSharedConstant", t1.getText());
}
In your Fxml1 controller, you should listen for this event and update your label when it is fired.
#Inject #Optional
public void getEvent(#UIEventTopic("SomeSharedConstant") String text) {
// text1 is a SWT Text field
l1.setText(text);
}
I am trying to make it such that I can create a new tab for my TabPane from within another tab but I am having some difficulty. Currently I have the TabPane set up in the "main-window.fxml" with the corresponding MainWindowController. I have a tab within this TabPane which, via fx:include, displays "mainTab.fxml" to the scene graph, controlled by MainTabController. Now from within the "mainTab" I want a button to be able to add an additional tab to the TabPane, but since this is requires a reference to the TabPane in "main-window", I have created a static method in "main-window". When the run the code below I get a NullPointerException on this line in the MainWindowController:
mainTabPane.getTabs().add(new Tab(team.getTeamName()));
Could someone please tell me as to why it is giving this exception and how I can begin to work around it?
main-window.fxml:
<TabPane fx:id="mainTabPane">
<tabs>
<Tab fx:id="mainTab" text="Main" closable="false">
<fx:include source="mainTab.fxml" fx:id="mainWindowTab" alignment="CENTER"/>
</Tab>
</tabs>
</TabPane>
mainTab.fxml (the event handler for the button):
#FXML
public void handleSubmit() {
String teamName = teamNameTextField.getText();
Roster roster = rosterComboBox.getValue();
int startWeek = spinner.getValue();
Team newTeam = new Team(teamName, startWeek, roster);
TeamData.addTeam(newTeam);
MainWindowController controller = new MainWindowController();
controller.createTeamTab(newTeam);
}
MainWindowController:
public class MainWindowController {
#FXML
private TabPane mainTabPane;
public void createTeamTab(Team team) {
mainTabPane.getTabs().add(new Tab(team.getTeamName()));
}
}
Your code doesn't work because you are not calling createTeamTab(...) on the controller: you are calling it on another instance of MainWindowController that you created. (The fields annotated #FXML are initialized in the controller instance by the FXMLLoader when the FXML is loaded: for fairly obvious reasons they will not be set to the same values in arbitrary other instances of the same class.) You need to get a reference to the controller you are using for the main tab, and pass it a reference to the main controller.
You didn't tell us the class name for the controller of mainTab.fxml: I will assume it is MainTabController (so just change it to whatever class name you actually use).
In MainWindowController, do:
public class MainWindowController {
#FXML
private TabPane mainTabPane;
#FXML
// fx:id of the fx:include with "Controller" appended
private MainTabController mainWindowTabController ;
public void initialize() {
mainWindowTabController.setMainWindowController(this);
}
public void createTeamTab(Team team) {
mainTabPane.getTabs().add(new Tab(team.getTeamName()));
}
}
and then in MainTabController do
public class MainWindowController {
private MainWindowController mainWindowController ;
public void setMainWindowController(MainWindowController mainWindowController) {
this.mainWindowController = mainWindowController ;
}
#FXML
public void handleSubmit() {
String teamName = teamNameTextField.getText();
Roster roster = rosterComboBox.getValue();
int startWeek = spinner.getValue();
Team newTeam = new Team(teamName, startWeek, roster);
TeamData.addTeam(newTeam);
mainWindowController.createTeamTab(newTeam);
}
}
I have an app, which has HomeScene.fxml file with headers and menu. HomeScene has also dashboardPane, which should be changed dynamically after menu button is being pressed. Dashboard pane content should be loaded from another fxml file, lets say 'FinancesPane.fxml' or 'SettingsPane.fxml'.
Im trying to replace content of dashboardPane in HomeController:
#FXML
public void handleFinancesButtonAction() {
FinancesPaneFactory paneFactory = new FinancesPaneFactory();
dashBoardPane.getChildren().clear();
dashBoardPane.getChildren().add(paneFactory.createPane());
}
My FinancesPaneFactory looks like this:
public class FinancesPaneFactory extends PaneFactory {
private static final String PANE_TEMPLATE_PATH = "/sceneTemplates/FinancesPane.fxml";
public FinancesPaneFactory() {
super(PANE_TEMPLATE_PATH );
}
#Override
protected Pane generatePane(FXMLLoader loader) {
try {
return (Pane) loader.load();
} catch (IOException e) {
throw new FatBirdRuntimeException("Unable to load FinancesPane", e);
}
}
}
To be more clear, this is how HomeScene looks like: HomeScene .
This empty space is a dashboardPane, and should be replaced with another content when user press the left menu button.
How to inject this content dynamically?
Yes, you should do this to keep scene graph low and you will benefit from better performance , what i do is create dynamic container :
#FXML
private ScrollPane dynamicNode;
Scroll pane is a good choice.
This is put to MainController.
I have main controller different from others , main controller is actually the only one i initialize, so in your main program class whatever you call it :
private static MainViewController mainViewController;
...
private static BorderPane loadMainPane() throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setController(mainViewController);
BorderPane mainPane = (BorderPane) loader.load(
CsgoRr.class
.getResourceAsStream(Info.Resource.FXML_FILE_MAIN));
mainPane.getStylesheets().add(CsgoRr.class.getResource("path...style.css").toString());
return mainPane;
}
Dont forget to create static accessor, other controllers that i have are usually not created this way , i use fx:controller in fxml to specify what controller should be for which fxml , its usually handy to have mainController accessable.
So to change your views create in your main controller methods that are connected to your menu with whose you change views
#FXML
private void setViewPreferences() {
setView(Info.Resource.FXML_FILE_PREFERENCES);
}
#FXML
private void setViewProductPage() {
setView(Info.Resource.FXML_FILE_PRODUCT_PAGE);
}
Currently in dynamicNode is helper to see what exactly is the current selected, its
private String currentlyInDynamicPane;//not important
Here is setView
public void setView(String fxmlPath) {
dynamicNode.setContent(getView(fxmlPath));
currentlyInDynamicPane = fxmlPath;
}
public Node getView(String fxmlPath) {
try {
return new FXMLLoader(getClass().getResource(fxmlPath)).load();
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
}
So when you click left menu you swap FXML files, you can make sure that you have some default FXML shown at the start or when nothing in menu is selected as well.
This is the way i do it, roughly.
So think about YOUR DASHBOARD as DynamicPane,
I've recently delved into JavaFX as a C# developer. One thing I noticed in Java is that you're not spoon fed the way Visual Studio/Microsoft spoonfeed us.
So. When I was creating a form using the scene builder for IntelliJ Idea on JavaFX. I inherited "Stage" for my controller class and created a void called load that will load the instance of the scene from the FXML file. Therefore when I call load() from the Main entry point or anywhere it will load the fxml file and show.
LoginController frmLogin = new LoginController();
frmLogin.load();
The problem is that it works and it does't work.
Here's my code.
Main.Java
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
LoginController frmLogin = new LoginController();
frmLogin.load();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
LoginController.Java
public class LoginController extends Stage
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
this.show(); //The problem occurs here!
}
else
{
Messagebox.Show("Incorrect Login");
}
}
public void load() throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("frmLogin.fxml"));
this.setScene(new Scene(root));
this.setTitle("JavaFX GUI");
this.setResizable(false);
this.initModality(Modality.APPLICATION_MODAL);
this.show();
}
}
Here's a GIF of the problem.
http://i.imgur.com/0hOG76M.gif
I want to know why when I call .show() it shows a blank for?
Any help would be appreicated.
Solution
Don't inherit Stage in your Controller.
JavaFX will implicitly create a Stage for your application and pass it to your application (the primaryStage parameter in your application start method).
Sample
Here is a quick update which should work. Another alternative for this is to factor out the stage management as in James's answer.
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("frmLogin.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("JavaFX GUI");
primaryStage.setResizable(false);
primaryStage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
. . .
public class LoginController
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
}
else
{
Messagebox.Show("Incorrect Login");
}
}
}
Aside: I am not sure what your MessageBox class is, but JavaFX 8u40 has a built-in Alert dialog box for standard message box style functionality, so that would be the preferred method to do that.
It looks like you have confused the different pieces that make up the application.
The FXML typically represents the "view"; i.e. the portion of the UI that is visible. It defines what controls are displayed and how they are laid out.
The controller implements the logic that is connected to (controls) the view. So it typically processes user input and updates the view in various ways.
A Stage is a window.
So, I don't think it really makes sense that your controller is a Stage. There are some scenarios where you might make a controller a subclass of a UI element, but those are somewhat advanced uses of JavaFX, and even then you would typically subclass a layout pane, not a Stage.
Here's roughly what happens when you call load on an FXMLLoader:
The FXMLLoader creates a hierarchy of Nodes (UI elements) corresponding to the elements defined in the FXML file
If the FXML file defines a fx:controller attribute in its root element, the FXMLLoader constructs a new instance of that class. It then injects any elements with fx:id attributes into fields in that controller instance with names matching the fx:id values. It also registers any event handlers mapping to methods in the controller instance.
The FXMLLoader's load() method returns the object corresponding to the root element of the FXML file.
So, in your code, you actually end up with two LoginController instances. You create one yourself in the start() method. You then call load() on that instance. That method calls load(...) on an FXMLLoader (via the really ugly static load method). Calling FXMLLoader.load(...) then causes the FXMLLoader to create an instance of the class declared in fx:controller. I'm guessing (you didn't show the FXML code) that class is also LoginController. So that is the second instance.
Now what happens, is that you get a reference to the UI element from FXMLLoader.load(). You put that in a Scene, and set the Scene in the LoginController, which - unusually - is a Stage. Then you make the Stage appear on the screen with show(). Note this happens in the instance you created in the start method.
When the user presses the button that has btnLogin_Clicked registered as its handler, the handler method is invoked on the controller instance: the one created by the FXMLLoader. That instance never had a Scene set, so when you then call this.show() it shows that instance of the LoginController (which, again, is a Stage). Since it never had its scene set, you see a blank window.
It's not actually clear to me what you intend with the call to this.show() in btnLogin_Clicked anyway. Assuming you thought this was the same Stage you had created from the start(...) method, that Stage is already showing.
The typical pattern is that you use the primaryStage that is passed to the start(...) method, and set a scene in that and show it. So you'd do something like:
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("frmLogin.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("JavaFX GUI");
primaryStage.setResizable(false);
primaryStage.initModality(Modality.APPLICATION_MODAL);
primaryStage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
and then the controller is just a controller: it just handles the logic:
public class LoginController
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
// I don't really know what you were trying to do here
// but if you need a reference to the window containing the
// associated fxml elements, you can get it from one of those
// elements:
Stage stage = (Stage) txtUsername.getScene().getWindow();
//this.show(); //The problem occurs here!
}
else
{
Messagebox.Show("Incorrect Login");
}
}
}
Typically what you want to do when the user has successfully logged in, is to display something new in the current window. The simplest way to do this is just to set the root of the current scene to the content of a different FXML file. For example:
public class LoginController
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
Scene currentScene = txtUsername.getScene();
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
currentScene.setRoot(root);
// resize window:
currentScene.getWindow().sizeToScene();
}
else
{
Messagebox.Show("Incorrect Login");
}
}
}
Here Main.fxml defines the main application the user sees, having successfully logged in, and defines its own controller class, etc.
I have one question regarding Javafx controller.
Lets say, I have multiple fxml files that are bind together in a main app. Then I have separate controllers for every fxml files. Lets see the following structure
com.par.app
- MainApp.java -> This is the main Application
- FirstController.java
- SecondController.java
com.par.app.view
- First.fxml
- Second.fxml
com.par.app.model
- MyModel -> This has some getter and setter methods.
Now as per above structure, I have a checkbox in First.fxml and a label in Second.fxml.
My Question : How can i set the label text in Second.FXML by checking and unchecking the checkbox in First.FXML , I have tried like this:
// In FirstController.Java
1) Initialize the SecondController
2) Get checkbox from FXMl as , priate CheckBox box1;
3) On initialize(....) method, I have set the event handler, as box1.setOnAction(enableHandle)
4) Finally the event Handler as,
EventHandler<ActionEvent> enableHandle = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (box1.isSelected()) {
secondController.setLabelText("PoP");
} else {
secondController.setText("Gone Wrong");
}
}
};
Similarly, On my second controller I have declared FXML control as,
#FXML
private Label lblTes;
// the method
public void setLabelText(String a)
{
this.lblTes.settest(a);
}
-> The above wont work as it returns Unknown Source.
The next way I tried is by using the MyModel , and using getter and setter methods, But unsuccessful.
I'm sorry my question is really long. I have tried but not succeeded.
What can I do to solve this?
Thanks in advance.
// my model looks like
public class MyModel {
private String btnname;
public String getBtnname() {
return btnname;
}
public void setBtnname(String btnname) {
this.btnname = btnname;
}
}
When you check the check box then in the controller of the FirstView (where you implement an event handler for the check box click) change the label text in your model.
Your model should be bound to your views therefore the label text in your SecondView should be updated.
If you did not bind the model to your views you may use an Observer pattern.
1.Change your model and extend java.util.Observable
public class MyModel extends Observable {
private String btnname;
public String getBtnname() {
return btnname;
}
public void setBtnname(String btnname) {
this.btnname = btnname;
pingObservers()
}
private void pingObservers() {
setChanged();
notifyObservers();
}
}
Register your SecondController as an Observer of the model. When you set the model to the controller add a line similar to this:
model.addObserver(this);
SecondController must implement java.util.Observer.update(...)
void update(Observable o, Object o1) {
// Set the label text with model value
}
In you event handler in the FirstController when you call the setBtnname() method on your model the update() method in the SecondController will be called. There up to you to add the code to change your label text. Since the label is in the view controlled by SecondController you just need to inject a reference of the label in the controller with #FXML annotation.