I am creating a JavaFX application and when I create a controller for my FXML file the constructors are always the same.
Is there any way to write a custom annotation to create my constructors?
Something like this:
public class MyClass() {
#InitFxml(file = "test")
public MyClass() {
}
And the #InitFxml would inject the following code into the constructor:
FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml");
...
or is it possible to create annotation for the class which creates this default constructor?
Any help is greatly appreciated.
To process the annotation, you would have to define some kind of container that processed it, and always load your class through that container, or define an annotation processor which you attached to the compiler (I think: I know nothing about that second option).
Why not just pass a string as a parameter, though. You could define an interface:
import java.net.URL;
import javafx.fxml.FXMLLoader;
public interface CustomComponent {
public default void loadFXML(String fxml) {
try {
URL resource = getClass().getResource(fxml);
FXMLLoader loader = new FXMLLoader(resource);
loader.setRoot(this);
loader.setController(this);
loader.load();
} catch (Exception exc) {
if (! (exc instanceof RuntimeException)) {
throw new RuntimeException(exc);
} else {
throw (RuntimeException)exc ;
}
}
}
}
and then just have your custom components implement it, calling the method from the constructor:
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CustomComponentTest extends Application {
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new CustomVBox(), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class CustomVBox extends VBox implements CustomComponent {
#FXML
private Label label ;
public CustomVBox() {
loadFXML("CustomVBox.fxml");
}
#FXML
private void click() {
System.out.println("Click!");
}
}
public static void main(String[] args) {
launch(args);
}
}
This seems to be no heavier than defining an annotation on an empty constructor.
Related
**I'm unable to create a constructor from "GUIController".. The program runs if i delete this line
"GUIController(){myModel = new TheModel(this)"
but i still need it in other part. Please help!
**
package theclient;
import java.rmi.RemoteException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class GUIController extends Application {
TheModel myModel;
GUIController(){
myModel = new TheModel(this);
}
//public void init(){}
public TextArea myTextArea;
public TextField myTextField;
// Button and text field actions
public void myButtonAction() {
sendMsg();
}
public void myTextFieldAction() {
sendMsg();
}
// Append coming message
public void displayMsg(String comingMSG) {
System.out.println("Receive 01");
myTextArea.appendText(comingMSG);
}
public void sendMsg() {
try {
System.out.println("Send 01");
myModel.myChatServer.tellOthers(myTextField.getText());
} catch (RemoteException ex) {
Logger.getLogger(GUIController.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("GUI.fxml"));
Scene scene = new Scene(root, 600, 400);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public static void main(String[] args) throws Exception {
new GUIController();
launch(args);
}
}
The Second class. I'd be thankful if you can suggest any edits to the code. Thanks in advance for your efforts.
package theclient;
import common.ChatServerInt;
import common.ClientInt;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TheModel implements ClientInt {
public GUIController myCtrl;
ChatServerInt myChatServer;
TheModel(GUIController myCtrl) {
this.myCtrl = myCtrl;
}
public ChatServerInt connection() {
if (myChatServer == null) {
try {
Registry reg = LocateRegistry.getRegistry(1111);
myChatServer = (ChatServerInt) reg.lookup("ChatService");
myChatServer.register(this);
myChatServer.tellOthers("I'm here!");
} catch (RemoteException | NotBoundException ex) {
Logger.getLogger(TheModel.class.getName()).log(Level.SEVERE, null, ex);
}
} return myChatServer;
}
#Override
public void receive(String msg) throws RemoteException {
myCtrl.displayMsg(msg);
}
}
Following the Model-view-controller design pattern, the model shouldn't be holding a reference to its controller. If the controller needs to respond to changes in the model's data, then this can be done with properties and listeners. The model holds a property (here, a StringProperty) and the controller listens for changes to the property.
For your code, this means storing the msg in a StringProperty. The controller, after constructing the model, attaches a ChangeListener that calls displayMsg when the model receives a message.
Using a property and listener, TheModel no longer stores a reference to GUIController and does not take a GUIController as a parameter in its constructor.
GUIController would look something like this:
public class GUIController extends Application {
...
TheModel myModel;
...
GUIController(){
myModel = new TheModel();
// Listen for changes in the msg StringProperty and call displayMessage when it changes
myModel.getMsgProperty().addListener(msg -> this.displayMsg(msg));
}
...
Note that the constructor for GUIController no longer needs to pass this to the constructor TheModel. (In general, avoid passing this outside of the constructor. An object is not fully constructed until the constructor returns.)
TheModel would look something like this:
public class TheModel implements ClientInt {
...
private StringProperty msgProperty;
...
// remember to add a getter and setter for msgProperty!
...
#Override
public void receive(String msg) throws RemoteException {
// When a message is received, listeners attached to msgProperty will execute when setValue is called
msgProperty.setValue(msg);
}
I have a runnable class "TemperatureSensor" which is periodically adding a new randomized floating point value to an array list TemperatureList as an object Temperature. The last added object in the array (index 0) is then sent from RMI client to RMI server - this happens without problems.
However, when I click a button on GUI to display the size of this object array, I always get a 0. If I print out the size of the array from RMI client class, it shows a correct size.
My question is, how do I access the same array from multiple classes correctly?
Here is the UML:
TemperatureSensor:
import java.text.DecimalFormat;
import java.util.Random;
public class TemperatureSensor implements Runnable
{
private int waitingTime;
private Model model;
public TemperatureSensor(Model model, int waitingTime)
{
this.model = model;
this.waitingTime = waitingTime;
}
#Override
public void run()
{
float temperature = 25.0f;
while(true)
{
temperature = measureTemperature(temperature);
model.addTemperatureData(temperature);
System.out.println("Sending: " + temperature);
waiting();
}
}
private float measureTemperature(float temperature)
{
Random rand = new Random();
float minTempFloat = 0.1f;
float maxTempFloat = 0.2f;
int incrementSwitch = rand.nextInt(3-0) + 0;
if (incrementSwitch == 0)
{
temperature += minTempFloat + rand.nextFloat() * (maxTempFloat - minTempFloat);
}
else if(incrementSwitch == 1)
{
//Do nothing
}
else if (incrementSwitch == 2)
{
temperature -= minTempFloat + rand.nextFloat() * (maxTempFloat -
minTempFloat);
}
return temperature;
}
private void waiting()
{
try
{
Thread.sleep(waitingTime);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
Model:
public interface Model
{
public void addTemperatureData(float value);
public Temperature getLatestTemperatureData();
public int getTempListSize();
}
ModelManager:
public class ModelManager implements Model
{
private TemperatureList temperatureList;
public ModelManager()
{
temperatureList = new TemperatureList();
}
#Override
public void addTemperatureData(float value)
{
Temperature temperature = new Temperature(value);
//this.temperatureList.clearTemperatureDataList();
this.temperatureList.addTemperatureDataToList(temperature);
}
#Override
public Temperature getLatestTemperatureData()
{
return temperatureList.getLatestTemperatureDataFromList();
}
#Override
public int getTempListSize()
{
return temperatureList.size();
}
}
RMIsensorClient:
import java.rmi.Naming;
import java.rmi.RemoteException;
public class RMIsensorClient
{
private RMIserverInterface serverInterface;
private static Model model = new ModelManager();
public static void main(String[] args) throws RemoteException, InterruptedException
{
TemperatureSensor tempSensor = new TemperatureSensor(model, 5000);
Thread tempThread = new Thread(tempSensor, "TempSensor");
tempThread.start();
RMIsensorClient sensorClient = new RMIsensorClient();
}
public RMIsensorClient() throws RemoteException
{
super();
try
{
serverInterface = (RMIserverInterface) Naming.lookup("rmi://localhost:1099/rmiServer");
while(true)
{
serverInterface.getTemperature(model.getLatestTemperatureData());
System.out.println(model.getTempListSize());
Thread.sleep(5000);
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
Controler:
public class Controller
{
private static Model model;
public Controller ()
{
this.model = new ModelManager();
}
public int getNumberOfListElements()
{
return model.getTempListSize();
}
}
GUI:
public class GUItemperatureController implements Initializable
{
private Controller controller = new Controller();
#FXML
private Label tlTemperature;
#FXML
private Pane mainPane;
#FXML
private TextField tfTemperature;
#FXML
private Button btnUpdate;
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
tfTemperature.setEditable(false);
}
#FXML
void showArraySize(ActionEvent event)
{
tfTemperature.setText(Integer.toString(controller.getNumberOfListElements()));
}
}
TemperatureList:
import java.io.Serializable;
import java.util.ArrayList;
public class TemperatureList implements Serializable
{
private ArrayList<Temperature> temperatureList;
public TemperatureList()
{
this.temperatureList = new ArrayList<>();
}
public void addTemperatureDataToList(Temperature temperature)
{
temperatureList.add(0,temperature);
}
public Temperature getLatestTemperatureDataFromList()
{
return this.temperatureList.get(0);
}
public void clearTemperatureDataList()
{
temperatureList.clear();
}
public int size()
{
return temperatureList.size();
}
}
Here is where I launch the GUI:
import java.rmi.RemoteException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class userMain extends Application
{
public FXMLLoader loader;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML/FXMLtemperature.fxml"));
loader.setController(new GUItemperatureController());
Parent root = loader.load();
Scene scene = new Scene(root);
primaryStage.setTitle("GEMS - Test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Your problem is not about classes.
You run two separate applications. One runs your RMIsensorClient and one runs your GUI. They know nothing about each other, your RMIsensorClient and your Controller have their own separate instances of ModelManager and you have no code anywhere that would share any data between them.
You need to make the data you want to show in your GUI accessible somehow.
One solution could be to use a network interface for that. Create two different ModelManagers, one that opens and listens to a ServerSocket, and one that uses a Socket in getLatestTemperatureData() to connect to the other one.
Use the former in your RMIsensorClient and the latter in your GUI's Controller.
Research networking in Java.
This is a very crude solution, but there are plenty of great tutorials for networking and sharing data between multiple Java applications.
You haven't included your TemperatureList implementation, but if as you say it's an ArrayList it's likely you're not properly synchronizing access across threads. It's imperative that you properly synchronize cross-thread work otherwise you'll wind up with some sort of undefined behavior, such as changes not propagating or data structures winding up broken.
There are a number of thread-safe collections in java.util.collect.concurrent that you might consider, otherwise you'll need to ensure you use synchronized blocks or methods anywhere you're working with this list.
The most obvious problem that I found(and this might not be all) is that your array list is method specific. it is not static. Meaning that it can only be accessed by the method it originates in. the easiest fix for this is to add a static modifier to your array list when it is created and create it outside of the methods.
I'm trying to implement TextFX in my project to do some UI testing. However it seems I can't get it to work properly. I've downloaded the jars from http://search.maven.org/#search%7Cga%7C1%7Ctestfx to a folder named 'TestFX-3.1.2' on my system.
Afterwards I've created a new library in Netbeans8 pointing to those jar files (jar, source and javadoc). As a matter of test I've created a simple Java FXML project with the new library added.
public class Test2 extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Next to that I have a controller for my FXML file with the following generated code:
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
To implement the TestFX side, I've created a new class that extends GuiTest:
package test2;
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import org.loadui.testfx.GuiTest;
public class TestTheThing extends GuiTest {
#Override
protected Parent getRootNode() {
FXMLLoader loader = new FXMLLoader();
Parent node = null;
try {
node = loader.load(this.getClass().getResource("FXMLDocument.fxml").openStream());
} catch (IOException e) {
System.out.println(e.toString());
}
return node;
}
#Test //<-- this Annotiation does not work
public void pressTheButton(){
//TODO
}
}
As said above in the code, the #Test simply does not work and is red underlined with the warning 'cannot find symbol'. Could anyone point me in the right direction about what I'm doing wrong?
According to https://repo1.maven.org/maven2/org/loadui/testFx/3.1.2/testFx-3.1.2.pom, testFx has several dependencies (guava, junit, hamcrest-all, hamcrest-core). To work correctly, you need to add the jars corresponding to these dependencies to your project. However, using maven is the recommended approach for that.
Don't load your fxml file directly in the test class as it may not work intentionally. Instead launch main class this way:
FXTestUtils.launchApp(Test2.class);
Thread.sleep(2000);
controller = new GuiTest()
{
#Override
protected Parent getRootNode()
{
return Test2.getStage().getScene().getRoot();
}
};
Create a static method getStage() in your Test2 class that returns the Stage. The above code should reside in a method annoted with #BeforeClass in your test class. The controller is a static reference to the GuiTest.
In the end your test class should look something like this:
import java.io.IOException;
import javafx.scene.Parent;
import org.loadui.testfx.GuiTest;
import org.loadui.testfx.utils.FXTestUtils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class TestTheThing
{
public static GuiTest controller;
#BeforeClass
public static void setUpClass() throws InterruptedException, IOException
{
FXTestUtils.launchApp(Test2.class);
Thread.sleep(2000);
controller = new GuiTest()
{
#Override
protected Parent getRootNode()
{
return Test2.getStage().getScene().getRoot();
}
};
}
#Test
public void testCase()
{
System.out.println("in a test method");
}
}
In this case you will not need to extend from GuiTest. And, don't forget to create static getStage() in Test2 class. Hope this will help. This is working fine in my case.
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.
I have simple main class.
There i try pass user to WindowLogin :
package client;
public class Client{
public User user;
public static void main(String[] args) {
try {
Client client = new Client();
client.run(args);
} catch (Exception e) {
System.out.println("[ERR] Fatal error");
}
}
public void run(String[] args)
{
user = new User();
WindowLogin windowLogin = new WindowLogin();
windowLogin.user = user;
windowLogin.show();
}
}
Window main class. There i try call test() function of user (in real, i need it pass to WindowMainController):
package client;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Parent;
import java.io.IOException;
public class WindowLogin extends Application{
private Stage stage;
public User user;
#Override
public void start(Stage primaryStage) throws Exception {
stage = new Stage();
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("views/WindowLogin.fxml"));
WindowLoginController controller =
fxmlLoader.<WindowLoginController>getController();
user.test();
Parent root = fxmlLoader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public void show(){
launch();
}
public void hide() { stage.hide(); }
}
When i try to run it all:
Exception in Application start method
Of course (maybe :) ) it because of user in windowLogin is null.
What i doing wrong? How pass user to windowLogin? (i wont use Singletone)
Update:
I need use user in start() method, as i said before - i need pass user to WindowMainController
OverView
The problem you are facing here is on calling launch(), Javafx thread creates a new object of WindowLogin. So the object that you have created for WindowLogin and assigned user to it is no longer used in the start method !
WindowLogin windowLogin = new WindowLogin();
windowLogin.user = user;
You can overcome this by declaring the User in WindowLogin as static !
public static User user;
This will help to just keep on instance of the User