I have my UIController class and a ButtonMethods class. The ButtonMethods class contains all the code for the button actions. I need to be able to use the buttons from the controller class in the ButtonMethods class. For example, I have these defined in the controller class
ButtonMethods button = new ButtonMethods();
#FXML Button buttonLockdown;
#FXML Button buttonRelease;
And for example, the buttonLockdown has an ActionEvent when clicked
#FXML
private void actionLockdown(ActionEvent event) {
button.lockdown();
Ideally, I want the ButtonMethods to do this:
public void lockdown() {
buttonLockdown.setDisable(true);
onLockdown = true;
buttonRelease.setDisable(false);
I can't just put that code into the action event for various reasons, and putting the button objects into the parameters would get too messy with what I'm trying to do. So how can I get FXML objects into the button class?
Try to send the UIController itself as parameter:
private UIController thisController;
#Override
public void initialize(URL url, ResourceBundle rb) {
thisController = this;
}
#FXML
private void actionLockdown(ActionEvent event) {
button.lockdown(thisController);
then
public void lockdown(UIController controller) {
controller.getButtonLockdown().setDisable(true);
onLockdown = true;
controller.getButtonRelease().setDisable(false);
You can also use bindings in appropriate situations.
Related
I have 2 scenes :
The first one has a "Balance" Label, which displays the balance from a variable.
The second scene is the deposit scene where the user adds to the balance.
(Each scene has its controller class)
I want the balance to be updated when the user goes back to the first scene.
what's the best way to do so? I couldn't find an event for the scene shown, I found online only a stage example which triggers an event when the window is closed, but here I am just changing scenes by changing the mainstage scene.
I have tried making an object of the first scene class inside the second scene's class and calling a method to change the label text when I click the back button but that didn't work.
Here's the code for the first scene where lbBalance is the label I want to update, and updateBal is the method I am using in the second scene class.
public class accountController extends Controller implements Initializable {
#FXML private Label gilbert;
#FXML private Label lbBalance;
#FXML private Button deposit;
#FXML private Button btn_showBalance;
private application.depositController depositController;
#Override
public void initialize(URL location, ResourceBundle resources) {
lbBalance.setText(String.valueOf(BAL));
}
#FXML
public void handleDeposit(ActionEvent event) throws IOException {
Parent depositParent = FXMLLoader.load(getClass().getResource("deposit.fxml"));
depositScene = new Scene(depositParent);
mainStage.setScene(depositScene);
mainStage.show();
}
public void updateBal() {
lbBalance.setText(String.valueOf(BAL));
}
}
Here's the second scene's class
accountController backtoscene= new accountController();
#FXML private Label info;
#FXML private Button btn_depositfinal;
#FXML private TextField depositamount;
#FXML private Button btn_back;
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
}
#FXML
public void handleDepositFinal(ActionEvent event) {
deposit(Integer.parseInt(depositamount.getText()));
info.setVisible(true);
}
#FXML
public void handleBackButton(ActionEvent event) throws IOException {
backtoscene.updateBal();
mainStage.setScene(newscene);
}
TL;DR calling the method is giving me a nullPointerException, is there any other way to update the balance label when getting back to previous scene?
NOTE: I haven't tested the code, I just wrote it freehand, but it gives you a general idea.
Your main issue is that you are creating a new AccountController in the DepositController. Meaning it's a different one than the one you originally instantiated.
public class AccountController extends Controller implements Initializable {
#FXML private Label gilbert;
#FXML private Label lbBalance;
#FXML private Button deposit;
#FXML private Button btn_showBalance;
private application.DepositController depositController;
#Override
public void initialize(URL location, ResourceBundle resources) {
lbBalance.setText(String.valueOf(BAL));
}
#FXML
public void handleDeposit(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("deposit.fxml"));
Parent depositParent = loader.load();
depositScene = new Scene(depositParent);
depositController = loader.getController();
depositController .setAccountController(this);
mainStage.setScene(depositScene);
mainStage.show();
}
public void updateBal() {
lbBalance.setText(String.valueOf(BAL));
}
}
Here's the second class where you need to set the AccountController to be the one you originally initialized :
public class DepositController extends Controller implements Initializable {
AccountController backtoscene;
#FXML private Label info;
#FXML private Button btn_depositfinal;
#FXML private TextField depositamount;
#FXML private Button btn_back;
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
}
public void setAccountController(AccountController controller){
backtoscene = controller;
}
#FXML
public void handleDepositFinal(ActionEvent event) {
deposit(Integer.parseInt(depositamount.getText()));
info.setVisible(true);
}
#FXML
public void handleBackButton(ActionEvent event) throws IOException {
backtoscene.updateBal();
mainStage.setScene(newscene);
}
}
Now you have access to the AccountController you originally initialized at the start, and the AccountController has access to the correct DepositController.
Alright, I have been reading a lot of stackoverflow posts about how to PROPERLY create a JavaFX application with two windows that share data between them I can't find any clear solution. This is my current solution, basically creating a reference to the other controller which is really bad practice.
Would it be good practice to create a Model that is shared between the two controllers? If so how would I inject the Model object into the controllers when they dont have any constructors?
public class ControllerOne implements Initializable {
#FXML private TextField textField;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
private void handleButtonAction(ActionEvent event) {
if(textField.getText().equals("")){
}
else{
System.out.println(textField.getText());
String itemNumber = textField.getText();
Main.getControllerTwo().setLabel(itemNumber);
textField.setText("");
textField.requestFocus();
}
}
}
Second window
public class ControllerTwo implements Initializable {
#FXML
private Label itemLabel;
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
}
public void setLabel(String itemNumber){
itemLabel.setText(itemNumber);
}
public Label getLabel(){
return itemLabel;
}
}
This is my code, some parts have been omitted (such as imports, psvm, etc.):
public class Proyect extends Application implements EventHandler{
private HBox menu1() {
Image food1 = new Image (getClass().getResourceAsStream("clipboard.png"));
Button btnR = new Button ("R", new ImageView(food1));
}
#Override
public void handle(Event event) {
if(event.getSource() == btnR) {
}
}
}
The problem is my IDE says I'm mistaken in the "btnR" thing inside my if statement (it's underlined in red).
Button btnR is defined within the method menu1(). Hence it won't be accessible within the method handle(). Why don't you define it within the class as a private data member?
Please use the logic as mentioned below:
public class Proyect extends Application implements EventHandler{
private Button btnR;
private HBox menu1() {
Image food1 = new Image (getClass().getResourceAsStream("clipboard.png"));
btnR = new Button ("R", new ImageView(food1));
}
#Override
public void handle(Event event) {
if(event.getSource() == btnR) {
}
}
}
I have a program with this window structure:
Where left side has buttons and green zone is different windows that change where user clic on a left button.
I have a main class (call Estructura) thats acts as control for main window except green panel. For green panel, I inject (thanks to afterburner.fx DI) correspond panel base on user button selection from left side. Injected panel has its own controller that is lazy thanks to DI Framework base on MVP.
public class EstructuraPresenter implements Initializable {
private static final Logger LOG = getLogger(EstructuraPresenter.class.getName());
#FXML
ToggleButton btnPlantillas, btnAlumnos, btnEstadisticas;
#FXML
BorderPane pEstructura; //Correspond to all window except green zone
#FXML
StackPane pContenedor; //Green zone
//Injection of all child windows that i want to show in green zone
#Inject
private AlumnosView alumnosview;
#Inject
private PlantillasView plantillasview;
#Inject
private EstadisticasView estadisticasview;
#Override
public void initialize(URL url, ResourceBundle rb) {
//I set up green zone with alumnos panel so I call method whit request section to load
cambiarSeccion("Alumnos");
btnPlantillas.setOnAction((ActionEvent event) -> {
cambiarSeccion("Plantillas");
});
btnAlumnos.setOnAction((ActionEvent event) -> {
cambiarSeccion("Alumnos");
});
btnEstadisticas.setOnAction((ActionEvent event) -> {
cambiarSeccion("Estadisticas");
});
}
//I pass to this method, name of window/panel that I like to load in green zone
public void cambiarSeccion(String nombreVentana) {
try {
//First, reset all buttons (so when user select an option and enter in case, I select option button making like effect as a selected)
btnAlumnos.setSelected(false);
btnPlantillas.setSelected(false);
btnEstadisticas.setSelected(false);
switch (nombreVentana) {
case "Alumnos":
if (btnAlumnos.isSelected() == false) {
sepTitulo.setVisible(true);
lbTitulo.setText("Alumnos");
btnAlumnos.setSelected(true);
//I need to do this check because for first time program load because green zone hasn't got any previous panel load
if (pContenedor.getChildren().contains(alumnosview.getView())) {
pContenedor.getChildren().clear();
alumnosview.getViewAsync(pContenedor.getChildren()::add);
} else {
pContenedor.getChildren().add(alumnosview.getView());
}
}
break;
case "Plantillas":
if (btnPlantillas.isSelected() == false) {
sepTitulo.setVisible(true);
lbTitulo.setText("Plantillas");
btnPlantillas.setSelected(true);
pContenedor.getChildren().clear();
plantillasview.getViewAsync(pContenedor.getChildren()::add);
}
break;
case "Estadisticas":
if (btnEstadisticas.isSelected() == false) {
sepTitulo.setVisible(true);
lbTitulo.setText("Estadísticas");
btnEstadisticas.setSelected(true);
pContenedor.getChildren().clear();
estadisticasview.getViewAsync(pContenedor.getChildren()::add);
}
break;
//There are more cases but I use same configuration like above examples...
}
}
catch (Exception e) {
LOG.log(Level.SEVERE, e.toString());
new Dialogos().mostrarExcepcion(null, e);
}
}
}
Well, when user clic on left button, panel is load ok in green zone. But problem I have is where user for example in green zone clic on a button/link... to load another panel (similiar to above description) but in that case I need to call from green panel controller to method cambiarSeccion() that is in EstructuraPresenter controller.
So for example, on case of user select estadisticas button (i hide in image, sorry I tried to simplified), that's is estadisticaspresenter controller:
public class EstadisticasPresenter implements Initializable {
private static final Logger LOG = getLogger(EstadisticasPresenter.class.getName());
#FXML
Hyperlink linkTotalAlumnos;
#Inject
private EstructuraView estructuraview;
#Override
public void initialize(URL url, ResourceBundle rb) {
linkTotalAlumnos.setOnAction((ActionEvent event) -> {
//This call apparently works, I debug and call to method cambiarSeccion happend but screen isn't update
((EstructuraPresenter) estructuraview.getPresenter()).cambiarSeccion("Alumnos");
});
}
}
Where estadisticas window is loaded If I click on left buttons, works but If I clic on green load panel nothing happend. I resume problem in next image:
In my opinion your approach has one problem: When you call in any of your children presenters
((EstructuraPresenter) estructuraview.getPresenter())
.cambiarSeccion("Alumnos");
estructuraview.getPresenter() is actually creating a new instance of the main presenter.
This means that EstructuraPresenter.initialize() is being called again when you click on the hyperlink, and you have two calls to cambiarSeccion() (one from Initialize, one from the action), that add the children to the new instance, not to the old one, which is the one you see. That's why you don't see any change!
I'd suggest a different approach: Let the main presenter listen to changes in some properties on their childrens.
For example, add a boolean property to notify a click on the hyperlink:
public class EstadisticasPresenter implements Initializable {
#FXML Hyperlink linkTotalAlumnos;
private final BooleanProperty link = new SimpleBooleanProperty();
public boolean isLink() { return link.get(); }
public void setLink(boolean value) { link.set(value); }
public BooleanProperty linkProperty() { return link; }
#Override public void initialize(URL url, ResourceBundle rb) {
linkTotalAlumnos.setOnAction((ActionEvent event) -> {
link.set(true);
});
}
}
while on the main presenter:
#Override
public void initialize(URL url, ResourceBundle rb) {
...
EstadisticasPresenter estadisticas =
(EstadisticasPresenter)estadisticasview.getPresenter();
estadisticas.linkProperty().addListener((ob,b,b1)->{
if(b1){
cambiarSeccion("Alumnos");
// reset link property
estadisticas.setLink(false);
}
});
}
As a side note, instead of clear() and add, I'd use pContenedor.getChildren().setAll(<view>); since you only show one child at a time.
Base on #josé-pereda answer, finally I can link button/hyperlink from sub-window (green area) to main controller. The advantage of my solution is that I avoid use of boolean variables.
In EstructuraPresenter controller I add listeners to buttons from EstadisticasPresenter(which is the controller from one of the sub-window):
public class EstructuraPresenter implements Initializable {
private static final Logger LOG = getLogger(EstructuraPresenter.class.getName());
#FXML
ToggleButton btnAlumnos, btnEstadisticas;
#FXML
StackPane pContenedor;
#Inject
private AlumnosView alumnosview;
#Inject
private EstadisticasView estadisticasview;
#Inject
private DataModel datos;
#Override
public void initialize(URL url, ResourceBundle rb) {
((EstadisticasPresenter) estadisticasview.getPresenter()).getLinkTotalAlumnos().setOnAction((ActionEvent event) -> {
this.cambiarSeccion("Alumnos");
});
//This call is from first time, where program starts to show by default Alumnos sub-window
cambiarSeccion("Alumnos");
btnAlumnos.setOnAction((ActionEvent event) -> {
cambiarSeccion("Alumnos");
});
btnEstadisticas.setOnAction((ActionEvent event) -> {
cambiarSeccion("Estadisticas");
});
}
public void cambiarSeccion(String nombreVentana) {
try {
btnAlumnos.setSelected(false);
btnEstadisticas.setSelected(false);
switch (nombreVentana) {
case "Alumnos":
if (btnAlumnos.isSelected() == false) {
sepTitulo.setVisible(true);
lbTitulo.setText("Alumnos");
btnAlumnos.setSelected(true);
alumnosview.getViewAsync(pContenedor.getChildren()::setAll);
pContenedor.getChildren().setAll(alumnosview.getView());
}
break;
case "Estadisticas":
if (btnEstadisticas.isSelected() == false) {
sepTitulo.setVisible(true);
lbTitulo.setText("Estadísticas");
btnEstadisticas.setSelected(true);
estadisticasview.getViewAsync(pContenedor.getChildren()::setAll);
}
break;
}
}
catch (Exception e) {
LOG.log(Level.SEVERE, e.toString());
new Dialogos().mostrarExcepcion(null, e);
}
}
}
NOTE In addition, I change getChildren().clear() && getChildren().add() to getChildren().setAll(); These prevents re-initializate controllers from each window that I put in green area.
estadisticasview.getViewAsync(pContenedor.getChildren()::setAll);
And in EstadisticasPresenter controller, I add getters from that controls that I want to user clic to go to another window (so when user clic on control, green area change with another window/pane).
public class EstadisticasPresenter implements Initializable {
private static final Logger LOG = getLogger(EstadisticasPresenter.class.getName());
#FXML
Hyperlink linkTotalAlumnos;
#Inject
private DataModel datamodel;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
//Getters that I use in EstructuraPresenter controller to set up event handlers
public Hyperlink getLinkTotalAlumnos() {
return linkTotalAlumnos;
}
}
In my javafx Application we have two FXML files first.fxml and second.fxml, same firstController.java and secondController.java now the main problem is first.fxml contain TextField name and on Button
when user will click on that button second.fxml display in second.fxml I have one ComboBox and one Button when user click second.fxml button I want to set that combobox value to first.fxml name TextField.
I am finding solution on Google from last three days but didn't get proper solution. In Java swing I was doing this using static public field that allowed me to access JFrame from another JFrame.
Eagerly waiting for helpful reply.
Expose a StringProperty from your SecondController. When the button is pressed, set its value:
public class SecondController {
private final StringProperty selectedValue = new SimpleStringProperty(this, "selectedValue", "");
public final StringProperty selectedValueProperty() {
return selectedValue ;
}
public final void setSelectedValue(String value) {
selectedValue.set(value);
}
public final String getSelectedValue() {
return selectedValue.get();
}
#FXML
private final ComboBox<String> comboBox ;
#FXML
private void handleButtonPress() {
selectedValue.set(comboBox.getValue());
}
}
In your FirstController, provide a method for setting the text:
public class FirstController {
#FXML
private TextField textField ;
public void setText(String text) {
textField.setText(text);
}
}
Now when you load the FXML files, just observe the property in the SecondController and call the method in FirstController when it changes:
FXMLLoader firstLoader = new FXMLLoader(getClass().getResource("first.fxml"));
Parent first = firstLoader.load();
FirstController firstController = firstLoader.getController();
FXMLLoader secondLoader = new FXMLLoader(getClass().getResource("second.fxml"));
Parent second = secondLoader.load();
SecondController secondController = secondLoader.getController();
secondController.selectedValueProperty().addListener((obs, oldValue, newValue) ->
firstController.setText(newValue));