I'm using afterburner fx the DI-framework.
My problem is, that I have a "RootLayout" which is a BorderPane. On the top is a MenueBar, which works and in the center I loaded an other pane on startup. Now I want to be able to click on a button, so that the Center-Pane changes to a new View AND I want to pass a value to the new View/Controller.
My main class:
#Override
public void start(Stage primaryStage) throws Exception {
initInjector();
//BorderPane
RootView appView = new RootView();
Scene scene = new Scene(appView.getView());
primaryStage.setTitle("Personalplanung");
primaryStage.setScene(scene);
primaryStage.show();
}
Controller/Presenter of my Root
public class RootPresenter implements Initializable {
#FXML
private AnchorPane center;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
MainView view = new MainView();
center.getChildren().add(view.getView());
}
//Doesn't work because it must be static -> AnchorPane can't be static
public void putCenter(FXMLView fxmlView) {
center.getChildren().add(fxmlView.getView());
}
Presenter of the view I want to change to and pass value (e.g. selected person)
public class MainPresenter implements Initializable {
#Inject
PersonService personService;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
}
/**
* Pseudo display of all persons
* should switch here to PersoView and pass values
* gets triggerd by button click
*/
#FXML
private void handlePersonsOverview() {
personService.all().forEach(person -> {
System.out.println(person.getVorname());
});
}
All my views are empty but extend the FXMLView (is convention)
import com.airhacks.afterburner.views.FXMLView;
public class PersonView extends FXMLView{
}
If I understand your structure correctly, I think you can solve this with a ViewModel:
public class ViewModel {
private final ObjectProperty<Node> mainView = new SimpleObjectProperty(this, "mainView", null);
public ObjectProperty<Node> mainViewProperty() {
return mainView ;
}
public final Node getMainView() {
return mainView.get();
}
public final void setMainView(Node mainView) {
this.mainView.set(mainView);
}
}
Now just inject the ViewModel wherever you need it. Bind the center of the root to the view model's mainView property, and update the mainView property from your other presenters:
public class RootPresenter {
#FXML
private BorderPane root ; // I think you have this?
#FXML
private AnchorPane center ; // possibly no longer need this?
#Inject
private ViewModel viewModel ;
public void initialize() {
root.centerProperty().bind(viewModel.mainViewProperty());
MainView view = new MainView();
viewModel.setMainView(view.getView());
}
}
Now any presenter that needs to change the center of your root just needs to do:
public class SomePresenter {
#Inject
private ViewModel viewModel ;
#FXML
public void someHandlerMethod() {
SomeView someView = new SomeView();
viewModel.setMainView(someView.getView());
}
}
To pass values to the new presenter, just define the appropriate properties and methods in the presenter and invoke them when you create the new view:
public class MainPresenter {
private final ObjectProperty<Person> person = new SimpleObjectProperty<>(this, "person") ;
public ObjectProperty<Person> personProperty() {
return person ;
}
public final Person getPerson() {
return person.get();
}
public final void setPerson(Person person) {
this.person.set(person);
}
public void initialize() {
// bind to person as needed ...
// other stuff as before
}
// ...
}
and then you can do
MainView mainView = new MainView();
MainPresenter mainPresenter = (MainPresenter) mainView.getPresenter();
mainPresenter.setPerson(selectedPerson);
Related
I have many FXML with different Controller and I want to access all Controller Instance in one class using static methods. The reason I am doing this because I want to change the UI of different FXML from different controller. I'm not sure if there is any better way to do it. My problem is in my SceenViews class because I don't know what datatype to use to hold FXML Controller Instance in my controllerMap variable.
Inside my Package: Main.java, ScreenViews.java, Frame.FXML, FrameController.java, Login.FXML, LoginController.java, Dashboard.FXML, DashboardController.java, Journal.FXML, and JournalController.java
Main.java
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
ScreenViews.loadFXML("Frame", "Frame.fxml");
ScreenViews.loadFXML("Login", "Login.fxml");
ScreenViews.loadFXML("Dashboard", "Dashboard.fxml");
ScreenViews.loadFXML("Journal", "Journal.fxml");
Scene scene = new Scene((Parent) ScreenViews.getView("Frame"));
scene.setFill(Color.TRANSPARENT);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In my ScreenViews.java what datatype can I use to hold different controller instance in my controllerMap variable?
public class ScreenViews {
private static HashMap<String, Node> viewMap = new HashMap<>();
//HASHMAP FOR CONTROLLER INSTANCE
private static HashMap<String, datatype? > controllerMap = new HashMap<>();
public static void addView(String name, Node screen) {
viewMap.put(name, screen);
}
public static Node getView(String name) {
return viewMap.get(name);
}
public static void addController(String name, datatype? controller) {
controllerMap.put(name, controller);
}
public static datatype? getController(String name) {
return controllerMap.get(name);
}
public static boolean loadFXML(String name, String resource) {
try {
FXMLLoader fxLoader = new
FXMLLoader(ScreenViews.class.getResource(resource));
Parent loadedFXML = (Parent) fxLoader.load();
addView(name, loadedFXML);
addController(name, fxLoader.getController());
return true;
} catch (IOException e) {
System.out.println(e.getMessage());
return false;
}
}
}
Sample LoginController.java code
public class LoginController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
public void frameSetUI(ActionEvent event) {
ScreenViews.getController("Frame").getFramePane().getChildren().add(ScreenViews.get("Dashboard"));
}
}
Sample FrameController.java code
public class FrameController implements Initializable {
#FXML private StackPane rootPane;
#FXML private AnchorPane titleBar;
#FXML private AnchorPane framePane;
#FXML private Button toDashboard;
#FXML private AnchorPane mainPane;
public void initialize(URL url, ResourceBundle rb) {
}
public AnchorPane getFramePane(){
return framePane;
}
}
I have below code. the tableview does not display record on GUI,is empty.
How I can Pass value from the ServerHandler thread to JAVAFX UI thread.
Can you please suggest?
Thanks
UPDATE
The Main class
public class Main extends Application {
private static Stage stage;
#Override
public void start(Stage primaryStage){
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("mainpane.fxml"));
fxmlLoader.load();
setStage(primaryStage);
Parent root = fxmlLoader.getRoot();
Scene scene = new Scene(root,800,800);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
new Thread(() -> launch(Main.class, args)).start();
new MyServer().startDownload();
}
The Controller
public class SampleController {
private ObservableList<Model> tableData = FXCollections.observableArrayList();
#FXML
private TableView<Model> table;
#FXML
private TableColumn<Model, String> firstCol;
#FXML
private TableColumn<Model, String> secondCol;
#FXML
public void initialize() {
table.setEditable(false);
firstCol.setCellValueFactory(cellData -> cellData.getValue().getName());
secondCol.setCellValueFactory(cellData -> cellData.getValue().getCurrent());
table.setItems(tableData);
}
public void addModel(ChannelFuture sendFileFeture,Model model){
table.getItems().add(Model);
System.out.println("row model= "+model.getName().get());// it works fine;
sendFileFeture.addListener(model);
}
The Server class with Netty 4
public class ServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
#Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
//some codes
Model model=new Model(file.getName(),fileLength+"");
SampleController sc=new SampleController();
sc.addModel(sendFileFeture, model);
}
The Model class with ChannelProgressiveFutureListener of Netty
public class Model implements ChannelProgressiveFutureListener{
private SimpleStringProperty name=null;
private SimpleStringProperty current=null;
public Model(String name,String current){
this.name=new SimpleStringProperty(name);
this.current=new SimpleStringProperty(current);
}
#Override
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception {
System.out.println("current: "+current+",progress: "+progress); //it works fine
current.set(progress+""); // can not update the TableView
}
#Override
public void operationComplete(ChannelProgressiveFuture future) throws Exception {
}
public void setName(String name) {
this.name.set(name);
}
public SimpleStringProperty getName() {
return name;
}
public void setCurrent(String current) {
this.current.set(current);
}
public SimpleStringProperty getCurrent() {
return current;
}
UPDATE
the tableview not updating with right size,the image i loaded is 2,407,257 bytes.you can find the errors in the images below.
image1
image2
secondCol.setCellValueFactory(cellData -> cellData.getValue().getCurrent());
secondCol.setCellFactory(column -> {return new TableCell<Model, String>() {
#Override
protected void updateItem(String item, boolean empty) {
System.out.println(item); //UPDATING NOT CURRECT
super.updateItem(item, empty);
setText(empty ? "" : getItem().toString());
}
};
The UI is not displaying anything because you are populating a different table to the one you are displaying, not because of threading (though you have threading issues too, or will do once you fix the initial problem).
In your start() method, you load the FXML, which creates a TableView and its columns, and creates a controller instance. Your ServerHandler class creates a new instance of the controller, which in turn creates a new instance of TableView (it is always a mistake to initialize variables that are annotated #FXML). That TableView instance is never displayed. So when your ServerHandler populates the table, it is populating a table that is not actually part of the UI, and you don't see anything.
Move the creation of the MyServer to the start() method, and pass it the existing controller instance:
public class Main extends Application {
private Stage stage;
#Override
public void start(Stage primaryStage){
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("mainpane.fxml"));
fxmlLoader.load();
setStage(primaryStage);
Parent root = fxmlLoader.getRoot();
Scene scene = new Scene(root,800,800);
primaryStage.setScene(scene);
primaryStage.show();
SampleController controller = loader.getController();
new Thread(() -> new MyServer(controller).startDownload()).start();
}
public static void main(String[] args) {
launch(args);
}
}
Your MyServer class should in turn pass the controller to the ServerHandler instance(s). Since the ServerHandler methods are being invoked on a background thread, they need to use Platform.runLater(...) to update the UI:
public class ServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
private final SampleController sc ;
public ServerHandler(SampleController sc) {
this.sc = sc ;
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
//some codes
Model model=new Model(file.getName(),fileLength+"");
Platform.runLater(() -> {
sc.addModel(sendFileFeture, model);
sc.addRowModel(sendFileFeture, rowModel);
});
}
}
Finally, don't initialize fields that are supposed to be initialized by the FXMLLoader. This will only have the effect of suppressing any NullPointerExceptions that indicate your controller-FXML bindings are not properly set up:
public class SampleController {
private ObservableList<Model> tableData = FXCollections.observableArrayList();
#FXML
private TableView<RowModel> table ;
#FXML
private TableColumn<Model, String> firstCol ;
#FXML
private TableColumn<Model, String> secondCol ;
#FXML
public void initialize() {
table.setEditable(false);
firstCol.setCellValueFactory(cellData -> cellData.getValue().getName());
secondCol.setCellValueFactory(cellData -> cellData.getValue().getProgress());
table.setItems(tableData);
}
public void addModel(ChannelFuture sendFileFeture,Model model){
table.getItems().add(model);
System.out.println("row model= "+model.getName().get());// it works fine;
sendFileFeture.addListener(rowModel);
}
}
I'm trying to make a generic class that I can use for future projects. It just makes a simple javafx browser. The issue I'm having is that I want to be able to change some of the properties dynamically (on instantiation). I added some simple setters hoping it would to the job, but they do not work. Is there a way to change the variables after start() has been executed?
Class code:
package rob.rushton;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class RushBrowser extends Application {
public RushBrowser() {}
private String url = "www.google.com";
private final String fullUrl = "http://" + url;
String title = "Simple Browser";
private int height = 750;
private int width = 750;
public void openBrowser() {
launch();
}
public void setURL(String u) {
url = u;
}
public void setHeightWidth(int h, int w) {
height = h;
width = w;
}
public void setTitle(String t) {
title = t;
}
#Override
public void start(Stage stage) {
stage.setTitle(this.title);
BorderPane pane = new BorderPane();
Scene scene = new Scene(pane, width, height);
WebView browser = new WebView();
WebEngine engine = browser.getEngine();
engine.load(fullUrl);
pane.setCenter(browser);
stage.setScene(scene);
stage.show();
}
}
And I was trying to run it like this:
package rushtest;
import rob.rushton.RushBrowser;
public class RushTest {
public static void main(String[] args) {
RushBrowser rush = new RushBrowser();
rush.setTitle("Test Title");
rush.setURL("www.github.com");
rush.setHeightWidth(1000, 1000);
rush.openBrowser();
}
}
EDIT: (8/9/15) None of the listed suggestions below have worked :( The problem is that I do not know how to access the application thread that is started by launch()
You should either make those properties true JavaFX properties, or update their setters delegate to the actual UI objects. Some rough code - only the relevant parts shown:
Case of true JavaFX properties:
public class RushBrowser extends Application {
...
private StringProperty titleProperty = new SimpleStringProperty("Simple Browser");
...
public void setTitle(String t) {
titleProperty.set(t);
}
#Override
public void start(Stage stage) {
stage.titleProperty().bind(this.titleProperty);
...
}
}
Case of delegation - you also have to keep a reference of the Stage:
public class RushBrowser extends Application {
...
private Stage primaryStage;
// no need to keep the title member variable
...
public void setTitle(String t) {
primaryStage.setTitle(t);
}
#Override
public void start(Stage stage) {
this.primaryStage = stage;
...
}
}
EDIT
As per the comment from James_D, there is another problem: the main method has no reference to the instance created by Application.launch(). So:
If you want to customize the parameters of your application before it starts, you can override Application.init():
public class SpecialRushBrowser extends RushBrowser {
public void init() {
this.setTitle("Test Title");
...
}
}
Or from the test code:
public class RushTest {
static class TestRushBrowser extends RushBrowser {
public void init() {
super.init(); // just in case
this.setTitle("Test Title");
...
}
}
public static void main(String[] args) {
TestRushBrowser.launch();
}
}
If you do not need to modify these parameters later, you can leave your code as is (i.e. no JavaFX properties are required). Otherwise, apply the changes mentioned above.
If you want to change the parameters after the application has started, you need to provide a reference to the actual instance of RushBrowser created by Application.launch() to the code that will execute the changes. A simple but dirty way is with a global variable:
public class RushBrowser extends Application {
public static RushBrowser INSTANCE;
public void init() {
INSTANCE = this;
}
...
}
And then from any code that runs after launch():
RushBrowser.INSTANCE.setTitle(...);
...
As global state is generally dangerous, you might want to try with a dependency injection framework, if the application gets more complex. Even with DI though it can get tricky because the main class is still created from JavaFX, outside the DI framework - but that's another story.
Again you need to apply the changes above the EDIT.
The reason the code you posted doesn't work is that the (static) launch(...) method creates a new instance of the Application subclass for you, and then calls its start(...) method. So the instance for which you call all the setXXX(...) methods is not the instance whose start(...) method is invoked.
You're defining the reusable part in the wrong place: an Application subclass is inherently not reusable. You should regard the start() method in your Application subclass as the equivalent of the main() method in a regular JavaFX application.
So:
public class RushBrowser {
private final BorderPane view ;
private String url = "www.google.com";
private final String fullUrl = "http://" + url;
private String title = "Simple Browser";
private int height = 750;
private int width = 750;
private WebEngine engine ;
public RushBrowser() {
WebView browser = new WebView();
view = new BorderPane(browser);
engine = browser.getEngine();
}
public Node getView() {
return view ;
}
public void show(Stage stage) {
Scene scene = new Scene(view, width, height);
stage.setScene(scene);
stage.show();
}
public void show() {
show(new Stage());
}
public void setURL(String u) {
url = u;
}
public void setHeightWidth(int h, int w) {
height = h;
width = w;
view.setPrefSize(w, h);
}
public void setTitle(String t) {
title = t;
Scene scene = view.getScene();
if (scene != null) {
Window window = scene.getWindow();
if (window instanceof Stage) {
((Stage)window).setTitle(title);
}
}
}
}
and then you test it with an Application subclass:
public class RushBrowserTest extends Application {
#Override
public void start(Stage primaryStage) {
RushBrowser rush = new RushBrowser();
rush.setTitle("Test Title");
rush.setURL("www.github.com");
rush.setHeightWidth(1000, 1000);
rush.show(primaryStage);
}
public static void main(String[] args) { launch(args); }
}
and of course you can use it elsewhere. As an arbitrary example:
TabPane tabPane = ... ;
RushBrowser rush = new RushBrowser();
rush.setURL("www.github.com");
Tab tab = new Tab();
tab.setContent(rush.getView());
I've been trying to build a simple GUI with JavaFX (I'm completely new to JavaFX) and I've found myself stuck. In every tutorial I've found event handling is done on the level of the UI object, mostly with annonymous inner classes - what I want to accomplish is to move the event handlers to controller class, and inject references to them trough methods called on controller's (and view's) instantiation.
My small GUI is properly build and displayed, the reference is indeed passed, but for a reason the handle() method is not invoked, and I can't find the reason why.
The View:
//imports here
public class View extends Application implements ViewInterface, Runnable {
private Menu fileMenu;
private Menu storheouseMenu;
private MenuBar menuBar;
private Scene scene;
private MenuItem exitItem;
public View() {
initialize();
}
public void initialize() {
fileMenu = new Menu("Plik");
storheouseMenu = new Menu("Magazyn");
MenuItem exitItem = new MenuItem("Exit");
MenuItem displayStorehouse = new MenuItem("Display");
fileMenu.getItems().addAll(exitItem);
storheouseMenu.getItems().add(0, displayStorehouse);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = new VBox();
scene = new Scene(root, 400, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("Szefowa test");
menuBar = new MenuBar();
menuBar.getMenus().addAll(fileMenu, storheouseMenu);
((VBox) scene.getRoot()).getChildren().addAll(menuBar);
primaryStage.setResizable(false);
primaryStage.show();
}
public void addFileMenuListeners(EventHandler<ActionEvent> eventHandler) {
exitItem = fileMenu.getItems().get(0);
exitItem.setOnAction(eventHandler);
}
public void addStorehouseMenuListeners(EventHandler<ActionEvent> eventHandler) {
MenuItem displayStorehouse = fileMenu.getItems().get(0);
displayStorehouse.setOnAction(eventHandler);
}
public void displayMessage(String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Ping");
alert.setContentText(message);
}
//other methods here
}
The Controller:
package kitke.szefowa.controller;
//imports here
public class Controller implements ControllerInterface {
private Model model;
private View view;
public Controller(Model model, View view) {
this.model = model;
this.view = view;
this.view.addFileMenuListeners(new FileMenuListener());
this.view.addStorehouseMenuListeners(new StorehouseMenuListener());
}
public class FileMenuListener implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent event) {
//do some stuff
}
}
public class StorehouseMenuListener implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent event) {
//do some stuff
}
}
}
}
PS I've no such problem while build the GUI with Swing so the issue is connected with JavaFX.
I have tested your code by manual instantiation as:
Controller controller = new Controller( this );
in View.start() method. The event handlers are working as expected with only small problem. Both in addFileMenuListeners() and addStorehouseMenuListeners() methods you are setting the event handler to the same menuitem fileMenu.getItems().get(0). So calling of these method one after another, second invocation is overriding the setOnAction of the first one.
So change the addStorehouseMenuListeners() to:
public void addStorehouseMenuListeners( EventHandler<ActionEvent> eventHandler )
{
MenuItem displayStorehouse = storheouseMenu.getItems().get(0);
displayStorehouse.setOnAction( eventHandler );
}
I have a TableView with three TableColumns. There is an ObservableList named "sensorDataList " in MainApp.java that stores the objects to be shown in the TableView. With the following code, only the first TableColumn named "idColumn" doesn't show the values from the observable list, however, the other two TableColumns works fine.
MainApp.java (this has an "ObservableList" of Sensor objects (see Sensor.java). Also this class has the method named "showSensorDialog()" which loads the .fxml file that has the TableView in question. FYI, this method is invoked by a button in other controller.)
public class MainApp extends Application {
private Stage primaryStage;
private Stage sensorDialogStage;
private ObservableList<sensor> sensorDataList = FXCollections.observableArrayList();
public MainApp(){
sensorDataList.add(new sensor("testText","1 1 0.7", "0 0 1"));
}
public ObservableList<Sensor> getSensorDataList(){return sensorDataList;}
public void showSensorDialog() {
try {
FXMLLoader loader = new FXMLLoader(MainApp.class.getResource("/view/SensorDialog.fxml"));
Object object = loader.load();
sensorDialogStage = new Stage();
Scene scene = new Scene((Parent) object);
sensorDialogStage.setScene(scene);
SensorDialogController controller = loader.getController();
controller.setSensorDialogStage(sensorDialogStage);
controller.setMainApp(this); // this line connects the ObservableList to the TableView.
sensorDialogStage.showAndWait();
} catch (IOException e) {}
}
}
SensorDialogController.java
public class SensorDialogController implements Initializable {
#FXML
private TableView<Sensor> sensorTable;
#FXML
private TableColumn<Sensor, String> idColumn;
#FXML
private TableColumn<Sensor, String> locationColumn;
#FXML
private TableColumn<Sensor, String> directionColumn;
private MainApp mainApp;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
sensorTable.setItems(mainApp.getSensorDataList()); // ObservableList and TableView gets connected.
}
#Override
public void initialize(URL url, ResourceBundle rb){
idColumn.setCellValueFactory(new PropertyValueFactory<Sensor, String>("id"));
locationColumn.setCellValueFactory(new PropertyValueFactory<Sensor, String>("location"));
directionColumn.setCellValueFactory(new PropertyValueFactory<Sensor, String>("direction"));
}
}
Sensor.java
public class Sensor{
private SimpleStringProperty id;
private SimpleStringProperty location;
private SimpleStringProperty direction;
public Sensor(String i, String loc, String dir){
this.id = new SimpleStringProperty(i);
this.location = new SimpleStringProperty(loc);
this.direction = new SimpleStringProperty(dir);
}
}