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);
}
}
Related
As said in the title I want to specialize the usage of a TableView that I will reuse many time, that speciazation contains :
Columns shown
Filtering of what is added according to a default duplicate filter and some addiotional (either based on boolean values or callbacks).
I use raw FXML files and Controller, no UI drag and drop building.
In order to keep the usage of my component the easiest possible I would like to hide the component part of JavaFX and only allow my methods, how to do it ?
Create a class extending Control. Add the methods you want the user to access to this class.
Create a skin for this class and implement the behavior you don't want the user to access there.
Benefits:
Hides the implementation details from user.
Allows the user to replace the "private" behavior, if neccessary.
Allows you to access the "public" behavior form the node directly.
Example
Control
public class MyControl extends Control {
#Override
protected Skin<?> createDefaultSkin() {
return new MyControlSkin(this);
}
private final StringProperty text = new SimpleStringProperty();
public final String getText() {
return this.text.get();
}
public final void setText(String value) {
this.text.set(value);
}
public final StringProperty textProperty() {
return this.text;
}
}
Skin
public class MyControlSkin extends SkinBase<MyControl> {
public MyControlSkin(MyControl control) {
super(control);
Text text = new Text();
text.textProperty().bind(control.textProperty());
getChildren().setAll(text);
}
}
Use
#Override
public void start(Stage primaryStage) {
final MyControl control = new MyControl();
Button btn = new Button("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
control.setText("Hello World!");
});
Scene scene = new Scene(new VBox(10, btn, control));
primaryStage.setScene(scene);
primaryStage.show();
}
Note that it does not matter how you create the UI. It could be directly created from java or loaded from fxml - this does not matter. You could e.g. use the skin as fxml controller and root:
<fx:root type="javafx.scene.control.SkinBase" xmlns:fx="http://javafx.com/fxml">
<children>
<Text fx:id="text"/>
</children>
</fx:root>
#FXML
private Text text;
public MyControlSkin(MyControl control) throws IOException {
super(control);
getChildren().clear();
FXMLLoader loader = new FXMLLoader(someUrl);
loader.setRoot(this);
loader.setController(this);
loader.load();
text.textProperty().bind(control.textProperty());
}
BTW: The duplicate filtering would IMHO be better of in a seperate class, like a TransformationList. This way you could reuse the behavior independent of the UI which would allow easier reuse (e.g. For use with a ListView).
Here is the current way I found to do it use an interface an use that interface when using #FXML injection.
My specialized components :
public class EquipmentTableView extends AnchorPane implements IEquipmentTableView{[...]}
Where AnchorPane is from JavaFX and the interface from me.
The FXML of the component :
<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
<TableView fx:id="tableView"
AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
</TableView>
</fx:root>
The FXML to include the component :
<EquipmentTableView fx:id="tableView"/>
The injection in the controller
public class ReadController {
// use the interface here
#FXML private IEquipmentTableView tableView;
public static String FXML="read.fxml";
public static String TITLE_KEY="read.title";
#FXML
void initialize(){
tableView.addAll(Arrays.asList(
new Equipment("name1", "partNumber1", "MFC1", "00000000001", "3400000000000001"),
new Equipment("name2", "partNumber2", "MFC2", "00000000002", "3400000000000002"),
new Equipment("name3", "partNumber3", "MFC3", "00000000003", "3400000000000003")));
}
}
To sum it up :
By using the TableView inside the components instead of inheriting it I don't allow to manipulate the TableView. I inherit AnchorPane instead because I want my table view to auto-resize, you can inherits from Pane otherwise.
I am allowed to manipulate it using only the interface methods since I cannot access the TableView.
Using only the interface disallow myself to manipulate the AnchorPane for other purposes or having to search through the methods that are mines.
I'm building a lobby that should display messages from players.
Here is my lobby controller:
public class LobbyController{
#FXML public JFXTextArea chatArea;
#FXML public JFXTextField chatField;
public void displayChatMessage(String message){
chatArea.appendText(message);
}
#FXML
public void onEnter(){
Main.client.sendChatMessage(chatField.getText());
}
}
I'm accessing this controller from another class like this:
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("/views/lobby.fxml"));
fxmlLoader.load();
LobbyController controller = fxmlLoader.getController();
if(!gameStarted){
controller.displayChatMessage(((Packets.ChatMessage) o).message);
}
My controller is definitely receiving the message as if I put a print line in displayChatMessage(), it prints the message correctly.
My problem is that appendText() does not seem to be changing the text at all.
I have an FXML file with an empty Label named welcomeText.
It's the main Scene of my program and I would like to set the label text to something like that: Hello "username" when I start the program on Windows or Linux.
public class MainAdminController implements Initializable {
#FXML
private Label welcomeText;
final String username = System.getProperty("user.name");
#FXML
private void SetWelcome() {
welcomeText.setText("Hello " +username);
}
}
But it isn't show anything. Any idea how can I set the Label properly when I open the scene? Thanks.
The SetWelcome method is superfluous.
Define an initialize() method for your controller and it will automatically be invoked when the FXMLLoader loads a new document linked to the controller.
public void initialize() {
welcomeText.setText("Hello " +username);
}
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);
}
}
// ...
}
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,