How to initialize JavaFX application dynamically, not hardcoded? - java

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.

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.

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

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();
}

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

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