I have problem.
This is my main:
#SpringBootApplication
public class MyAppSpringApplication extends Application {
public static ConfigurableApplicationContext springContext;
private FXMLLoader fxmlLoader;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
fxmlLoader.setLocation(getClass().getResource("/sample.fxml"));
Parent root = fxmlLoader.load();
stage.setTitle("Sample app");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
#Override
public void stop() throws Exception {
springContext.stop();
}
#Override
public void init() throws Exception {
springContext = SpringApplication.run(MyAppSpringApplication.class);
fxmlLoader = new FXMLLoader();
fxmlLoader.setControllerFactory(springContext::getBean);
}
}
And my first window (sample.fxml) with samplecontroller and sampleservice works ok. But i create another dish-builder.fxml with their contoller and service, but when i try to use my service there, it doesnt work because of null in dishbuilderservice (albo doesnt work sampleservice in that new controller). I heard that i shound also use that:
public static ConfigurableApplicationContext springContext;
but i have no idea how should i use it. Sorry for my weak knowledge and english.
#Controller
public class DishBuilderController implements Initializable {
#Autowired
DishBuilderService dishBuilderService;
#Autowired
SampleService sampleService;
private void somefun(){
sampleService.somefunInService(); //here sampleService and
every other service has null.
}
Here is the moment when i open new dishBuilder window (its in SampleController):
#FXML
void addNoweOknoClicked(ActionEvent event) {
try {
Stage stage = (Stage)anchorPane.getScene().getWindow();
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("/dish-builder.fxml"));
AnchorPane root = fxmlLoader.load();
stage.setTitle("Sample app");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}catch (IOException e){
e.printStackTrace();
}
}
When you load dish-builder.fxml you are not setting the controller factory on the FXMLLoader. This means the FXMLLoader is simply creating the controller by calling its no-arg constructor. Since the controller is not a spring-managed bean, spring cannot inject any components into it.
You need to set the controller factory, as you do when you load sample.fxml, so that the FXMLLoader will ask Spring to retrieve the controller from the application context.
A couple of points that are not strictly relevant to your question:
There is no need to expose the ApplicationContext as a public static field. You can inject it into any spring-managed beans that need access to it
It is not recommended to re-use FXMLLoaders. Therefore there's no point in making the FXMLLoader an instance variable.
The #Controller annotation is intended for web controllers in a Spring MVC application. These are quite different to controllers in the JavaFX sense. You should use a generic #Component annotation for JavaFX controllers.
In the event that you were to reload an FXML file, you would need a new controller instance. This means that if the controller is managed by Spring, it needs to have PROTOTYPE scope, instead of the default SINGLETON scope.
So you need:
#SpringBootApplication
public class MyAppSpringApplication extends Application {
private ConfigurableApplicationContext springContext;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setControllerFactory(springContext::getBean);
fxmlLoader.setLocation(getClass().getResource("/sample.fxml"));
Parent root = fxmlLoader.load();
stage.setTitle("Sample app");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
#Override
public void stop() throws Exception {
springContext.stop();
}
#Override
public void init() throws Exception {
springContext = SpringApplication.run(MyAppSpringApplication.class);
}
}
Then your SampleController should look like
#Component
#Scope("prototype")
public class SampleController {
#Autowired
private ConfigurableApplicationContext springContext ;
#FXML
void addNoweOknoClicked(ActionEvent event) {
try {
Stage stage = (Stage)anchorPane.getScene().getWindow();
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setControllerFactory(springContext::getBean);
fxmlLoader.setLocation(getClass().getResource("/dish-builder.fxml"));
AnchorPane root = fxmlLoader.load();
stage.setTitle("Sample app");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}catch (IOException e){
e.printStackTrace();
}
}
}
and similarly
#Component
#Scope("prototype")
public class DishBuilderController implements Initializable {
#Autowired
DishBuilderService dishBuilderService;
#Autowired
SampleService sampleService;
private void somefun(){
// this should now work, since the controller is managed by Spring:
sampleService.somefunInService();
}
}
Related
Good day. I am a new learner of Java and I am currently stucking in performing a unit test for javafx scene builder controller.
I have an application class,
public class WebScraperApplication extends Application{
#Override
public void start(Stage stage) throws Exception{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/ui.fxml"));
VBox root = (VBox) loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("WebScraper");
stage.show();
}
public static void main(String args[]){
Application.launch(args);
}
}
ui.fxml stores the GUI information and Controller class is the javafx scene builder controller,
public class Controller{
#FXML
//variable and constructor
...
#FXML
//function
public int size(List<Object> x){
return x.size();
}
...
}
I have no idea to create a unit test to test the Controller class since I cannot create an object of it and implement the function inside.
You can take a look at TestFX. It's a testing framework for Java FX
I'm currently working on a 'small' project with JavaFX. I used the SceneBuilder to create the first sketch of my GUI. it still needs some adjustment and styling but I wanted to see if it's working so far.
I have 2 hyperlinks on the GUI, if the user clicks one of them the default system-browser should open with a specific URL.
So far I got this:
Main.java:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws MalformedURLException, IOException {
DataBean dataBean= new DataBean(primaryStage);
Controller controller = new Controller(dataBean);
controller.show();
}
public static void main(String[] args) {
launch(args);
}
}
DataBean.java:
public class DataBean {
private Stage primaryStage;
public DataBean(Stage stage) {
primaryStage = stage;
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
TestautomatView.java:
public class TestautomatView implements Initializable {
#FXML
private ComboBox<String> environmentCombo;
#FXML
private Hyperlink crhl;
#FXML
private Hyperlink help;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
private Scene scene;
private BorderPane root;
public TestautomatView() throws MalformedURLException, IOException {
root = FXMLLoader.load(new URL(TestautomatView.class.getResource("Sample.fxml").toExternalForm()));
scene = new Scene(root);
}
public void show(Stage stage) {
stage.setTitle("CrossReport Testautomat");
stage.setScene(scene);
stage.show();
}
public ComboBox<String> getEnvironmentCombo() {
return environmentCombo;
}
public Hyperlink getCrhl() {
return crhl;
}
public Hyperlink getHelp() {
return help;
}
public Scene getScene() {
return scene;
}
}
In my controller I want to set the ActionHandler to the hyperlinks but it's not working because the getters in my view return null.
public class Controller {
private DataBean dataBean;
private TestautomatView view;
public Controller(DataBean databean) throws MalformedURLException, IOException {
this.dataBean = databean;
this.view = new TestautomatView();
setActionHandlers();
}
public void show() throws MalformedURLException, IOException {
view.show(dataBean.getPrimaryStage());
}
private void setActionHandlers() {
// setHyperlink(view.getCrhl(), "www.example.com");
// setHyperlink(view.getHelp(), "www.example2.com");
}
private void setHyperlink(Hyperlink hl, String uri) {
hl.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//TODO - Open Default Browser
}
});
}
}
When I start my application, I can see the GUI but when I want to add the ActionHandlers I get a NullPointerException.
In the ´Sample.fxml´ file the hyperlinks are children of a HBox
<Hyperlink fx:id="crhl" text="Report" />
<Hyperlink fx:id="help" text="Help" />
But it's not just the hyperlinks even the ComboBox is null when I inspect my app in the debugger.
Where is my mistake?
The problem is that you are creating your controller manually by using new TestautomatView(). It must be created by FXMLLoader for annotations to work. You must also set fx:controller attribute in Sample.fxml to your controller (TestautomatView) fully qualified class name.
Example code:
FXMLLoader fl = new FXMLLoader(new URL(TestautomatView.class.getResource("Sample.fxml").toExternalForm()));
root = fl.load();
TestautomatView controller = fl.getController();
PS: You should rename your TestautomatView to TestautomatController. FXML file is your "view".
As pointed out in another answer, the issue is that you create an instance of TestautomatView "by hand". The default behavior of the FXMLLoader is to create an instance of the controller class specified in the FXML file, and use that instance as the controller. Consequently, you have two instances of TestautomatView: the one you created (and have a reference to), and the one that was created by the FXMLLoader. It is the second one that has the #FXML-annotated fields initialized.
You can change this default behavior by creating an FXMLLoader instance, and setting the controller on it directly. E.g. consider doing:
public class TestautomatView implements Initializable {
#FXML
private ComboBox<String> environmentCombo;
#FXML
private Hyperlink crhl;
#FXML
private Hyperlink help;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
private Scene scene;
private BorderPane root;
public TestautomatView() throws MalformedURLException, IOException {
FXMLLoader loader = new FXMLLoader(TestautomatView.class.getResource("Sample.fxml"));
loader.setController(this);
root = loader.load();
scene = new Scene(root);
}
// etc...
}
Since you are directly setting the controller, you need to remove the fx:controller attribute from the Sample.fxml file for this to work.
You may also be interested in this pattern, which is quite similar (though not exactly the same) as what you are trying to do here.
I'm still fighting with my issue. I want to use Spring Framework in order to incject dependencies and I have to use Spring boot to integrate both.
Unfortunately, in first view autowiring is run correctly, but if I go next Stage, I got still only Null Pointer Exception.
Thats main class:
#SpringBootApplication(scanBasePackages = "boxingchallenge")
public class BoxingChallengeApplication extends Application {
public ConfigurableApplicationContext springContext;
private Parent root;
public static Stage stage;
#Override
public void init() throws Exception {
springContext = SpringApplication.run(BoxingChallengeApplication.class);
springContext.getAutowireCapableBeanFactory().autowireBean(this);
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/FXML/start.fxml"));
fxmlLoader.setControllerFactory(springContext::getBean);
root = fxmlLoader.load();
}
#Override
public void start(Stage primaryStage) throws Exception {
stage = primaryStage;
primaryStage.setTitle("Boxing challenge");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() {
springContext.stop();
}
public static void main(String[] args) {
launch(BoxingChallengeApplication.class, args);
}
}
Here in first controller class autowiring run cool:
#Component
public class Start {
#FXML
public Button loadGame;
#FXML
public Button create;
#Autowired
private Boxer boxer;
public void load(ActionEvent event) {
System.out.println(boxer.getName());
}
//next stage
public void createNew(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/FXML/creator.fxml"));
BoxingChallengeApplication.stage.setScene(new Scene(root));
}
}
Here in second stage, autowiring not working:
#Component
public class Creator {
#FXML
public Button ready;
public TextField nation;
public TextField name;
public Boxer boxer;
/*#Autowired
private ApplicationContext context;*/
#Autowired
public void setBoxer(Boxer boxer) {
this.boxer = boxer;
}
public void createdAndPlay(ActionEvent event) {
if (boxer == null)
System.out.println("boxer is null");
else
System.out.println("Injected correctly");
}
}
Thanks, i hope it's going to finished...
#Jewelsea's comment is correct: you must set the controller factory when you load creator.fxml. If you don't do this, the FXMLLoader will create the controller simply by calling its no-arg constructor, so Spring will know nothing about it and will have no opportunity to inject any dependencies.
To do this, all you need is access to the ApplicationContext in Start, and you can inject "well-known objects", of which the ApplicationContext is an example, into your Spring-managed beans:
#Component
public class Start {
#FXML
public Button loadGame;
#FXML
public Button create;
#Autowired
private Boxer boxer;
#Autowired
private ApplicationContext context ;
public void load(ActionEvent event) {
System.out.println(boxer.getName());
}
//next stage
public void createNew(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/FXML/creator.fxml"));
load.setControllerFactory(context::getBean);
Parent root = loader.load();
BoxingChallengeApplication.stage.setScene(new Scene(root));
}
}
As an aside, you almost certainly want a new instance of any controller when you load an FXML file, so you should probably make any controllers prototype scope.
I am testing using Spring Boot with JavaFX (Based on some excellent YouTube videos that explain this).
To make it work with TestFX, I need to create the context like this:
#Override
public void init() throws Exception {
SpringApplicationBuilder builder = new SpringApplicationBuilder(MyJavaFXApplication.class);
builder.headless(false); // Needed for TestFX
context = builder.run(getParameters().getRaw().stream().toArray(String[]::new));
FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
loader.setControllerFactory(context::getBean);
rootNode = loader.load();
}
I now want to test this JavaFX application, for this I use:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyJavaFXApplicationUITest extends TestFXBase {
#MockBean
private MachineService machineService;
#Test
public void test() throws InterruptedException {
WaitForAsyncUtils.waitForFxEvents();
verifyThat("#statusText", (Text text ) -> text.getText().equals("Machine stopped"));
clickOn("#startMachineButton");
verifyThat("#startMachineButton", Node::isDisabled);
verifyThat("#statusText", (Text text ) -> text.getText().equals("Machine started"));
}
}
This starts a Spring context and replaces the "normal" beans with the mock beans as expected.
However, I now get a java.awt.HeadlessException because this 'headless' property is not set to false like is done during normal startup. How to I set this property during the test?
EDIT:
Looking closer it seems that there are 2 context started, one that the Spring testing framework starts and the one I create manually in the init method, so the application under test is not using the mocked beans. If somebody would have a clue how to get the test context reference in the init() method, I would be very happy.
The #SpringBootTest uses SpringBootContextLoader class as context loader, so the ApplicationContext is load from the method SpringBootContextLoader.loadContext as this:
SpringApplication application = getSpringApplication();
......
return application.run();
When invoke the method application.run(), application configure the system headless property with it's internal headless property.
So, If we want to set 'headless' property in a Spring Boot test, just create a customize specific ContextLoader class extends SpringBootContextLoader class, and override the method getSpringApplication with set the headless property as false, then assign the specific ContextLoader with annotation #ContextConfiguration for #SpringBootTest. The code:
public class HeadlessSpringBootContextLoader extends SpringBootContextLoader {
#Override
protected SpringApplication getSpringApplication() {
SpringApplication application = super.getSpringApplication();
application.setHeadless(false);
return application;
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.NONE)
#ContextConfiguration(loader = HeadlessSpringBootContextLoader.class)
public class ApplicationTests {
#Test
public void contextLoads() {
}
}
The comment from Praveen Kumar pointed in the good direction. When I run the test with -Djava.awt.headless=false, then there is no exception.
To solve the other problem of the 2 Spring contexts, I had to do the following:
Suppose this is your main JavaFx startup class:
#SpringBootApplication
public class MyJavaFXClientApplication extends Application {
private ConfigurableApplicationContext context;
private Parent rootNode;
#Override
public void init() throws Exception {
SpringApplicationBuilder builder = new SpringApplicationBuilder(MyJavaFXClientApplication.class);
builder.headless(false); // Needed for TestFX
context = builder.run(getParameters().getRaw().stream().toArray(String[]::new));
FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
loader.setControllerFactory(context::getBean);
rootNode = loader.load();
}
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
double width = visualBounds.getWidth();
double height = visualBounds.getHeight();
primaryStage.setScene(new Scene(rootNode, width, height));
primaryStage.centerOnScreen();
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void stop() throws Exception {
context.close();
}
public void setContext(ConfigurableApplicationContext context) {
this.context = context;
}
}
And for testing, you use this abstract base class (Courtesy of this YouTube video by MVP Java):
public abstract class TestFXBase extends ApplicationTest {
#BeforeClass
public static void setupHeadlessMode() {
if (Boolean.getBoolean("headless")) {
System.setProperty("testfx.robot", "glass");
System.setProperty("testfx.headless", "true");
System.setProperty("prism.order", "sw");
System.setProperty("prism.text", "t2k");
System.setProperty("java.awt.headless", "true");
}
}
#After
public void afterEachTest() throws TimeoutException {
FxToolkit.hideStage();
release(new KeyCode[0]);
release(new MouseButton[0]);
}
#SuppressWarnings("unchecked")
public <T extends Node> T find(String query, Class<T> clazz) {
return (T) lookup(query).queryAll().iterator().next();
}
}
Then you can write a test like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyJavaFXApplicationUITest extends TestFXBase {
#MockBean
private TemperatureService temperatureService;
#Autowired
private ConfigurableApplicationContext context;
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
loader.setControllerFactory(context::getBean);
Parent rootNode = loader.load();
stage.setScene(new Scene(rootNode, 800, 600));
stage.centerOnScreen();
stage.show();
}
#Test
public void testTemperatureReading() throws InterruptedException {
when(temperatureService.getCurrentTemperature()).thenReturn(new Temperature(25.0));
WaitForAsyncUtils.waitForFxEvents();
assertThat(find("#temperatureText", Text.class).getText()).isEqualTo("25.00 C");
}
}
This allows to start the UI using mock services.
New to JAVAFX so this maybe a simple fix, but I have controllers in my application setup using FXML files. I reference the controller to use via the FXML file and to load the file i use the following code in my Application class
private void replaceScene(String resource) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
Pane screen = (Pane) loader.load();
Scene scene = new Scene(screen);
scene.getStylesheets().addAll(getClass().getResource("/css/application.css").toExternalForm());
stage.setScene(scene);
stage.sizeToScene();
IControlledScreen controller = (IControlledScreen) loader.getController();
controller.setApp(this);
} catch (Exception e) {
System.out.println("Cannot load resource " + resource);
System.out.println(e.getMessage());
}
}
And here is a basic controller
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
btnStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// code here
}
});
}
}
I have also got an interface called IControlledScreen to set the reference to the application
public interface IControlledScreen {
// ALlows us a reference to the application
public void setApp(MyApplication app);
}
Now this all works fine, until i try to access the app variable during the initialize event. So changing the above controller to this now breaks, because app = NULL.
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
// HERE app = NULL
app.GetSomeProperty = "";
}
}
How can i get round this?
Well I think you have to change your design.
The initialize method is called during FXMLLoader.load()
So the call stack would be something like
..replaceScene
..loader.load
....MyController.initialize()
..loader.getController
..controller.setApp(app)
If you really have to access the application from inside your controller you would need to make it a singleton.