Pass value between two JavaFX windows that are setup through fxml? - java

I understand how to pass a value between two forms ala https://www.youtube.com/watch?v=HFAsMWkiLvg
The problem is in the way it is done in the video. (Being static that is). I was unable to get FXMLLoaders to work when inside of a static method because of the usage of the getClass() method. It is non-static only.
getClass().getResource("myFile.fxml")
Here is how I am loading my second form.
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("LoginForm.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("HWI - Login");
stage.setResizable(false);
stage.setScene(new Scene(root1));
stage.showAndWait();
} catch (Exception e) {
e.printStackTrace();
}
Inside of scenebuilder I am setting the method to be run where it's essentially checking against a DB for a username/password. All of this is done within my loginController class. Once successful it does this. (Above I have my #FXML hook declared for loginButton)
Stage stage = (Stage) loginButton.getScene().getWindow();
stage.close();
The current way I have the program setup is that all of the menus are set to disabled before the user is signed in. I have a non-static method already setup to re-enable everything but I'm unable to call it because I can't bridge the gap between static / non-static before closing my 2nd window.
Thanks,

The generally accepted way to share data between two or more distinct parts of the UI is to use a MVC/MVP type approach. In these patterns, you encapsulate the data in a "model" (the "M" in either) in a way that it can be observed and notifications sent if it changes. Then one part of the UI can update the model, and other parts of the UI that are observing it will be notified if it changes.
JavaFX makes this particularly easy by implementing observable properties for you, which just wrap values and make it easy to add listeners that are notified if they change.
So in this example you might do something like:
public class AuthenticationState {
private final BooleanProperty loggedIn = new SimpleBooleanProperty(false);
public BooleanProperty loggedInProperty() {
return loggedIn ;
}
public final boolean isLoggedIn() {
return loggedInProperty().get();
}
public final void setLoggedIn(boolean loggedIn) {
loggedInProperty().set(loggedIn);
}
private final StringProperty userName = new SimpleStringProperty();
public StringProperty userNameProperty() {
return userName ;
}
public final String getUserName() {
return userNameProperty().get();
}
public final void setUserName(String userName) {
userNameProperty().set(userName);
}
// other properties as needed, e.g. IntegerProperty logInAttempts , etc.
}
Now your main controller can do:
public class MainController {
#FXML
private final MenuItem deleteAllDataMenuItem ;
private AuthenticationState authenticationState ;
public void initialize() {
authenticationState = new AuthenticationState();
deleteAllDataMenuItem.disableProperty()
.bind(authenticationState.loggedInProperty().not());
}
#FXML
public void logIn() {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("LoginForm.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
LoginController loginController = fxmlLoader.getController();
loginController.setAuthenticationState(authenticationState);
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("HWI - Login");
stage.setResizable(false);
stage.setScene(new Scene(root1));
stage.showAndWait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
and your login controller can look like:
public class LoginController {
private AuthenticationState authenticationState ;
public void setAuthenticationState(AuthenticationState authenticationState) {
this.authenticationState = authenticationState ;
}
#FXML
public void login() {
// check login:
boolean loginSuccessful = ... ;
authenticationState.setLoggedIn(loginSuccessful);
// ...
}
}
Now when the user logs in, the login controller sets the loggedIn property in the authenticationState to true. Since the disabled state of the menu item is bound to (the negative of) the loggedIn property, the menu item is automatically enabled. If you have a "Log Out" button, just have it set the loggedIn property to false, and the menu item will be disabled again.

Related

How i can get selection model from one method to another metod from other class with Table View? [duplicate]

This question already has answers here:
Passing Parameters JavaFX FXML
(10 answers)
Closed 9 months ago.
I want to get one selected model with name, author,key_words to next window. Where it will be in tex fields. After i changing this, i want to save it in database by SQL Update command
public void displaySelected(ActionEvent mouseEvent) {
ModelTable selection = Table_View.getSelectionModel().getSelectedItem();
if (selection == null){
System.out.println("not selected");
}else {
String SelectedAuthor = selection.getAuthor();
String SelectedName = selection.getName();
String SelectionWord = selection.getWords();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("Admin_Change.fxml"));
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
Change_Add_Button.getScene().getWindow().hide();
Parent root = loader.getRoot();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.show();
System.out.println("Change display");
}
}
I want to set text field from selected item to new page to
Controller_Admin_Change
#FXML
private TextField Change_AdminEdit_Author;
#FXML
private TextField Change_AdminEdit_Name;
#FXML
private TextField Change_AdminEdit_Word;
Maybe its common mistake of OOP, but idk how to find a solution
Thanks for advice
Selected item what i what to insert
From this
to this fields
To this
You can get a reference to the controller via method getController in class javafx.fxml.FXMLLoader. Then you can invoke methods of the controller class.
Add a method for setting the text of Change_AdminEdit_Author, in the controller class:
public void setAuthor(String text) {
Change_AdminEdit_Author.setText(text);
}
Call the method from displaySelected method:
(Note I assume that your controller class is named Controller_Admin_Change)
Controller_Admin_Change controller = loader.getController();
controller.setAuthor(SelectedAuthor);
It is recommended to adhere to Java naming conventions. The below code is the same as the above code but the names are according to the conventions.
public void setAuthor(String text) {
changeAdminEditAuthor.setText(text);
}
String selectedAuthor = selection.getAuthor();
ControllerAdminChange controller = loader.getController();
controller.setAuthor(selectedAuthor);

JavaFx add text from scene 1 to ListView in second scene

I am trying to make a Javafx program that will take the user input from scene one, and show it in a ListView in scene two when a button is pressed. Also, the user can go back to the scene one and add another input, and while in scene two the user can remove one of the inputs inside the listview. I have the following code, but for some reason instead of adding each new input underneath the previous one, it just overwrites the first input. Can you help me figure it out? Thanks!
In scene one I have the following code
public class Controller {
#FXML
private TextField userEmail;
#FXML
public void handleRegisterButton(ActionEvent event) throws IOException{
Data data = Data.getInstance();
data.setEmailAddress(userEmail.getText());
Parent viewEmailsParent = FXMLLoader.load(getClass().getResource("../view/viewUserEmails.fxml"));
Scene viewEmailsScene = new Scene(viewEmailsParent);
Stage window = (Stage) ((Node)event.getSource()).getScene().getWindow();
window.setScene(viewEmailsScene);
window.show();
}
}
And in scene two, where I'm trying to handle most of it, I have this code:
public class secondController implements Initializable {
ObservableList<String> listOfEmails;
#FXML
ListView<String> emailList = new ListView<>();
#FXML
public void handleBackButton(ActionEvent event) throws IOException {
Parent viewEmailsParent = FXMLLoader.load(getClass().getResource("../view/register.fxml"));
Scene viewEmailsScene = new Scene(viewEmailsParent);
Stage window = (Stage) ((Node)event.getSource()).getScene().getWindow();
window.setScene(viewEmailsScene);
window.show();
}
#FXML
public void handleDeleteButton(ActionEvent event) throws IOException{
String selected = emailList.getSelectionModel().getSelectedItem();
listOfEmails.remove(selected);
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
listOfEmails = FXCollections.observableArrayList(Data.getInstance().getEmailAddress());
emailList.setItems(listOfEmails);
}
}
If it helps, this is the data class
public class Data {
public static Data emailStorage;
private String emailAddress;
private Data(){
}
public static Data getInstance(){
if(emailStorage == null){
emailStorage = new Data();
}
return emailStorage;
}
public String getEmailAddress(){
return emailAddress;
}
public void setEmailAddress(String emailAddress){
this.emailAddress = emailAddress;
}
}
Because you reload the FXML file viewUserEmails.fxml every time, a new ListView and observable list is created every time, just displaying the single item in your data model class.
So your data model class should contain the complete list, not just the single item that was most recently added:
public class Data {
public static Data emailStorage;
private ObservableList<String> emailAddresses;
private Data(){
}
public static Data getInstance(){
if(emailStorage == null){
emailStorage = new Data();
}
return emailStorage;
}
public ObservableList<String> getEmailAddresses(){
return emailAddress;
}
}
Now you can do:
public class SecondController implements Initializable {
// ...
#FXML
ListView<String> emailList ;
// ...
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
listOfEmails = FXCollections.observableArrayList(Data.getInstance().getEmailAddresses());
emailList.setItems(listOfEmails);
}
}
and
public class Controller {
#FXML
private TextField userEmail;
#FXML
public void handleRegisterButton(ActionEvent event) throws IOException{
Data data = Data.getInstance();
data.getEmailAddresses().add(userEmail.getText());
// ...
}
}
You can also consider modifying the code so that viewUserEmails.fxml is loaded only once, and redisplayed. The code above will still work with that modification.
Note there are a ton of other errors in your code, unrelated to the actual question:
You should never initialize fields annotated #FXML. Note I replaced
#FXML private ListView<String> emailList = new ListView<>();
with
#FXML private ListView<String> emailList ;
If this gives you null pointer exceptions, something else is wrong
Your resource paths are wrong. They will not work if you bundle this as a jar file. See How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application?
Using a singleton for your data model is a bad idea. (Using the singleton anti-pattern in general is a bad idea.) Instead, it's better to create an instance of your data model and pass it to the controllers that need it. See Passing Parameters JavaFX FXML
Stick to Java naming conventions. It will make your code easier for other programmers to read, and aid syntax-highlighting tools to properly interpret your code.

Editing values/properties of an object of the controller class from another class

I have tried following the solution from here but without success: JavaFX Change label text from another class with controller
I am not sure if they want the same as I do.
So basically what I have is: FXMLDocumentController.java, FXMLDocument.xml, Operations.java, and Main.java. (I have some other classes that make the Arduino connection)
This is the start method that I have in my Main.java:
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("This is the title");
primaryStage.setScene(scene);
primaryStage.show();
}
EDIT:
Here's my Operations.java:
public class Operations {
private String mensagem, hora, dados;
private String [] msgSeparada, dadosSeparados;
private int origem, destino, tipoMensagem, comprimento;
private int [] estadoDosSensores;
public FiltrarMensagem(String mensagem) {
//remove o primeiro e ultimo carater
mensagem = mensagem.substring(1, mensagem.length()-2);
this.mensagem = mensagem;
System.out.printf("Mensagem Recebida: %s\n", mensagem);
msgSeparada = this.mensagem.split(";");
destino = Integer.valueOf(msgSeparada[0]);
origem = Integer.valueOf(msgSeparada[1]);
hora = msgSeparada[2];
tipoMensagem = Integer.valueOf(msgSeparada[3]);
comprimento = Integer.valueOf(msgSeparada[4]);
dados = msgSeparada[5];
dadosSeparados = dados.split(",");
}
public void imprimir() {
System.out.printf("Origem: %d\n", origem);
System.out.printf("Destino: %d\n", destino);
System.out.printf("Hora: %s\n", hora);
System.out.printf("Tipo de Mensagem: %d\n", tipoMensagem);
System.out.printf("Comprimento: %d\n", comprimento);
System.out.printf("Dados: %s\n\n", dados);
if(Integer.valueOf(dadosSeparados[0]) == 1) {
//change label value here
}
}
}
To simplify, here's what my program does:
I have my controller class with 2 simple buttons that receive data from the serial port coming from an Arduino, and with the data received from the Arduino, I create an object of the class Operations so I can make the necessary changes depending on the data received from the Arduino, and what I would like to do is to change labels and all the objects available at the FXML file, but I am not able to do that. What is the simplest way to do it?
I've tried everything and with no success... So would really appreciate if someone could help me on this.
Simple solution for easy case
If you're instantiating your "other class" in response to a button press, i.e. in the controller, all you need to do is pass the new object a reference to the controller.
I.e.
public class Controller {
#FXML
private Label label ;
public void showMessage(String message) {
label.setText(message);
}
#FXML
private void handleButtonPress(ActionEvent event) {
Operations ops = new Operations(this);
ops.doYourThing();
}
}
and then
public class Operations {
private final Controller controller ;
public Operations(Controller controller) {
this.controller = controller ;
}
public void doYourThing() {
// ...
String someMessage = ... ;
controller.showMessage(someMessage);
// ...
}
}
MVC approach
A slightly more general and robust solution is to use a MVC-approach, and create a model class. Your model class can use an observable StringProperty to keep the text to display. Share the model instance with the controller and with the service class (Operations). The controller can observe the property, so it can update the label whenever the property changes, and the service can update the property. This looks something like this:
public class Model {
private final StringProperty message = new SimpleStringProperty();
public StringProperty messageProperty() P{
return message ;
}
public final String getMessage() {
return messageProperty().get();
}
public final void setMessage(String message) {
messageProperty().set(message);
}
}
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
Model model = new Model();
controller.setModel(model);
Scene scene = new Scene(root);
primaryStage.setTitle("This is the title");
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class Controller {
#FXML
private Label label ;
private Model model ;
public void setModel(Model model) {
this.model = model ;
model.messageProperty().addListener((obs, oldMessage, newMessage) ->
label.setText(newMessage));
}
#FXML
private void handleButtonPress(ActionEvent event) {
Operations ops = new Operations(model);
ops.doYourThing();
}
}
And finally
public class Operations {
private final Model model ;
public Operations(Model model) {
this.model = model ;
}
public void doYourThing() {
// ...
String someMessage = ... ;
model.setMessage(message);
// ...
}
}
The benefits to this (slightly more complex) approach are:
You remove any coupling between your "service" class and the controller, so the service is really independent of the UI
This now works if the service is created elsewhere, as you have access to the model in a wider scope (it's created in the Main class, which is the entry point to the application).

Data is not being inputted into TableView with CellValueFactory in JavaFX using Scenebuilder

I have looked at other similar problems on this website, however none of the solutions seem to apply to me. I have already checked the flagged answer and updated the comments, however the program is still not working.
I am trying to make a Scheduler Application. I am running into a problem with adding data into my TableView in JavaFX. This is the main program below:
public class SchedulerController implements Initializable {
/**
* Initializes the controller class.
*/
//configures the table
#FXML
private TableView<ScheduleTask> tableView;
#FXML
private TableColumn<ScheduleTask, String> timeColumn;
#FXML
private TableColumn<ScheduleTask, String> descriptionColumn;
#Override
public void initialize(URL url, ResourceBundle rb) {
// this sets up the columns in the tableview
timeColumn.setCellValueFactory(new PropertyValueFactory<ScheduleTask, String>("scheduleTime"));
descriptionColumn.setCellValueFactory(new PropertyValueFactory<ScheduleTask, String>("scheduleName"));
}
public void initData(Object[] tasks) {
TodoTask t;
//iterate around array of tasks adding to the scheduler
for (int i = 0; i < tasks.length; i++) {
t = (TodoTask) tasks[i];
t.getDueDate();
t.getNumHours();
int hoursForTask = Integer.parseInt(t.getNumHours());
String nameofScheduleTask = (t.getTodoName());
LocalDate today = LocalDate.now();
LocalDate futureDue = t.getDueDate();
double daystoDue = ChronoUnit.DAYS.between(today, futureDue);
double timeForTask = hoursForTask / daystoDue;
String timeForTaskString = Double.toString(timeForTask);
//**This is the main part where I input the data into the Scheduler, and
//nothing comes up**
ScheduleTask newScheduleTask = new ScheduleTask(t.getTodoName(),t.getNumHours());
tableView.getItems().add(newScheduleTask);
}
}
I have commented on the part where I think there is a problem. The above program is the Controller for the Scheduler Screen, and the below code links the Scheduler screen to the other screen where the data is inputted, when the user presses 'Change Screen' on my program:
public void changeScreen(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"Scheduler.fxml"
));
Parent root = (Parent) loader.load();
Parent schedulerParent = FXMLLoader.load(getClass().getResource("Scheduler.fxml"));
Scene schedulerScene = new Scene(schedulerParent);
Stage window = (Stage) ((Node) event.getSource()).getScene().getWindow();
window.setScene(schedulerScene);
SchedulerController newcontroller =
loader.<SchedulerController>getController();
ObservableList ol=todoView.getItems();
Object[] ob=ol.toArray();
newcontroller.initData(todoView.getItems().toArray());
window.show();
}
I have tried to pass the objects through an array to the program above, however whenever I add a new Task and change screen to the Scheduler, there is no task added. I also already have added getters and setters in other classes, and there seem to be no problems with that. I have attached it below:
public class ScheduleTask {
private SimpleStringProperty scheduleName,scheduleTime;
public ScheduleTask(String scheduleName, String scheduleTime) {
this.scheduleName = new SimpleStringProperty (scheduleName);
this.scheduleTime = new SimpleStringProperty(scheduleTime);
}
public String getScheduleName() {
return scheduleName.get();
}
public void setScheduleName(SimpleStringProperty scheduleName) {
this.scheduleName = scheduleName;
}
public String getScheduleTime() {
return scheduleTime.get();
}
public void setScheduleTime(SimpleStringProperty scheduleTime) {
this.scheduleTime = scheduleTime;
}
}
I am not too experienced with the website, so please let me know if you need further clarification or if anything is unclear. Thank you.

Is there a "best" way to invoke a class method from a static method?

I have multiple controllers, each associated to a different FXML file. There is an event in one node that requires synchronization across other nodes, so I decided to do this with another event, and event handlers in the various controller files.
To register the event handlers requires the event handler method to be static (i.e., addEventHandler(SomeEvent, ClassName::MethodName).
So, the controller looks something like...
public class MyController {
private static MyController selfRef = null;
public MyController() {
selfRef = this;
}
public static void someEventHandler(Event event) {
if (selfRef != null) {
selfRef.doSomethingUseful();
}
}
private void doSomethingUseful() { /* synch the nodes */ }
}
This works, but seems a bit of a hack. Is there a preferred mechanism to achieve the same end result?
You might have more flexibility with this if you get rid of all the static stuff and make the event handler a member of your controller class as demonstrated below.
Sample implementation without static members
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.*;
import java.io.IOException;
class CustomerDialogController {
#FXML
private Label customerName;
private EventHandler<Event> customEventHandler = event -> {
// handle the event...
};
void initData(Customer customer) {
customerName.setText(customer.getName());
}
public EventHandler<Event> getCustomEventHandler() {
return customEventHandler;
}
}
public class EventHandling {
public Stage showCustomerDialog(Customer customer) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("customerDialog.fxml"));
Stage stage = new Stage(StageStyle.DECORATED);
stage.setScene(new Scene(loader.load()));
CustomerDialogController controller = loader.getController();
controller.initData(customer);
stage.addEventHandler(Event.ANY, controller.getCustomEventHandler());
stage.show();
return stage;
}
}
class Customer {
private String name;
Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Notes on implementation options
In the example the event handler has been added to the stage, but it could equally have been added to any scene or node or anything that has the ability to handle events.
If desired, you could also add a setter for the event handler to allow changing of the event handling logic externally.
In addition to the setup above you might wish to have the controller self-register the event handler in it's initialize method. Whether you do so or not just depends on whether you want the ability to register event handlers exposed outside the controller or if you want to use encapsulation to hide all of the event handling logic local to the controller.
Notes on (perhaps superior) alternatives
As an alternate approach, rather than using the event handling system within JavaFX for your custom approach, you could make use of a third party system such as the Google Guava Event Bus.
You should also consider why you need to add custom event handling to your application. JavaFX supports very flexible binding and observer patterns. By exposing properties of your model objects as observable, it is often not necessary to have custom events. Often, your view controllers can observe any changes to associated model objects and modify the internal state of model objects based upon UI interactions. This is especially the case if you introduce a dependency injection based system for injecting models into your controllers, such as Guice, Spring, afterburner.fx or Gluon Ignite.
Maybe you could use some kind of registry, which takes care of the synchronisation. Here is a quick and dirty example:
public class Synchronizer {
private ObservableList<Node> nodes;
private boolean isSyncing;
public Synchronizer() {
nodes = FXCollections.observableArrayList();
}
public void addNode(Node node) {
nodes.add(node);
}
public void sync(Node sourceNode, Event event) {
if (isSyncing) {
return;
}
isSyncing = true;
for (Node node : nodes) {
if (node != sourceNode) {
node.fireEvent(event);
}
}
isSyncing = false;
}
}
In your Controller you can add the node, whose event you like to get synchronized, to the synchronizer, and call sync() in the eventListener.
public class Controller {
private StackPane root;
private Button button;
public Controller(Synchronizer synchronizer) {
button = new Button();
button.setOnAction(evt -> {
synchronizer.sync(button, evt);
//action
});
synchronizer.addNode(button);
root = new StackPane(button);
}
}
EDIT:
This should make for a cleaner version:
public class Starter extends Application {
#Override
public void start(Stage primaryStage) {
ViewController controller1 = new ViewController();
ViewController controller2 = new ViewController();
Synchronizer synchronizer = new Synchronizer();
synchronizer.add(controller1);
synchronizer.add(controller2);
VBox box = new VBox(controller1.root, controller2.root);
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public interface SyncTarget {
Node getSyncNode();
void triggerAction();
}
public class Synchronizer {
private ObservableList<SyncTarget> syncTargets;
private EventHandler<Event> eventHandler;
public Synchronizer() {
syncTargets = FXCollections.observableArrayList();
eventHandler = e -> sync();
}
public void add(SyncTarget target) {
syncTargets.add(target);
target.getSyncNode().addEventHandler(ActionEvent.ANY, eventHandler);
}
public void remove(SyncTarget target) {
syncTargets.remove(target);
target.getSyncNode().removeEventHandler(ActionEvent.ANY, eventHandler);
}
public void sync() {
for (SyncTarget target : syncTargets) {
target.triggerAction();
}
}
}
public class ViewController implements SyncTarget {
private StackPane root;
private Button button;
public ViewController() {
button = new Button();
root = new StackPane(button);
}
#Override
public Node getSyncNode() {
return button;
}
#Override
public void triggerAction() {
//action
}
}
}

Categories

Resources