Problem
I'm trying to implement the transmission of messages over a Controller Area Network, where the messages are built from user's inputs through a GUI created with JavaFX.
I have a MainController class which is linked to a Main.fxml. In the MainController I have defined an FXML annotated TextField attribute in_desiredVelocity which is correctly linked to its fx:id in Main.fxml.
Then I have defined an abstract class canMessage which defines the backbone of a message which has to be sent over the network.
Now the class PcToVcuMessage implements a particular type of canMessage. In order to be able to access the FXML attribute (defined in MainController) I've decided that the abstract class canMessage extends MainController, and PcToVcuMessage extends canMessage.
The application compiles correctly but when I enter, in the GUI, the TextField in_desiredVelocity a NullPointerException is launched.
Question
Despite the above FXML attribure is inherited by PcToVcuMessage (which inherits from the abstract class canMessage and this one extends the MainController), how can I use it in this class to achieve my goal?
Main:
package canbusgui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class MainApplication extends Application {
public static Stage stage = null;
@Override
public void start(Stage stage) throws IOException {
stage.setOnCloseRequest(event -> {
System.exit(0);
});
MainApplication.stage = stage;
// Create a FXMLLoader to load the FXML file that defines the user interface
FXMLLoader fxmlLoader = new FXMLLoader(MainApplication.class.getResource("MainView.fxml"));
Scene scene = new Scene(fxmlLoader.load());
stage.setTitle("CANbus GUI");
stage.setScene(scene);
stage.setResizable(false);
stage.setMinHeight(768);
stage.setMinWidth(1366);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
MainController:
package canbusgui;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class MainViewController {
@FXML
protected TextField in_desiredVelocity;
@FXML
public void initialize (){
in_desiredVelocity.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ENTER) {
try {
sendMessage(new PcToVcuMessage("222"));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
public void sendMessage(canMessage message) throws Exception {
message.constructData();
message.validateInputs();
byte[] data = message.getData();
// Send the data array to the CAN bus
canBusController.sendCommand(HexFormat.fromHexDigits(message.getId()), data);
}
}
canMessage.java (it contains an abstract class canMessage and PcToVcuMessage extends it):
package canbusgui;
import static java.lang.Integer.parseInt;
public abstract class canMessage extends MainViewController{
// Declare common attributes
protected String id;
protected byte[] data;
public canMessage(String id) {
this.id = id;
// Initialize an empty byte array for data
this.data = new byte[8];
}
// Define an abstract method to construct the data array
public abstract void constructData();
// Define an abstract method to validate the inputs
public abstract void validateInputs() throws Exception;
// Define a getter method for the data array
public byte[] getData() {
return this.data;
}
public String getId() {
return this.id;
}
}
// Define a subclass for PC_to_VCUMessage message
class PcToVcuMessage extends canMessage {
public PcToVcuMessage(String id) {
// Call the superclass constructor
super(id);
}
// Override the constructData method
@Override
public void constructData() {
data[0] = (byte) 0;
data[1] = (byte) 0;
data[2] = (byte) 0;
data[3] = (byte) parseInt(in_desiredVelocity.getText()); //HERE in_desiredVelocity is null and a NillPointerException is launched
data[4] = (byte) 0;
data[5] = (byte) 0;
data[6] = (byte) 0;
data[7] = (byte) 0;
}
public void validateInputs() throws Exception{}
}
EDIT
The format of the CAN message is the following: ID(hex), data0, data1, data2, data3, ......, data7. So, when I call the PcuToVcuMessage's constructor in the controller I'm passing the ID of the message 222 (which, btw, is specified in the data sheet of the device)
In PcuToVcuMessage I need to acess to the FXML attribute in_desiredVelocity which has been set by the user by typing the value in the TextField of the GUI: in this way it is possible to retrieve the value typed by the user in order to build the message.
EDIT2
Since there can be multiple messages with different IDs, I have thought to use polymorphism in the sendMessage method in the controller. Furthermore there can be messages which needs to access more than one FXML attribute from controller class.
This simply isn't what inheritance does. Inheritance is a relationship between classes, not between objects.
When you do
it means that every instance of
MainViewControllerwill have a field calledinDesiredVelocityof typeTextField.When you do
it means that every instance of
CanMessageis also an instance ofMainViewController.When you load the FXML, the
FXMLLoadercreates an instance ofMainViewController(because you havefx:controller="canbusgui.MainViewController"in the FXML) and initializes theinDesiredVelocityfield in that instance with a reference to the text field declared in the FXML.Later in your controller, you do
which, of course, creates a new instance of
PcToVcuMessagewith id"222". SincePcToVcuMessageinherits fromCanMessage, that new instance is also an instance ofCanMessage, and sinceCanMessageinherits fromMainViewController, that instance is also an instance ofMainViewController, and since every instance ofMainViewControllerhas a fieldinDesiredVelocity, this new instance ofPcToVcuMessagehas a field calledinDesiredVelocityof typeTextField.However, at no point do you initialize that field (and there's no sensible way to do so), so the
inDesiredVelocityfield in yourPcToVcuMessageis null.It doesn't make any sense to do any of this. I don't really understand what your domain model is (and I probably don't need to to answer this question), but it doesn't make any sense for a
TextFieldto be part of an object whose type is some kind of message.Instead, it probably makes sense for the data that is being sent by this message to be part of the
PcToVcuMessage. I.e. you can doand in the controller replace
new PcToVcuMessage("222")withThen just remove the
extends MainViewControllerfrom theCanMessageclass. This obviously makes no sense at all (a message is not a thing that controls a UI).Some unrelated issues with your code:
CanMessageis a verb (or verbal phrase). ProbablyMessageis more appropriate, but again I don't really understand what you are modeling here.