Java updating Table view cell values from another class - java

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.

Related

How to change this JavaFX into using a ControllerFactory

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
}
}

SetText To textfiled or lable From other Window javafx

lets say you have lable in MainWindow ..
and you want change value of this lable
from Window2
Not in Same Window !!
i want the changes while the MainWindow its open
It's easy if ur using 2 .fxml files with there own controllers
if that's the case create a new class file namely 'AllControllers'
you have two 2 controllers namely ControllerWindow1 and ControllerWindow2
public class AllControllers {
private static ControllerWindow1 control1;
private static ControllerWindow2 control2;
public static ControllerWindow1 getControl1() {
return control1;
}
public static void setControl1(ControllerWindow1 control1) {
Controlls.control1 = control1;
}
public static ControllerWindow2 getControl2() {
return control2;
}
public static void setControl2(ControllerWindow2 control2) {
Controlls.control2 = control2;
}
}
You have to initialize each controller like this
public class ControllerWindow1 implements Initializable{
#FXML
public Label mylabel;
#Override
public void initialize(URL location, ResourceBundle resources) {
AllControllers.setControl1(this);
}
}
Now you can access your controller from any class. Just use
AllControllers.getControl1().mylabel.setText("hello");

Why does adding a View after initialization throws NLP but not during initialization, using afterburner.fx

I am using afterburner.fx http://afterburner.adam-bien.com/
It works as advertised. I can add multiple fxml Files to a central/main "view".
But if I want to add another fxml/presenter later, for example, using a button on a different navigationPane to add another fxml to the mainAnchorPane.
Then it throws a NullPointerException.
public class MainscenePresenter implements Initializable {
#FXML
AnchorPane breadcrumbAnchor;
#FXML
AnchorPane navigationAnchor;
//--------------------------------------------------------
#FXML
private AnchorPane mainAnchorPane; //ADD NEW ATPANE HERE
private AtPresenter atPresenter;
private AtView atView;
//--------------------------------------------------------
#Override
public void initialize(URL url, ResourceBundle rb) {
//add BreadCrumBar WORKS
BreadcrumbbarView breadcrumbbarView = new BreadcrumbbarView();
breadcrumbbarView.getViewAsync(breadcrumbAnchor.getChildren()::add);
//add DFD WORKS
DfdView dfdView = new DfdView();
Parent view2 = dfdView.getView();
this.mainAnchorPane.getChildren().add(view2);
//add Navigation WORKS
NavigationView navigationView = new NavigationView();
Parent view = navigationView.getView();
navigationAnchor.getChildren().add(view);
//add AT
this.atView = new AtView();
this.atPresenter = (AtPresenter) this.atView.getPresenter();
//ADDING AT VIEW LIKE THIS WORKS <=========================
this.showAt();
}
void showAt() {
this.mainAnchorPane.getChildren().add(this.atView.getView()); // <== NLP here if invoked with buttonAt
}
public void buttonAt() {
//ADDING AT VIEW LIKE THIS(Button on different Presenter) DOES NOT WORK => NLP
this.showAt();
}
}
public class NavigationPresenter implements Initializable {
#FXML
Button atNavButton;
#Inject
MainscenePresenter mainscene;
private ResourceBundle resources = null;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.resources = resources;
}
#FXML
void showDfdScene(ActionEvent event) {
mainscene.buttonAt();
}
}
It seems I don't understand some central mechanism of JavaFX! And can't name it, to look it up!
Why does it throw NullPointerException in this case and not durin initialization?
Caused by: java.lang.NullPointerException
at abc.abc.app.mainscene.MainscenePresenter.showAt(MainscenePresenter.java:107)
at abc.abc.app.mainscene.MainscenePresenter.buttonAt(MainscenePresenter.java:112)
at abc.abc.app.navigation.NavigationPresenter.showDfdScene(NavigationPresenter.java:41)
... 58 more
Afterburner.fx is a dependency-injection framework for JavaFX. The main functionality it provides is the ability to inject objects into the controllers/presenters that are created when you load an FXML file (by instantiating a subclass of FXMLView). The basic process that happens when you instantiate a FXMLView is:
A new instance of the corresponding presenter is created
The presenter is inspected to find any #Inject-annotated fields
For each #Inject-annotated field, if an instance of that type exists in the injector's cache, it is set as the value of that field. Otherwise, a new instance of that type is created and placed in the cache, and set as the value of the field.
The main point to note here is that the presenters themselves are treated differently to their dependencies. If you try (as in your code) to inject one presenter in another, an instance of the presenter class will be created specifically for injection purposes: this will not be the same instance that is created when the FXML file is loaded, and consequently it won't have any #FXML-fields injected. This is why you get a null pointer exception: mainAnchorPane is null in the ``MainScenePresenterthat is injected into theNavigationPresenter`.
One presenter having a reference to another is generally a bad idea anyway: it creates unnecessary coupling between the two presenters. Instead, you should inject a model into both presenters that represents the state you want to share between them. In your case you might have something like
public class ViewState {
private final BooleanProperty atShowing = new SimpleBooleanProperty();
public BooleanProperty atShowingProperty() {
return atShowing ;
}
public final boolean isAtShowing() {
return atShowingProperty().get();
}
public final void setAtShowing(boolean atShowing) {
atShowingProperty().set(atShowing);
}
}
Now in your presenters, do
public class MainscenePresenter implements Initializable {
#Inject
private ViewState viewState ;
#FXML
AnchorPane breadcrumbAnchor;
#FXML
AnchorPane navigationAnchor;
//------------------------------------------------------
#FXML
private AnchorPane mainAnchorPane; //ADD NEW ATPANE HERE
private AtPresenter atPresenter;
private AtView atView;
//------------------------------------------------------
#Override
public void initialize(URL url, ResourceBundle rb) {
//add BreadCrumBar WORKS
BreadcrumbbarView breadcrumbbarView = new BreadcrumbbarView();
breadcrumbbarView.getViewAsync(breadcrumbAnchor.getChildren()::add);
//add DFD WORKS
DfdView dfdView = new DfdView();
Parent view2 = dfdView.getView();
this.mainAnchorPane.getChildren().add(view2);
//add Navigation WORKS
NavigationView navigationView = new NavigationView();
Parent view = navigationView.getView();
navigationAnchor.getChildren().add(view);
//add AT
this.atView = new AtView();
this.atPresenter = (AtPresenter) this.atView.getPresenter();
this.viewState.atShowingProperty().addListener((obs, wasShowing, isNowShowing) -> {
if (isNowShowing) {
this.mainAnchorPane.getChildren().remove(this.atView.getView());
} else {
this.mainAnchorPane.getChildren().add(this.atView.getView());
}
});
}
}
and
public class NavigationPresenter implements Initializable {
#FXML
Button atNavButton;
#Inject
private ViewState viewState ;
private ResourceBundle resources = null;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.resources = resources;
}
#FXML
void showDfdScene(ActionEvent event) {
viewState.setAtShowing(true);
}
}

Auto Updating Tableview in JAVA Fx

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

Add data to static ComboBox in JavaFX 2.2

I just can't figure out how to add data to static ComboBox in JavaFX 2.2. Whatever I try to do ComboBox is empty. Here is the code:
#FXML private MenuItem menuItemNewTile;
#FXML private static ComboBox<Tile> comboBoxTileList;
#FXML
private void menuItemNewTileSetOnAction(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource(TILE_WINDOW_URL));
Scene scene = new Scene(root);
Stage tileStage = new Stage();
tileStage.setScene(scene);
tileStage.show();
}
#FXML
private void comboBoxTileListSetOnAction(ActionEvent event) {
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
comboBoxTileList = new ComboBox<>();
}
public static void refreshTileList(Tile tile) {
comboBoxTileList.getItems().add(tile);
}
If ComboBox is private, and I add item in initialize method it's working, but with static ComboBox I tried million things and still no progress.
Solution
Don't use static and #FXML together.
Rework your design so that the static keyword is no longer required for the comboBoxTileList and use an instance variable instead.
Additional Issue
An #FXML member such as comboBoxTileList should never be set to a new value, so you should not have comboBoxTileList = new ComboBox<>();
Answer to additional questions
I use another window to create new Tile object and from controller class of that window i call refreshTileList method. How to do that without using static?
There are numerous ways of writing your code so that you don't need a static reference to controller members. Here is a sample based on a solution from: Passing Parameters JavaFX FXML. You will need to modify the example to fit your exact case, it's just presented to demonstrate a possible pattern that you could use.
You can construct a new controller in code, passing any parameters you want from your caller into the controller constructor. Once you have constructed a controller, you can set it on an FXMLLoader instance before you invoke the load() instance method.
To set a controller on a loader (in JavaFX 2.x) you CANNOT also define a fx:controller attribute in your fxml file.
class ComboController {
#FXML private static ComboBox<Tile> comboBoxTileList;
public void refreshTileList(Tile tile) {
comboBoxTileList.getItems().add(tile);
}
}
class AnotherController {
#FXML private Button createTile;
#FXML private Button newCombo;
#FXML private StackPane mainPane;
private comboController;
#FXML private void createTile(ActionEvent event) {
if (comboController == null) {
return;
}
comboController.refreshTileList(
new Tile()
);
}
#FXML private void newCombo(ActionEvent event) {
try {
comboController = new ComboController();
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"combo.fxml"
)
);
loader.setController(comboController);
Pane comboPane = (Pane) loader.load();
mainPane.getChildren().setAll(comboPane);
} catch (IOException e) {
// handle exception.
}
}
}

Categories