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
public class MainViewController {
// ...
protected TextField inDesiredVelocity;
// ...
}
it means that every instance of MainViewController
will have a field called inDesiredVelocity
of type TextField
.
When you do
public abstract class CanMessage extends MainViewController {
// ...
}
it means that every instance of CanMessage
is also an instance of MainViewController
.
When you load the FXML, the FXMLLoader
creates an instance of MainViewController
(because you have fx:controller="canbusgui.MainViewController"
in the FXML) and initializes the inDesiredVelocity
field in that instance with a reference to the text field declared in the FXML.
Later in your controller, you do
new PcToVcuMessage("222")
which, of course, creates a new instance of PcToVcuMessage
with id "222"
. Since PcToVcuMessage
inherits from CanMessage
, that new instance is also an instance of CanMessage
, and since CanMessage
inherits from MainViewController
, that instance is also an instance of MainViewController
, and since every instance of MainViewController
has a field inDesiredVelocity
, this new instance of PcToVcuMessage
has a field called inDesiredVelocity
of type TextField
.
However, at no point do you initialize that field (and there's no sensible way to do so), so the inDesiredVelocity
field in your PcToVcuMessage
is 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 TextField
to 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 do
class PcToVcuMessage extends CanMessage {
private int desiredVelocity ;
public PcToVcuMessage(String id, int desiredVelocity) {
// Call the superclass constructor
super(id);
this.desiredVelocity = desiredVelocity;
}
// Override the constructData method
@Override
public void constructData() {
data[0] = (byte) 0;
data[1] = (byte) 0;
data[2] = (byte) 0;
data[3] = (byte) desiredVelocity;
data[4] = (byte) 0;
data[5] = (byte) 0;
data[6] = (byte) 0;
data[7] = (byte) 0;
}
public void validateInputs() throws Exception{}
}
and in the controller replace new PcToVcuMessage("222")
with
new PcToVcuMessage("222", Integer.parseInt(inDesiredVelocity.getText()))
Then just remove the extends MainViewController
from the CanMessage
class. This obviously makes no sense at all (a message is not a thing that controls a UI).
Some unrelated issues with your code:
CanMessage
is a verb (or verbal phrase). Probably Message
is more appropriate, but again I don't really understand what you are modeling here.