JavaFX controller communication with dynamic content - javafx

Here is a general structure of a JavaFX android mobile application I am creating.
Using (AppBar or AppBarSearch these interchange dynamically) as nested controllers within Primary Application FXML.
ParentController
- AppBarController
- AppBarSearchController
primary.fxml
- appBar.fxml / appBarSearch.fxml
<AnchorPane fx:id="appBarPane" prefHeight="56.0" prefWidth="350.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<children>
<fx:include source="appbar.fxml" fx:id="appBar" />
<!-- initially shows AppBar but appBarSearch can also be here after clicking search button -->
</children>
</AnchorPane>
A button in each of the child fxml is responsible for changing between the fxml content from appBar/AppBarSearch.
The issue arises when I am dynamically change the content of the appBar to appBarSearch and back. I want the appBar menu button to communicate to the NavigationMenu to slide in and out.
I have been looking into whether I should somehow have an instance of the parentController inside the AppBarController.
I did use the following:
#FXML
private AppBarController appBarController; // injected via <fx:include fx:id="child" ... />
<fx:include source="appbar.fxml" fx:id="appBar" /> <- dynamically changes
//to dynamically change content in Panes
public static void setView(View view, Pane pane) {
try {
pane.getChildren().clear();
pane.getChildren().setAll((Node) FXMLLoader.load(Main.class.getResource(view.getTemplate()), resources));
} catch (IOException e) {
logger.error(e.getMessage());
}
}
#FXML
public void initialize() {
appBarController.setParentController(this);
}
#FXML
private void menuButtonClick (ActionEvent event) {
this.parentController.triggerMenu();
}
Initially the above works but after switching between appBarSearch and appBar it gives me a nullPointer to the parentController instance.
It may be that after switching between controllers dynamically it would not recognise the child controller.
I want the menuBtn in AppBar to open a navigationMenu so it would require to call triggerMenu() from within PrimaryController to start animations of it sliding in and out after the button in AppBar is clicked.

Thanks very much to the comments I was able to fix the issue and improve my understanding of how to use Controllers and included fxml.
Below is the general code I used to connect the child controllers with my primary controller and change views dynamically.
#FXML
private AppBarController appBarController; // injected via <fx:include fx:id="child" ... />
#FXML
private NavMenuController navMenuController; // injected via <fx:include fx:id="child" ... />
#FXML
private MainContentController mainContentController; // injected via <fx:include fx:id="child" ... />
#FXML
public void initialize() {
appBarController.setScreenParent(this);
navMenuController.setScreenParent(this);
mainContentController.setScreenParent(this);
}
/**
* Set View on a Pane Javafx component
*/
public <T extends Pane> boolean setView(View view, T pane) {
try {
FXMLLoader myLoader = new FXMLLoader(getClass().getResource(view.getTemplate()), resources);
pane.getChildren().clear();
pane.getChildren().setAll((Node) myLoader.load());
ControlledScreen childController = myLoader.getController();
childController.setScreenParent(this);
return true;
} catch (IOException e) {
logger.error(e.getMessage());
return false;
}
}
public void setMainContentView(View view) {
setView(view, mainContentPane);
}
public void setAppBarView(View view) {
setView(view, appBarPane);
}
public void setNavMenuView(View view) {
setView(view, navMenuPane);
}
public void triggerNavMenu() {
if (navMenuPane.getTranslateX() != 0) {
openNav.play();
appBarController.setMenuClosedImage();
} else {
closeNav.setToX(-(navMenuPane.getWidth()));
closeNav.play();
appBarController.setMenuClosedImage();
}
}

Related

Update component in javafx

Hy guys, I'm developing an application using javafx and fxml.
What I'm currently trying to do is to update a flowpane, adding components to it when the user performs an action (is not the case but let's take as an example the click of a button).
Let's suppose that the method just has to add buttons with the passed labels to the flowpane.
In the Controller of the fxml that contains the flowpane I have this:
public class CategoryPaneController implements Initializable {
#FXML
private FlowPane categoryContainer;
public void setCategories(String[] labels) throws IOException{
for(String label : labels){
Button button = new Button(label);
categoryContainer.getChildren().add(button);
}
}
}
This method is called in another controller as follows:
public class AddCategoryController implements Initializable {
#FXML
private Pane addCategoryPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void refreshCategories(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/categoryPane.fxml"));
loader.load();
CategoryPaneController cat = loader.getController();
String[] labels = {"categoria3", "categoria4"};
cat.setCategories(labels);
}
}
The categoryPane.fxml is the following:
<ScrollPane fx:id="storeMainPane" fx:controller="controller.CategoryPaneController">
<content>
<VBox prefHeight="629.0" prefWidth="862.0">
<children>
<Label alignment="TOP_CENTER" contentDisplay="CENTER" prefHeight="68.0" prefWidth="857.0" text="Magazzino" />
<FlowPane fx:id="categoryContainer" prefHeight="549.0" prefWidth="862.0" />
</children>
</VBox>
</content>
</ScrollPane>
And the following is the addCategory.fxml file
<Pane fx:id="addCategoryPane" fx:controller="controller.AddCategoryController">
<children>
<Button onAction="#refreshCategories" text="aggiungi categoria" />
</children>
</Pane>
I've debbugged the code and the method is called in the right way, it adds the buttons to the flowpane but the latter doesn't change.
Thanks in advance. <3
Every time you load a new FXML a new Controller instance and new nodes will be generated.
If you want to use an existing controller and nodes, you need to store a reference to the originally loaded controller and invoke methods on that.
Calling methods on a controller.
Or you can use MVC:
Applying MVC With JavaFx

Using JavaFX to pass data to an already opened window

I'm new to JavaFX. This was easy to do without FXML, but the FXML controllers are stumping me.
What I'm trying to do: Set up a main window that has a button. When clicked, the button launches a second popup window in which the user submits a value. Upon closing the second window (done currently with a button click on the pop-up), I'd like the user's input to be passed back to the main controller-- the main window that is already open.
So far, I've got 2 .fxml files(one for a main window the other for a popup), and the corresponding controllers: MainWindowController:
public class MainController implements Initializable {
#FXML
public Label label;
#FXML
private Button button;
#FXML
private void popBtnClick(ActionEvent event) throws IOException {
//creates new pop-up window
Stage popupSave = new Stage();
popupSave.initModality(Modality.APPLICATION_MODAL);
popupSave.initOwner(ComWins.stage);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("PopUp.fxml"));
Parent root = loader.load();
PopUpController controller = loader.getController();
//calls a method in the PopUpController, and uses it to pass data to
//the Popup window.
controller.dataToPopUp(7);
Scene scene = new Scene(root);
popupSave.setScene(scene);
popupSave.showAndWait();
}
I also tried calling this method from the popup window with no success in
changing Main's label.
public void dataPass(String name){
label.setText(name);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
And PopUpController:
public class PopUpController implements Initializable {
#FXML
private Button ok_btn;
#FXML
public TextField input_tf;
#FXML
private String input;
#FXML
private void okBtnClick() throws IOException {
input = input_tf.getText();
/*my attempt to pass the variable-- using a loader to get the
controller and then referencing the public label. */
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("Main.fxml"));
Parent root = loader.load();
FXMLDocumentController controller = loader.getController();
//this line works, and retrieves the label's text.
String temp = controller.label.getText();
//but this line does not work. Why?
controller.label.setText(input);
//closes this pop-up
Stage stage = (Stage)input_tf.getScene().getWindow();
stage.close();
}
//this method is called in the maincontroller and used to pass data to
//the popup's textfield.
public void dataToPopUp(int x){
input_tf.setText(Integer.toString(x));
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Using the above, Main passes ('7') into the PopUp's textfield. But if the user enters something else into the textfield, I cannot seem to get that data back to Main. This is like having a Settings Pop-up window, and then passing the user's selections from the Settings popup back to the main window. I just cannot figure out how to pass things back to the main window.
I am not using SpringBoot, but thanks for the suggestion.
Thank you in advance!
If you are using Spring Boot, MPV Java on YouTube has great examples of connecting your controllers together allowing you to pass information cleanly and easily between them.In my application I've been able to implement these examples. Each controller is registered as a bean using #Component which means you can #Autowire the controller into another controller. In your main controller I would recommend setting up some basic getters/setters to allow outside interaction with your fields so other controllers can "talk" to the main controller.
A really basic example would be:
#Component
public class MyMainController {
#FXML private TextField exampleTextField;
...
...
/* Get the text of a field from this controller: can be accessed from another controller */
public String getExampleTextField() {
exampleTextField.getText();
}
/* Set the text of a field on this controller: can be accessed from another controller */
public void setExampleTextField(String text) {
exampleTextField.setText(text);
}
}
#Component
public class AnotherController {
#Autowired private MyMainController myMainController;
...
...
public void someMethod(String newText) {
// Do some work here and set some text back to the main controller
myMainController.setExampleTextField(newText);
}
}
MPV Java does a much better job of explaining this concept.
https://www.youtube.com/watch?v=hjeSOxi3uPg
I reviewed the suggestions and was unable to get anything to work for me-- many of the concepts were over my head, as I'm new to Java. After several attempts, I was able to get a fairly simple solution to the problem. It is likely far from best practices, but it works:
In the main window's controller, use the popup's controller to call the Pop Up's string variable and set it as the label's text (label.setText(controller.test)). The string variable has to be public, and is set once the pop-up is closed by the button click. See the code below:
Main.fxml:
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchingpass.MainController">
<children>
<Button fx:id="button" layoutX="126" layoutY="90" onAction="#popBtnClick" text="Click Me!" />
<Label fx:id="label2" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
<Label fx:id="label" layoutX="143.0" layoutY="38.0" text="Label" />
</children>
</AnchorPane>
MainController:
public class MainController implements Initializable {
#FXML
public Label label;
#FXML
private Button button;
#FXML
private void popBtnClick(ActionEvent event) throws IOException {
//creates new pop-up window
Stage popupSave = new Stage();
popupSave.initModality(Modality.APPLICATION_MODAL);
popupSave.initOwner(SwitchingPass.stage);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("PopUp.fxml"));
Parent root = loader.load();
PopUpController controller = loader.getController();
Scene scene = new Scene(root);
popupSave.setScene(scene);
popupSave.showAndWait();
//after the popup closes, this will run, setting the label's text to the popup's test variable, which is public.
label.setText(controller.test);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
PopUp.fxml:
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchingpass.PopUpController">
<children>
<TextField fx:id="input_tf" layoutX="207.0" layoutY="65.0" />
<Button fx:id="close_btn" layoutX="268.0" layoutY="185.0" mnemonicParsing="false" onAction="#closeBtnClick" text="Close" />
</children>
</AnchorPane>
PopUpController:
public class PopUpController implements Initializable {
#FXML
public Button close_btn;
#FXML
public TextField input_tf;
#FXML
public String test;
#FXML
private void closeBtnClick() throws IOException {
//stores textfield input as a string
test = input_tf.getText();
Stage stage = (Stage)input_tf.getScene().getWindow();
stage.close();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Please note that the code's main class that extends Application must declare the stage variable as a public static variable:
public class SwitchingPass extends Application {
//this is necessary to initialize the owner of the popup
public static Stage stage;
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Again, probably not the best way to accomplish this, but it works, and perhaps it is helpful to someone else.
I did that this way.
First create an interface
public interface DataSender {
void send(String data);
}
and then implement that interface to your Main Window Controller
public class FXMLDocumentController implements Initializable,DataSender {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
try {
Stage popupSave = new Stage();
popupSave.initModality(Modality.NONE);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("PopUp.fxml"));
Parent root = loader.load();
PopUpController controller = loader.getController();
controller.setX(7);
//this is the line use to get data from popup
controller.setSendDataSender(this);
Scene scene = new Scene(root);
popupSave.setScene(scene);
popupSave.show();
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
//this method can call from popus controller
#Override
public void send(String data) {
label.setText(data);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
and then override implemented interface method and inside the method you can change Main window UI data I changed label text.
and then create variable in pupup controller with type DataSender(created interface befor) and create method to set that interface
public class PopUpController implements Initializable {
private int x;
//this is the variable
private DataSender dataSender;
#FXML
private Button btnOk;
#FXML
private TextField txtText;
#Override
public void initialize(URL url, ResourceBundle rb) {
btnOk.setOnAction(e->{
//When Click this button will call Main Window send method
dataSender.send(txtText.getText());
});
}
public void setX(int x){
this.x=x;
}
//this is the method to set variable value from Main Window
public void setSendDataSender(DataSender ds){
this.dataSender=ds;
}
}
and then when popup window button clicked then call send method with data and then will change main window label text.
This solution is work for me.hope this useful for you also .

Override TableRow styling on TableCell

so I have the following problem.
I have a JavaFX TableView, where all the Cells has the same styling but one. For that one cell I would like to remove all the styling, but I guess the RowFactory has some priority or whatever.
I have some kind of code like that:
FXML
<TableView fx:id="tableView">
<columns>
<TableColumn fx:id="tcolNoStyle"/>
<TableColumn fx:id="tcolStyled"/>
</columns>
</TableView>
Controller
public class TableController {
#FXML
TableView<TableData> tableView;
#FXML
TableColumn<TableData, String> tcolNoStyle;
#FXML
TableColumn<TableData, String> tcolStyled;
#FXML
public void initialize(){
tableView.setRowFactory(row -> new RowFactory());
tcolNoStyle.setCellFactory(cell -> new CellFactoryForNoStyling());
}
}
The Data behind the table
public class TableData {
public final SimpleStringProperty noStyleTextProperty;
public final SimpleStringProperty styledTextProperty;
public TableData(){
noStyleTextProperty = new SimpleStringProperty();
styledTextProperty = new SimpleStringProperty();
}
}
RowFactory
public class RowFactory extends TableRow<TableData> {
public RowFactory(){
itemProperty().addListener(((observable, oldValue, newValue) -> {
if (newValue != null){
setStyle("-fx-text-alignment: right");
}
}));
}
}
CellFactory that one no styled Cell
public class CellFactoryForNoStyling extends TableCell<TableData, String> {
public CellFactoryForNoStyling(){
super();
setStyle(null);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setStyle(null);
}
}
So what I want is, that only that one column should have no style
Thank you in advance!
The problem here is the fact that the -fx-text-alignment property is inherited. setStyle(null) only makes sure there are no additional changes done for the node it's called for.
The probably simplest option would be to simply specify the default value using setStyle:
public CellFactoryForNoStyling() {
setStyle("-fx-text-alignment: left");
}
You could leave the styling to a CSS stylesheet though. This is much more convenient than using inline styles, since it's much easier this way to deal with selection, focus, ect.
You could e.g. add the following stylesheet to the scene; this way you do not to use a custom cellValueFactory/rowFactory:
/* default cell style */
.my-table .table-cell {
-fx-text-alignment: right;
}
/* overwrite above style using selector with higher specifity */
.my-table .table-cell.unstyled {
-fx-text-alignment: inherit;
}
<TableView fx:id="tableView" styleClass="my-table"> <!-- style class added here-->
<columns>
<TableColumn fx:id="tcolNoStyle" styleClass="unstyled"/> <!-- style class added here -->
<TableColumn fx:id="tcolStyled"/>
</columns>
</TableView>

mvvmFX: Changing Part of a Scene on Runtime

I'm building a JavaFX Application with mvvmFX.
I have a scene with two panes. Both panes should include any.fxml . I would like to change the included panes at runtime.
So I've found this solution: JavaFX/SceneBuilder - Changing only PART of a Scene and tried to apply it.
So far, so good. Pane2 is displaying any.fxml, but unfortunately, the controller/viewModel is not getting loaded. At least it seems so, any.fxml contains a label which should get modified by the viewModel.
If O add fx:controller="anyVM" to any.fxml and include it via fx:include source="any.fxml" in pane2.fxml everything is working fine. But it's important for me to change it on runtime.
I would be glad if somebody knows a solution how to achieve this.
This is the View of my Main-Scene:
public class Scene implements FxmlView<SceneVM>, Initializable {
#FXML
private BorderPane pane2;
#InjectViewModel
private SceneVM sceneVM;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("any.fxml"));
loader.setController(anyVM.class);
try {
pane2.setCenter(loader.load());
} catch (IOException e) {
e.printStackTrace();
}
}
}
This is pane2 in scene.fxml:
<BorderPane fx:id="pane2" layoutX="846.0" prefHeight="547.0" prefWidth="521.0">
<center> <!--<fx:include source="any.fxml" />-->
</center>
</BorderPane>
This is the ViewModel / Controller
#Singleton
public class anyVM implements ViewModel {
private StringProperty lbl = new SimpleStringProperty("27");
public StringProperty getLbl(){
return basementViewKesselTempLbl;
}
public void setLbl(String message){
lbl.set(message);
}
}
In your example code of initialize you are using FXMLLoader which is part of standard JavaFX. However, to load a mvvmFX View with all setup logic you have to use mvvmFX's FluentViewLoader.
The FluentViewLoader uses FXMLLoader internally but does a whole lot more operations like injecting ViewModels into their Views.
So your code should look like this:
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
ViewTuple<AnyView, AnyViewModel> viewTuple = FluentViewLoader.fxmlView(AnyView.class).load();
pane2.setCenter(viewTuple.getView());
}

How can I create my own icon with propertise in JavaFX

Maybe somebody knows the answer and try help me.
I am creating own button.
<fx:root maxHeight="100.0" maxWidth="100.0" minHeight="50.0" minWidth="50.0" prefHeight="80.0" prefWidth="80.0" style="-fx-background-color: red;" type="StackPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" >
<children>
<ImageView fx:id="baseImage" fitHeight="66.0" fitWidth="72.0" pickOnBounds="true" preserveRatio="true" StackPane.alignment="TOP_CENTER" />
<Label fx:id="textBtn" alignment="BOTTOM_LEFT" prefHeight="17.0" prefWidth="75.0" textFill="WHITE" textOverrun="CLIP" StackPane.alignment="BOTTOM_LEFT" />
</children>
</fx:root>
So I need to change my button (Image and Label), when I am creating this in FXML file.
<MyButton layoutX="200.0" layoutY="162.0" />
e.g
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
Can somebody help me ?
My Java Code
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
init();
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public Label getTextBtn() {
return textBtn;
}
public void setTextBtn(Label textBtn) {
this.textBtn = textBtn;
}
public ImageView getBaseImage() {
return baseImage;
}
public void setBaseImage(Image location) {
this.baseImage.setImage(location);
}
public void setButton(Label textBtn, Image location){
this.baseImage.setImage(location);
this.textBtn = textBtn;
}
But I care about icon are changed in FXML file, not JavaCode
}
If you want to set properties in FXML:
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
you must define those properties in the corresponding class. In particular, MyButton must define setImage(...) and setText(...) methods (it already has setLayoutX(...) and setLayoutY(...) which are inherited from StackPane). It's hard to know exactly what functionality you want here, but you probably want to set these up as JavaFX Properties. If the intention is to map these into the Label and ImageView defined in the FXML file, you can just expose the relevant properties from the controls. You might need to work a bit to map the string from the image property into the correct thing.
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
// not sure what this is:
// init();
// note that if you define
// public void initialize() {...}
// it will be called
// automatically during the FXMLLoader.load() method
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public StringPropergty textProperty() {
return textBtn.textProperty();
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
// similarly expose a property for image, but you need to be able to coerce it from a String
}
(Incidentally, I assume this is just an example for the purposes of understanding how to do this. Everything you have in the example can be done using a regular button. Just wanted to make that clear for any others reading this post.)

Resources