How to use TestFX to test a scene's contents using JavaFXML - java

I'm wondering how I'm supposed to be testing contents of certain scenes in JavaFXML when using TestFX.
Examples include these links:
https://github.com/TestFX/TestFX/blob/master/subprojects/testfx-junit5/src/test/java/org/testfx/framework/junit5/ApplicationRuleTest.java
https://medium.com/#mglover/java-fx-testing-with-testfx-c3858b571320
The first link constructs the scene within the test class, and the latter uses a pre-defined scene stored in its own class.
How am I supposed to do something similar to this when using JavaFXML instead of JavaFX where the scenes' structures are defined in an fxml file instead of java code?

First step is giving your components fx:id-s in your fxml files, and then something like:
public class ChangeProfilesMenuControllerTest extends ApplicationTest {
Pane mainroot;
Stage mainstage;
#Override
public void start(Stage stage) throws IOException {
mainroot = (Pane) FXMLLoader.load(Main.class.getResource("ChangeProfilesMenU.fxml"));
mainstage = stage;
stage.setScene(new Scene(mainroot));
stage.show();
stage.toFront();
}
#Before
public void setUp() throws Exception{
}
#After
public void tearDown () throws Exception {
FxToolkit.hideStage();
release(new KeyCode[]{});
release(new MouseButton[]{});
}
#Test
public void addingAndDeletingProfiles() {
#SuppressWarnings("unchecked")
ListView<String> listview = (ListView<String>) mainroot.lookup("#listview");
clickOn("#textfield");
write("This is a test");
clickOn("#createnewprofile");
...
}
If you want to acces your controller class instance:
#Override
public void start(Stage stage) throws IOException {
this.mainstage = stage;
FXMLLoader loader = new FXMLLoader(getClass().getResource("GameOn2.fxml"));
this.mainroot = loader.load();
this.controller = loader.getController();
stage.setScene(new Scene(mainroot));
stage.show();
stage.toFront();
}

Related

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.

Convert Perametertype into class not working

I am Working on JavaFx.i want to display Number Of Node that is used in javaFX class.for that i get class and its start method.
NodeClassTree is a javaFX Class which i created with only start method and
in following code i get the start method. and i get method's perameters as a Class.
but i cant cast that class in to Stage Class object.
public static void main(String[] args)
{
try
{
Method myClasss = NodeClassTree.class.getDeclaredMethod("start",Stage.class);
System.out.println(myClasss.getParameterTypes()[0].getCanonicalName());
Class<?> stg = myClasss.getParameterTypes()[0];
System.out.println(stg);
stg.cast(new Stage()); //.........[1] Not Working
//stg.cast(new Object()); //.........[2] Not Working
}
catch(Exception e)
{
System.err.println("ERROR : "+e);
}
}
NodeClassTree.java
public class NodeClassTree extends Application {
#Override
public void start(Stage primaryStage){
BorderPane bdrpn = new BorderPane();
Scene cin = new Scene(bdrpn);
primaryStage.setScene(cin);
}
public static void main(String[] args) {
launch(args);
}
}
Error
Exception in thread "main" java.lang.ExceptionInInitializerError
at javafx.stage.Window.<init>(Window.java:1191)
at javafx.stage.Stage.<init>(Stage.java:239)
at javafx.stage.Stage.<init>(Stage.java:227)
at application.Controls.GetTree.main(GetTree.java:21)
Caused by: java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = main
at com.sun.glass.ui.Application.checkEventThread(Application.java:443)
at com.sun.glass.ui.Screen.setEventHandler(Screen.java:285)
at com.sun.javafx.tk.quantum.QuantumToolkit.setScreenConfigurationListener(QuantumToolkit.java:667)
at javafx.stage.Screen.<clinit>(Screen.java:79)
... 4 more
i get name of class using getCanonicalName() but i want to convert geterameterTypes()[0] into the Stage class after that i want to get Scene object
As the exception says, you are doing a UI update on a non-UI thread. You should use Platform.runLater()
I did not test this, so it may not work. In fact, I would say this is likely not to work, but you can try.
public class GetTree extends Application {
#Override
public void start(Stage primaryStage){
Application appToTest = new NodeClassTree();
appToTest.start(primaryStage);
Scene scene = primaryStage.getScene();
// And so on...
}
public static void main(String[] args) {
launch(args);
}
}

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).

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

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.

How to initialize JavaFX application dynamically, not hardcoded?

In many samples it is shown how to extend Application method to have JavaFX app composed and ran.
But what if I don't want to? What if I want to configure app dynamically from my code? Example is below:
import javafx.application.Application;
import javafx.stage.Stage;
public class HollowTry {
public static class HollowApplication extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
}
}
public static void main(String[] args) {
Application.launch(HollowApplication.class, args);
// now I want to set title, scene etc... how?
}
}
Please don't dispute on why I need it.
UPDATE
Okay, launch() is never terminated, I didn't check this. Anyway, I need to have a way to build application programmatically, without any hardcoding.
UPDATE 2
I was wishing con build application from Spring and I found the following solution for now.
JavaFX wrapper class
It wraps context initialization into FX thread and captures config classes to be accessible from start():
public class SpringJavaFX extends Application {
private static Class<?>[] annotatedClasses;
#Override
public void start(Stage primaryStage) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
String title = (String) context.getBean("primaryStageTitle");
primaryStage.setTitle(title);
Scene scene = (Scene) context.getBean("primaryStageScene");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void launch(Class<?>... annotatedClasses) {
SpringJavaFX.annotatedClasses = annotatedClasses;
Application.launch();
}
}
Spring way building
And here is an example of spring-way building. Each component is a bean and created in place:
public class Attempt01_HelloWorld {
#Configuration
public static class Config {
#Bean
String primaryStageTitle() {
return "Attempt01_HelloWorld";
}
#Bean
Scene primaryStageScene() {
Scene ans = new Scene(root(), 800, 600);
return ans;
}
#Bean
Button button() {
Button ans = new Button();
ans.setText("Say 'Hello World'");
ans.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
root().getChildren().add(ans);
return ans;
}
#Bean
StackPane root() {
StackPane root = new StackPane();
return root;
}
}
public static void main(String[] args) {
SpringJavaFX.launch(Config.class);
}
}
I'm not sure it would work, but you could try and add setter methods for the app's parameters inside the inner class, and try and call them from outside (e.g. from your main). Again, I don't know whether this would work or not, but I'd give it a try in your place.

Categories

Resources