variable recUsername is assigned with a value passed by another controller. But after initialization when recUsername is printed inside BtnClicked method, it prints null. why?
public class nextSceneController implements Initializable {
private String recUsername;
public void transferMessage(String message){
recUsername = message;
}
#FXML
private Button Btn;
#FXML
void BtnClicked(ActionEvent event) {
System.out.println(recUsername);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(recUsername);
}
}
This is MenuController.java which is linked to menu.fxml
public class MenuController {
public MenuController() throws SQLException, ClassNotFoundException {
}
String orderRefNo = Key_Generator.getReferenceKey();
FXMLLoader loader = new FXMLLoader(getClass().getResource("orderList.fxml"));
Parent root = loader.load();
OrderListController orderListController = loader.getController();
orderListController.transferMessage(orderRefNo);
Stage orderListStage = new Stage();
orderListStage.setScene(new Scene(root));
orderListStage.show();
}
This is OrderListController which is linked to orderList.fxml
public class OrderListController implements Initializable {
public OrderListController() throws SQLException, ClassNotFoundException, IOException {
}
private String orderRefNoRec;
public void transferMessage(String message){
orderRefNoRec = message;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
try{
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `FoodName`, `Quantity`, `Price` FROM `orders` WHERE OrderRefNo='"+orderRefNoRec+"'");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
observableList.add(new orderListTableModel(resultSet.getString("FoodName"), resultSet.getString("Quantity"),
resultSet.getString("Price")));
}
} catch (SQLException e) {
e.printStackTrace();
}
food_OrderListTableModel.setCellValueFactory(new PropertyValueFactory<>("foodName"));
quantity_OrderListTableModel.setCellValueFactory(new PropertyValueFactory<>("qty"));
price_OrderListTableModel.setCellValueFactory(new PropertyValueFactory<>("price"));
orderListTableModel.setItems(null);
orderListTableModel.setItems(observableList);
}
}
This is a case where you need to illustrate out your program flow, to know when you are able to call up the set variable.
Below an illustration on where it probably goes wrong(Can't say for certain as I don't have access to the full code, but this is an oversimplification of the most likely issue at hand):
Your main scene controller creates a new nextScene.
The business logic then calls initialize to pass along all necessary resources etc...
You call print here, but transferMessage hasn't been called yet at this point, so the recUserMessage variable is still null.
You then probably call transferMessage, which sets the variable, but printing is then never called.
When you come across such a problem in the future, where something doesn't do what you think it should do, draw it out, step by step what you think is happening to pin point where things should happen and where your expectations should be adapted.
Related
I have looked into using ControllerFactory a lot, to allow this code to be instantiated from a database, and have cross-controller compatibility. But with my original setup different from others I found online, I found it extremely hard to follow along, and use what would fit into my program from theirs. Any advice on where to start?
Current Controller creation -
// get Main Class package name to get correct files path
String pathRef = mainRef.getClass().getPackage().getName();
// set FXRouter current route reference
currentRoute = route;
// create correct file path. "/" doesn't affect any OS
String scenePath = "/" + pathRef + "/" + route.scenePath;
// Creates controller for route
Controller_Factory cf = new Controller_Factory();
Object controller = cf.CreateController(route.scenePath);
FXMLLoader loader = new FXMLLoader(controller.getClass().getResource(scenePath));
loader.setController(controller);
Parent root = loader.load();
// set window title from route settings or default setting
window.setTitle(route.windowTitle);
// set new route scene
window.setScene(new Scene(root, route.sceneWidth, route.sceneHeight));
// show the window
window.show();
}
Controller Example-
public class BuyController extends Controller {
#FXML
public Button CloseAppButton;
#FXML public Button SwitchToProfileButton;
#FXML public Button SwitchToSellButton;
#FXML public Button SwitchToBuyButton;
#FXML public Button SwitchToMainButton;
#FXML public TextField BuyText;
String AmountBought;
public void initialize (URL location, ResourceBundle resources){
CloseAppButton.setPrefHeight(30);
CloseAppButton.setPrefWidth(56);
SwitchToBuyButton.setPrefHeight(30);
SwitchToBuyButton.setPrefWidth(56);
SwitchToMainButton.setPrefHeight(30);
SwitchToMainButton.setPrefWidth(56);
SwitchToSellButton.setPrefHeight(30);
SwitchToSellButton.setPrefWidth(56);
SwitchToProfileButton.setPrefHeight(30);
SwitchToProfileButton.setPrefWidth(56);
}
public void OnBuyButton (ActionEvent event) {
AmountBought = BuyText.getText();
System.out.println("You have bought " + AmountBought + " of crypto");
BuyText.clear();
}
#Override
public void initilize(URL url, ResourceBundle rb) {
}
}
Current Controller_Factory-
public class Controller_Factory {
private static final Controller_Factory instance = new Controller_Factory();
public static Controller_Factory getInstance() {
return instance;
}
public Object CreateController (String routeScenePath) throws IllegalArgumentException, IOException {
Object controller = null;
switch (routeScenePath) {
case "Buy.fxml":
controller = new BuyController();
break;
case "Error.fxml":
controller = new ErrorController();
break;
case "Home.fxml":
controller = new HomeController();
break;
case "Profile.fxml":
controller = new ProfileController();
break;
case "Sell.fxml":
controller = new SellController();
break;
default:
}
System.out.println(routeScenePath);
return controller;
}
}
How would I pass this info with the said controller? (This is not real code I have, but an example of configuration JSON I want to pass with the controller.)
"HomePage": {
"ValidPages": [
"BuyPage",
"SellPage"
],
"InternalID": "HP"
},
"BuyPage": {
"ValidPages": [
"HomePage"
],
"InternalID": "BP",
"Cryptos": [
"BTC",
"LTC"
]
The controller factory is simply a Callback<Class<?>, Object> whose call(Class<?> type) function takes the class defined in the fx:controller attribute in the FXML file and returns the object to be used as the controller. This is invoked by the FXMLLoader at the time the FXML is loaded.
I think your question is asking if you can use a controller factory to automatically populate controllers with data that's stored in JSON, which will be read at runtime.
You can do something like this:
public class NavigationInfo {
private final Map<String, PageNavigationInfo> pageInfoPerPage ;
public NavigationInfo(Map<String, PageNavigationInfo pageInfoPerPage) {
this.pageInfoPerPage = pageInfoPerPage;
}
public PageNavigationInfo getInfoForPage(String page) {
return pageInfoPerPage.get(page);
}
}
public class PageNavigationInfo {
private final String internalID ;
private final List<String> validPages ;
private final List<String> cryptos ;
// .... etc
}
public class NavigationControllerFactory implements Callback<Class<?>, Object> {
private final NavigationInfo navigationInfo ;
public NavigationControllerFactory() {
// read and parse JSON and create NavigationInfo instance
}
#Override
public Object call(Class<?> type) {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(NavigationInfo.class)) {
return c.newInstance(navigationInfo);
}
}
// no suitable constructor, just use default constructor as fallabck
return type.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Now just define the fx:controller attribute in each FXML in the usual way. E.g. for Buy.fxml do
<BorderPane ... fx:controller="com.yourcompany.yourproject.BuyController">
<!-- ... -->
</BorderPane>
Then
public class BuyController {
private final PageNavigationInfo navInfo ;
public BuyController(NavigationInfo navigationInfo) {
this.navInfo = navigationInfo.getInfoForPage("BuyPage");
}
#FXML
private void initialize() {
// do whatever you need with navInfo
}
}
I am trying to change one of my columns and its cell values from another class however i keep getting a null pointer exception when java try's to execute that line OverviewController.getOverviewController.returnStatusColumn.setCellValueFactory(
cellData -> cellData.getValue().getStatusProperty());, I have removed all irreverent code
TableView Class:
#FXML
private TableView<Task> taskTable;
#FXML
private TableColumn<Task, String> statusColumn;
private final static OverviewController controller = new OverviewController
();
public static OverviewController getOverviewController() {
return controller;
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
taskTable.setItems(mainApp.getTaskData());
taskTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
taskTable.setPlaceholder(new Label(""));
}
public TableView<taskTable> returnTasks() {
return taskTable;
}
public TableColumn<taskTable, String> returnStatusColumn() {
return statusColumn;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
statusColumn.setCellValueFactory(cellData ->
cellData.getValue().getStatusProperty());
}
#FXML
public void createTask(ActionEvent event) throws InterruptedException,
IOException, ParseException {
thread = new MyThread();
thread.main(null);
statusColumn.setCellValueFactory(cellData ->
cellData.getValue().getStatusRunningProperty());
statusColumn.setStyle("-fx-text-fill: green; -fx-font-weight: bold;");
taskTable.refresh();
}
#FXML
public void stopTasks() {
statusColumn.setCellValueFactory(cellData ->
cellData.getValue().getStatusProperty());
statusColumn.setStyle("-fx-text-fill: red; -fx-font-weight: bold;");
taskTable.refresh();
}
This class works fine when i want to update the table columns, if i click stop tasks method (which is linked to a button) the status column gets updated to the stop label which i want to do, same with start tasks method.
Random class where i want to update the Table view status column:
public class UpdateTable {
public static void main(String[] args) {
OverviewController.getOverviewController.returnStatusColumn.setCellValueFactory(
cellData -> cellData.getValue().getStatusProperty());
OverviewController.getOverviewController().returnTasks().refresh();
}
}
TableView Data:
//Status Information
private final SimpleStringProperty status;
private final SimpleStringProperty statusRunning;
public Task() {
this(null, null);
}
public Task() {
this.statusRunning = new SimpleStringProperty("Running");
this.status= new SimpleStringProperty("Stop");
}
public StringProperty getStatusProperty( ) {
return status;
}
public StringProperty getStatusRunningProperty( ) {
return statusRunning;
}
}
If i ran the random class it will lead to a null pointer exception in particular this line:
OverviewController.getOverviewController().returnStatusColumn().setCellValueFactory(cellData -> cellData.getValue().getStatusProperty());
Have i done this completely the wrong way? I just want to be able to update the Table view column cells from a different class.
Yes, you're doing this the wrong way.
You don't use a Application subclass anywhere which needs to be used as entry point of your application (assuming you're not using JFXPanel or Platform.startup). Furthermore you access the column as first statement in your program which means there's no way the statusColumn field is initialized.
Also usually there shouldn't be a need to involve any class but the controller class for initializing the cellValueFactory. Especially using static fields is a bad approach:
Assuming you specify the controller class in the fxml, FXMLLoader creates a new instance of the controller class. This instance is different from the instance stored in the controller field so even when using
OverviewController.getOverviewController.returnStatusColumn.setCellValueFactory(
cellData -> cellData.getValue().getStatusProperty());
after loading the fxml you wouldn't get the instance you need.
Instead I recommend using the initialize method of the controller class for these kind of initialisations. It's invoked by FXMLLoader after creating and injecting all the objects specified in the fxml.
public class OverviewController {
...
#FXML
private TableColumn<Task, String> statusColumn;
...
#FXML
private void initialize() {
statusColumn.setCellValueFactory(cellData -> cellData.getValue().getStatusProperty());
}
}
If you do need to pass some info from an class that is not the controller, refer to the answers here Passing Parameters JavaFX FXML . Better approaches than using static are described in the answers.
In my project when a client will be disconnected, server will delete the name from observable list and the tableview should stop showing the name. But the tableview is not updating.
Controller class
public class Controller {
#FXML
public TableView tableView;
#FXML
private TableColumn<clientLoginData,String> client;
#FXML
private TableColumn<clientLoginData,String> activeTime;
void initialize(ObservableList<clientLoginData> data)
{
client.setCellValueFactory(new PropertyValueFactory<>("clientName"));
client.setCellFactory(TextFieldTableCell.<clientLoginData>forTableColumn());
activeTime.setCellValueFactory(new PropertyValueFactory<>("time"));
activeTime.setCellFactory(TextFieldTableCell.<clientLoginData>forTableColumn());
tableView.setItems(data);
tableView.setEditable(true);
}
}
main class
public class Main extends Application{
volatile public ObservableList<clientLoginData> data= FXCollections.observableArrayList();
public Controller controller;
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("server.fxml"));
Parent root = loader.load();
data.addAll(new clientLoginData((new SimpleStringProperty("john")),new SimpleStringProperty(ZonedDateTime.now().getHour()+":"+ZonedDateTime.now().getMinute())));
controller=loader.getController();
controller.initialize(data);
primaryStage.setTitle("Server");
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
Thread t=new Thread(new messengerServer(this));
t.start();
}
public static void main(String[] args) {
launch(args);
}
}
updating class
public class messengerReadThread implements Runnable {
private Thread thr;
private NetworkUtil nc;
public Hashtable<SimpleStringProperty, NetworkUtil> table;
SimpleStringProperty oldName;
Main main;
public messengerReadThread(NetworkUtil nc, Hashtable<SimpleStringProperty, NetworkUtil> table, SimpleStringProperty s, Main main) {
this.nc = nc;
this.thr = new Thread(this);
thr.start();
this.table=table;
oldName=s;
this.main=main;
}
public void run() {
try {
while(true) {
String s1=(String)nc.read();
StringTokenizer st=new StringTokenizer(s1);
if(st.nextToken().equals("Name"))
{
String sn=s1.substring(5,s1.length());
NetworkUtil n1=table.get(oldName);
table.remove(oldName);
oldName=new SimpleStringProperty(sn);
table.put(oldName, n1);
main.data.add(new clientLoginData(oldName,new SimpleStringProperty(ZonedDateTime.now().getHour()+":"+ZonedDateTime.now().getMinute())));
}
else
{
System.out.println("here it is"+s1);
}
}
} catch(Exception e) {
System.out.println("disconnected "+oldName.toString());
main.data.remove(oldName);
//System.out.println(main.data.contains(oldName));
main.controller.tableView.refresh();//the tableview should update
}
nc.closeConnection();
}
}
There are some modification I should to to that code, like avoid using those "static references", by defining the ObservableList and move your Updating Code inside Controller so you can have a 2 classes code, the Main Class and your Controller... but i'll try to keep it simple.
First, you need to define the ObservableList inside you controller.
Then place your "updating" code inside the controller in a method. I suggest you to use a Task<> to keep your controller updated in the JavaFX Thread.
Try something like this:
private void updateTable(){
Task<Void> myUpdatingTask=new Task<Void>() {
#Override
protected Void call() throws Exception {
//Your Updating Code Here
}
}
//and then you run it like this:
Thread hilo=new Thread(myUpdatingTask);
hilo.setDaemon(true);
hilo.start();
}
Then, remove the parameter from your Initialize Method and define it private with the #FXML annotation like this:
#FXML
private void initialize(){
//Your Stuff to initialize
//here is were you fill your table like you did in the Main
//and don't forget to call you updateTable Method
this.updateTable();
}
Since this a dirty hack as pointed out by #kleopatra I am going to decorate it as a dirty hack.
****************************Dirty Hack*****************************************
Try hiding a column and displaying it again and your tableview should refresh
I recently started programming with JavaFx. I have the following Problem:
I am writing a controller class, in which I hava a Table with orders, but the content can only be set in the initialize method.
The List with orders should be set in the orderTable with the method setorderList. The problem is that the Orders are not shown in the TableView.
I have already tried to trigger an update of the TableView by removing the items of the Table and filling them up again.
For test purposes i created the orderList with a test-order in the initialize() method and set them to the table with orderTable.setItems(orderList). When i did this it worked perfectly.
So the order from the initialize method (with number 23) is shown in the Table but the List thats set in setOrderList is not.
This confuses me, because it seems like the orderTable.setItems(orderList) Statement only works in the initialize() method.
Here is my code:
public class OrderOverviewController implements Initializable{
#FXML
private TableView<Order> orderTable;
#FXML
private TableColumn<Order,Integer> orderNumberColumn;
private ObservableList<Order> orderList;
private OrderDao orderDao;
#Override
public void initialize(URL location, ResourceBundle resources) {
orderNumberColumn.setCellValueFactory(new PropertyValueFactory<>("orderId"));
orderList=FXCollections.observableArrayList();
//ordernumber and Current user
orderList.add(new Order(23,null));
orderTable.setItems(orderList);
}
public void setOrderList(ObservableList<Order> orderList) {
orderTable.setItems(orderList);
}
}
This is the start method of the main class where i setup the scenes for my application. First the LoginN.fxml gets loaded, and the mainScene is only set up. After this the LoginManager gets created.
#Override
public void start(Stage primaryStage) throws IOException {
this.stage=primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("/view /LoginN.fxml"));
loginScene=new Scene(root);
primaryStage.setScene(loginScene);
primaryStage.show();
root=null;
root = FXMLLoader.load(getClass().getResource("/view/Main.fxml"));
mainScene=new Scene(root);
new LoginManager(this);
}
So here is my LoginManager which has an static instance of itself, so i can access it easier.
The method setLoggedInUser sets the user after the login procedure as you can see in the next code sample which contains the method login.
Here i get all orders of the user, with which i call then the setOrderMethod of the controller where my Problem with the table is.
public class LoginManager {
private static LoginManager loginManager;
private Main main;
private User loggedInUser;
private OrderDao orderDao;
private MainController mainController;
private OrderOverviewController ooc;
public LoginManager(Main main){
loginManager=this;
this.main=main;
}
public static LoginManager getInstance(){
return loginManager;
}
public void setMainView(){
main.setMainView();
}
public void setLoggedInUser(User loggedInUser) {
this.loggedInUser = loggedInUser;
//load OrderViewController to set Previous orders
try {
URL location=getClass().getResource("/view/Orders.fxml");
FXMLLoader loader=new FXMLLoader();
loader.setLocation(location);
loader.setBuilderFactory(new JavaFXBuilderFactory());
Parent root=(Parent)loader.load(location.openStream());
orderDao=new OrderDao();
ooc=loader.getController();
ooc.setOrderList(orderDao.getOrdersOfUser(loggedInUser));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Here is the login method of the LoginController which gets triggered when the loginButton is pressed:
public void login() throws Exception{
userName=usernameField.getText();
password=AESCrypt.encrypt(passwordField.getText());
if(userdao.isUserValid(userName, password))
{
LoginManager.getInstance().setMainView();
LoginManager.getInstance().setLoggedInUser(userdao.getUserByName(userName));
}
else
errorLabel.setText("Login failed!");
}
If the userData was valid the mainScene gets set up with the LoginManager and the logged in user is set.
This is how i included the Orders.fxml (The view of the OrderOverviewController) in a Tab in the Main view:
<fx:define>
<fx:include source="Orders.fxml" fx:id="orderOverviewContent"/>
</fx:define>
<Tab content="$orderOverviewContent" text="Order Overview" />
Thank you for your help.
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.