Stuck with event handling Java FX - Controller - Scene builder - javafx

I want to use Netbeans Java FX with Scene builder for a measurement application. I have designed a scene with controls. I can handle the events from the UI-controls within the '...Controller.java'.
The 'controller' is the standard piece of code that is referenced in the XML file and gets initialized by the system with:
public void initialize(URL url, ResourceBundle rb) { ..
My problem: how do I access my central, persisting, 'model' objects from within the controller? Or, to be more exact, from the event handlers created within the controller initialize function.
The 'model' object would be created within the application object.
The solution must be trivial, but I have not found a way to
either access the Application from the controller
or access the controller from within the Application.
What am I missing?
(the next question would be how to access the tree of panes within the object hierarchy created by screen builder, e.g. for graphics manipulation on output. Since the objects are not created by own code I can not store references to some of them. Ok, they could perhaps be found and referenced by tree-walking, but there must be a better way!)
Thanks for all insights!

I have used the 2nd approach (access the controller from within the Application) for awhile ago similar to following. In Application class:
//..
private FooController fooController;
private Pane fooPage;
private Model myModel;
#Override
public void start(Stage stage) {
//..
myModel = new Model();
getFooController().updateModel(myModel);
//..
Button button = new Button("Update model with new one");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Model myNewModel = new Model();
getFooController().updateModel(myNewModel);
}
}
// create scene, add fooPage to it and show.
}
private FooController getFooController() {
if (fooController == null) {
FXMLLoader fxmlLoader = new FXMLLoader();
fooPage = fxmlLoader.load(getClass().getResource("foo.fxml").openStream());
fooController = (FooController) fxmlLoader.getController();
}
return fooController;
}
Actually the first and second parts of your question is answered JavaFX 2.0 + FXML. Updating scene values from a different Task to the similar question of yours.

Related

Is it possible to launch a JavaFX application through another JavaFX application?

Can I know why there is an error when I say.
Stage s = new Stage();
new CaeserCipherFX().start(s);
This is my code below. I need to launch another JavaFX Application from this one. Please help. Thank you.
public class Main extends Application
{
String args[];
#Override
public void start(Stage stage) throws Exception
{
// creating types of encryptions (Button)
Button caeserCipher = new Button("1. Caeser Cipher");
Button runningKeyCipher = new Button("2. Running Key Cipher");
Button trithemiusCipher = new Button("3. Trithemius Cipher");
Button vignereCipher = new Button("4. Vignere Cipher");
//setting styles
caeserCipher.setTextFill(Color.BLUE);
runningKeyCipher.setTextFill(Color.BLUE);
trithemiusCipher.setTextFill(Color.BLUE);
vignereCipher.setTextFill(Color.BLUE);
/*need to add more!*/
//setting action listeners
String arr [] = {"CaeserCipher","RunningKeyCipher","TrithemiusCipher","VignereCipher"};
caeserCipher.setOnAction((ActionEvent event)->{
//open caeser cipher
Stage s = new Stage();
new CaeserCipherFX().start(s);
});
runningKeyCipher.setOnAction((ActionEvent event)->{
//open running key cipher
stage.hide();
});
trithemiusCipher.setOnAction((ActionEvent event)->{
//open trithemius cipher
stage.hide();
});
vignereCipher.setOnAction((ActionEvent event)->{
//open vignere cipher
stage.hide();
});
// creating flowpane(FlowPane)
FlowPane menu = new FlowPane();
menu.setHgap(25);
menu.setVgap(25);
menu.setMargin(caeserCipher, new Insets(20, 0, 20, 20));
//list for Flowpane(ObservableList)
ObservableList list = menu.getChildren();
//adding list to flowpane
list.addAll(caeserCipher,runningKeyCipher,trithemiusCipher,vignereCipher);
//scene for stage
Scene scene = new Scene(menu);
stage.setTitle("Main Menu");
stage.setScene(scene);
// stage.initStyle(StageStyle.UTILITY);
stage.setHeight(100);
stage.setWidth(600);
stage.setResizable(false);
// Show the Stage (window)
stage.show();
}
}
And I want to launch the code below:
public class CaeserCipherFX extends Application
{
#Override
public void start(Stage stage) throws Exception
{//some other code
//some other code
}
}
There is a ubiquitous JavaFX main application thread which takes a while to get used to.
Think of it like the front-end thread. Theoretically, you should use that thread to handle UI updates and complex cpu tasks such as looking up something in a BD or figuring out the 100000th decimal of PI should be done in a background thread. If you don't do this, the UI will become unresponsive until the DB data is returned, or that decimal is found.
public class TestClass extends Application {
public static void main(String[] args) {
System.out.println("here");
Application.launch(TestClass.class, args);
System.out.println("this is called once application launch is terminated.");
}
#Override
public void init() throws Exception {
super.init(); //To change body of generated methods, choose Tools | Templates.
System.out.println("message from init");
}
#Override
public void start(Stage primaryStage) throws Exception { // this is abstract.
System.out.println("message from start");
Platform.exit(); // if you remove this line, the application won't exit.
}
}
Since JavaFX comes with some prerequisites, you need to start you rapplication using a front-end. You can work around this, but technically,
public void start(Stage primaryStage)
is what , for all intensive purposes, starts your program.
From here, you can use the primaryStage to control most of your application. It's a good idea to put a .onCloseRequest() on it in which you call Platform.exit();
If you want to have multiple windows in your application, you could use something like
public class TestClass extends Application {
public static void main(String[] args) {
System.out.println("here");
Application.launch(TestClass.class, args);
System.out.println("this is called once application launch is terminated.");
}
#Override
public void init() throws Exception {
super.init(); //To change body of generated methods, choose Tools | Templates.
System.out.println("message from init");
}
#Override
public void start(Stage primaryStage) throws Exception { // this is abstract.
primaryStage.setScene(new Scene(new TextArea("this is the first stage (window)")));
primaryStage.setTitle("stage 1");
primaryStage.show();
primaryStage.setOnCloseRequest((event) -> {
Platform.exit();
});
Stage secondaryStage = new Stage();
secondaryStage.setTitle("stage 2");
TextArea ta2 = new TextArea("this is a different stage.");
Scene scene = new Scene(ta2);
secondaryStage.setScene(scene);
secondaryStage.show();
primaryStage.setX(200);
secondaryStage.setX(200 + primaryStage.getWidth() + 50);
}
}
This is what I assume you want to do. Basically create a new window whenever you press a button. You can create stages like this.
The reason for which you can't do it your way is because you are attempting to start another javafx thread by invoking new CaeserCipherFX which is an application object, not a Stage.
new CaeserCipherFX().start(s); // this can only be called once.
IF you absolutely want to have 2 distinct applications (note: not application windows), then you need to have 2 distinct processes.
Lastly, the primaryStage parameter used in either examples is in the beginning basically a placeholder (as in it's constructed, but there's nothing really in it... like a new String()). You can use different stage objects as your "primary" UI.
Lastly, if depending on the stuff you want to decrypt, you may need to use background threads if you want to keep the UI responsiveness. For this you will need to check out the concurrency part of the javafx tutorial.
Is it possible to launch a JavaFX application through another JavaFX application? Not really.
Alternatively, you can use java.lang.ProcessBuilder
This class essentially sends command lines to your operating system shell.
You can use it to run something like "java -jar XXX\YYY\CaeserCipherFX.jar" whenever you click a button. (you'll have to build a CaeserCypherFX project into a jar file)
This will create a new JVM. This means no memory state sharing. You can handle this through IPC.

JavaFX: Need help understanding setControllerFactory

I set up multiple custom controllers during the creation of an app and would need some help in organizing these controllers with setControllerFactory in JavaFX.
I'm fairly inexperienced with JavaFX but invested quite some time in creating a small app with Scenebuilder and JavaFX.
Background of the app
The app consists of:
- a map (implemented as an imageView)
- a sidebar with buttons and icons for drag and drop events.
- the map also has separate layers as the target for the drag and drop of different icon types.
As a prototype of my drag and drop event I used the instructions of Joel Graff (https://monograff76.wordpress.com/2015/02/17/developing-a-drag-and-drop-ui-in-javafx-part-i-skeleton-application/). He writes "in order for an object to be visible beyond a container’s edges, it must be a child of a parent or other ancestral container – it must belong to a higher level of the hierarchy. In the case of our drag-over icon, this means we had to add it as a child to the RootLayout’s top-level AnchorPane." and he uses dynamic roots for his project.
To teach myself how to use custom control with FXML I used Irina Fedortsova's tutorial https://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm.
And to learn how to set up multiple screens I used the video https://www.youtube.com/watch?v=5GsdaZWDcdY and associating code from https://github.com/acaicedo/JFX-MultiScreen.
After building my app the logic tier of my app got more and more entangled with the presentation tier, and I feel as if my code would benefit greatly from some refactoring.
My problem seems to be a lack in the understanding of the load and initialize process of controller classes. Since the drag icons and the RootLayout have to be loaded from the beginning, it is a mystery to me how I can load these classes in a way that I can call them again at a later time.
When I was looking for further solutions, I repeatedly came across the method setControllerFactory. Unfortunately I can't find a good explanation for how to use it properly and what it's specific purpose is.
The only tutorial I found was: https://riptutorial.com/javafx/example/8805/passing-parameters-to-fxml---using-a-controllerfactory, unfortunately it seems to be a bit insufficient for my purpose.
I feel as if I would benefit the most from a methode/class with which I could organize all my custom controllers, load and initialize them at the appropriate time and then later access them again (similar to the interface and superclass in the video for JFX-MultiScreen).
I repeatedly came across the method setControllerFactory. Unfortunately I can't find a good explanation for how to use it properly and what it's specific purpose is
By default, the FXMLLoader.load() method instantiates the controller named in the fxml document using the 0-arg constructor. The FXMLLoader.setControllerFactory​ method is used when you want your FXMLLoader object to instantiate controllers in a certain way, e.g. use a different controller constructor on specific arguments, call a method on the controller before it's returned, etc, as in
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(c -> {
return new MyController("foo", "bar");
});
Now when you call loader.load() the controller will be created as above. However, calling the FXMLLoader.setController​ method on a preexisting controller may be easier.
I feel as if I would benefit the most from a methode/class with which I could organize all my custom controllers, load and initialize them at the appropriate time and then later access them again
When I first came across this problem, as you have, I tried and retried many approaches. What I finally settled on was turning my main application class into a singleton. The singleton pattern is great when you need to create one instance of a class which should be accessible throughout your program. I know there are many people who will take issue with that (in that it's essentially a global variable with added structure), but I've found that it reduced complexity significantly in that I no longer had to manage a somewhat artificial structure of object references going every which way.
The singleton lets controllers communicate with your main application class by calling, for example, MyApp.getSingleton(). Still in the main application class, you can then organize all of your views in a private HashMap and add public add(...), remove(...), and activate(...) methods which can add or remove views from the map or activate a view in the map (i.e. set the scene's root to your new view).
For an application with many views that may be placed in different packages, you can organize their locations with an enum:
public enum View {
LOGIN("login/Login.fxml"),
NEW_USER("register/NewUser.fxml"),
USER_HOME("user/UserHome.fxml"),
ADMIN_HOME("admin/AdminHome.fxml");
public final String location;
View(String location) {
this.location = "/views/" + location;
}
}
Below is an example of the main application class:
public final class MyApp extends Application {
// Singleton
private static MyApp singleton;
public MyApp() { singleton = this; }
public static MyApp getSingleton() { return singleton; }
// Main window
private Stage stage;
private Map<View, Parent> parents = new HashMap<>();
#Override
public void start(Stage primaryStage) {
stage = primaryStage;
stage.setTitle("My App");
add(View.LOGIN);
stage.setScene(new Scene(parents.get(View.LOGIN)));
stage.show();
}
public void add(View view) {
var loader = new FXMLLoader(getClass().getResource(view.location));
try {
Parent root = loader.load();
parents.put(view, root);
} catch (IOException e) { /* Do something */ }
}
public void remove(View view) {
parents.remove(view);
}
public void activate(View view) {
stage.getScene().setRoot(parents.get(view));
}
public void removeAllAndActivate(View view) {
parents.clear();
add(view);
activate(view);
}
}
If you have application-wide resources you can put them in the app class and add getters/setters so your controllers can access them. Here is an example controller class:
public final class Login implements Initializable {
MyApp app = MyApp.getSingleton();
// Some #FXML variables here..
#FXML private void login() {
// Authenticate..
app.removeAllAndActivate(View.USER_HOME);
}
#FXML private void createAccount() {
app.add(View.NEW_USER);
app.activate(View.NEW_USER);
}
#Override
public void initialize(URL url, ResourceBundle rb) {}
}

Get a reference to a Tab from outside FXML Controller

I'm playing around with JavaFX for the first time on a personal project.
I want to be able to update the content in a Tab (adding a PieChart), but from outside the FXML controller, can anyone tell me if that's possible? How would I get a reference to the relevant tab, and is there a way to specify the location of the item I am adding?
UPDATE: Added some example code.
Hope this gives a clear idea of what I'm trying to do:
The interface:
interface ChartStrategy {
public void DisplayChart(Info info)
}
Two implementations:
public class BarChartStrategy extends ChartStrategy {
public void DisplayChart(Info info)
{
// Create bar charts on specific tabs in the UI
}
}
public class PieChartStrategy extends ChartStrategy {
public void DisplayChart(Info info)
{
// Create pie charts on specific tabs in the UI
}
}
The context:
public class ChartContext {
private ChartStrategy strategy;
public void setChartStrategy(ChartStrategy strategy) {
this.strategy = strategy;
}
public void drawGraphs(Info info) {
strategy.DisplayChart(info);
}
}
In my Controller, I'm reading in a file the user selects from which the data to generate the charts is parsed, .e.g.
#FXML
private void handleButtonAction(ActionEvent event) {
// Load the file and parse the data
...
ChartContext charts = new ChartContext();
charts.setChartStrategy(new PieChartStrategy());
}
So my original thought was that I could draw the charts from the DisplayChart function in the implementations, but it seems that's not a good idea - can anyone give me some advice here on the best way to get this to work?
I would refactor this a bit.
First, letting the strategy display the chart is a bit inflexible. You are giving the strategy two responsibilities: first to decide how to represent the data visually (choose a chart) and second to actually display it somewhere. That violates the single responsibility principle.
So I would do
interface ChartStrategy {
public Chart generateChart(Info info)
}
and similarly for the implementations, of course. Then the responsibility of the strategy is simply to provide a chart: the context can decide what to do with it.
(You can also consider whether returning a Chart here is too rigid: maybe you just want it to return a Parent, or Node. Then your "chart" could be, e.g., a TableView, for example.)
In the theoretical descriptions of the strategy pattern, at least in my interpretation, the "context" just represents "something that is using the strategy". So your context is likely a view or controller (depending on your choice of MVC variant...). As a trivial example you might have something like
public class ChartTab {
private ChartStrategy chartGenerator ;
public void setChartGenerator(ChartStrategy chartGenerator) {
this.chartGenerator = chartGenerator ;
}
public Tab createChartTab(Info info) {
Tab tab = new Tab();
tab.setContent(chartGenerator.generateChart(info));
return tab ;
}
}
and then in your controller
#FXML
private TabPane tabPane ;
#FXML
private void handleButtonAction(ActionEvent event) {
// Load the file and parse the data
...
ChartTab chartTab = new ChartTab();
chartTab.setChartGenerator(new PieChartStrategy());
tabPane.getTabs().add(chartTab.getTab(info));
}
It's also possible just to consider the controller the context (if for a fixed controller you are just creating one kind of chart, which depends on how you split up the FXML files and their corresponding controllers):
public class MyController {
private ChartStrategy chartGenerator ;
#FXML
private TabPane tabPane ;
public MyController(ChartStrategy chartGenerator) {
this.chartGenerator = chartGenerator ;
}
#FXML
private void handleButtonAction(ActionEvent event) {
// Load the file and parse the data
...
Tab tab = new Tab();
tab.setContent(chartGenerator.generateChart(info));
tabPane.getTabs().add(tab);
}
}
Note this controller doesn't have a no-arg constructor, so you cannot use the fx:controller attribute in the FXML file (i.e. remove that attribute from the FXML file). Instead, you'd do
FXMLLoader loader = new FXMLLoader("/path/to/DataDisplay.fxml");
MyController controller = new MyController(new PieChartStrategy());
loader.setController(controller);
Parent root = loader.load();
Now you have an FXML and controller with the functionality to generate charts and display them in a tab pane (or whatever), but the details of what kind of chart is generated are factored out into a pluggable strategy. You still have the proper MVC (or MVP, etc etc) encapsulation in which the details of the UI are kept private to the view-controller (here it's really a presenter, but who's counting...) pair. In other words the strategy knows nothing about the rest of the view, which is as it should be.

JavaFX exchange data between controllers (parent/child forms)

Is this approach lame to do so?
I've tried many approaches e.g.
How can I exchange data between forms
JavaFX pass values from child to parent
JavaFX 2.2 -fx:include - how to access parent controller from child controller
but this seems to be pretty direct and understandable.
public class ParentController {
private Settings settings;
public void setSettings(Settings settings) {
this.settings = settings;
System.out.println(this.settings.toString());
}
#FXML
private Button open;
#FXML
private void pass() throws IOException {
Stage st = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("Child.fxml"));
Region root = (Region) loader.load();
Scene scene = new Scene(root);
st.setScene(scene);
ChildController controller = loader.<ChildController>getController();
controller.initialize(this);
st.show();
}
}
public class ChildController {
#FXML
private TextField number; // some settings
#FXML
private Button ok;
private ParentController parentController ;
#FXML
public void pass() {
Stage stage = (Stage) ok.getScene().getWindow();
parentController.setSettings(setSettings());
stage.close();
}
private Settings setSettings(){
return new Settings(Integer.valueOf(this.number.getText()));
}
public void initialize(ParentController parentController) {
this.parentController = parentController;
}
}
In such the way I'm getting settings object generated in Child controller and pass this object to the parent controller.
This works...
Is this approach appropriate? If not, what pitfall is may imply?
The question is probably too opinion-based for this forum. The trade-off for the simplicity you gain (compared to, say, JavaFX pass values from child to parent) is that you have tightly-coupled the child to the parent: in other words you can't use the child view/controller in any context where you don't have that specific parent. In the linked approach, the child view/controller have no dependency on the parent. Whether or not this is desirable/beneficial/worth the added complexity will depend on your exact use case.
I thought it was appropriate. for me it was just a coding style.
but in my opinion , change your settings variable to public static, stored in the Main class and initialized in the Main class, to be accessible at all controllers or other classes. thats my style :D
public static settings Settings;
Main.settings.( Something)
Happy coding..

Inject dependencies to the Java fx application

Probably I miss somehting out, but I'm struggeling to find a solution how I can pass dependencies like an instance of my event bus and some service interfaces to my javafx application.
I got an UI-Init class which does not much more than starting the application and receiving some dependencies for the UI like an eventBus:
public class Frontend {
public Frontend(MBassador<EventBase> eventBus) {
Application.launch(AppGui.class);
}
My AppGui class extends Application and loads an FXML:
public class AppGui extends Application {
private Stage primaryStage;
private GridPane rootLayout;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("RootLayout.fxml"));
rootLayout = (GridPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
scene.setFill(null);
primaryStage.setScene(scene);
RootLayoutController rootController = loader.getController();
rootController.init(/*here I would like to inject my eventBus*/);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
Now, how can I pass my eventBus and other service interfaces to this controller? I've read about using DI frameworks like guice (How can JavaFX controllers access other services?) or afterburner.fx to use it. But even if I use guice for the rest of my application, somehow I need to pass the Injector instance to the JavaFX application?.
But Application.launch(AppGui.class); is static and internally creates an AppGui instance on the javafx thread, which I don't get access to. So how I can inject dependencies to my AppGui object without using static variables?
Here is what I do:
The Application class has a couple of lifecycle callbacks, init() and stop().
From the Javadocs:
public void init() throws java.lang.Exception
The application initialization method. This method is called immediately after the Application class is loaded and constructed. An application may override this method to perform initialization prior to the actual starting of the application.
public void stop() throws java.lang.Exception
This method is called when the application should stop, and provides a convenient place to prepare for application exit and destroy resources.
Also from the Javadocs, the lifecycle:
Constructs an instance of the specified Application class
Calls the init() method
Calls the start(javafx.stage.Stage) method
Waits for the application to finish, which happens when either of the following occur:
the application calls Platform.exit()
the last window has been closed and the implicitExit attribute on Platform is true
Calls the stop() method
I start the IoC container in init() and stop it in stop(). Now my Application class has a reference to the IoC container and can supply the first controller with its dependencies.
As a matter of fact, I let the IoC framework manage the controllers. I set them to the loaded FXML using FXMLLoader.setController(), instead of specifying them with fx:controller.
You can pass a static reference to your application class before you call launch(). Something like:
public class Frontend {
public Frontend(MBassador<EventBase> eventBus) {
AppGui.setEventBus(eventBus);
Application.launch(AppGui.class);
}
}
public class AppGui extends Application {
private static MBassador<EventBase> eventBus;
public static void setEventBus(MBassador<EventBase> eventBus) {
this.eventBus = eventBus;
}
private MBassador<EventBase> eventBus;
#Override
public void init() {
if (AppGui.eventBus == null) {
throw new IllegalStateException();
// or however you want to handle that state
} else {
this.eventBus = AppGui.eventBus;
AppGui.eventBus = null;
}
}
}
Whether you keep and use the static reference, or you copy the static reference to a local reference is up to you and the design of your application. If you expect to instantiate more than one copy of AppGui, you may need the local reference.
No idea if this is thread safe (probably not). The advice from #Nikos and #James_D is solid and preferred... but sometimes you just need a hack. :) YMMV

Resources