Pass EventHandler from Controller to View trough method DI - JavaFX - java

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

Related

Running a thread by toggling a flag externally (from GUI)

I am making a simple JavaFX college course project and I need a good way of dealing with threads, mainly running them while a certain flag is activated.
This is a simple sketch I came up with:
public class ListenerService extends Thread {
private static ArrayList<ListenerService> listeners = new ArrayList<>();
private ToggleButton button;
private File folder;
private SimpleBooleanProperty active = new SimpleBooleanProperty();
ListenerService(ToggleButton button, String pathname) {
this.button = button;
this.folder = new File(pathname);
button.setOnAction(event -> active.set(button.isSelected()));
active.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
-> {if (newValue.booleanValue()) start();});
listeners.add(this);
}
#Override
public void run() {
while(active.get())
System.out.print(".");
}
The process is as following:
The user dynamically creates a ToggleButton on the form. A
ListenerService object is created, to which a button and a directory
are assigned.
A listener is assigned to the button - if it's clicked - activate
the flag. Otherwise, deactivate. The flag here is a
SimpleBooleanProperty instance.
If the flag is switched on, run the thread. The thread will run
while the flag is active. If the user toggles the button again and
deactivates it, the condition in the while loop would fail and
thread should stop running.
As soon as I run the program, it freezes. I tried making the flag volatile, but nothing changed. Since the flag is controlled externally (from GUI), there isn't a way to make this method synchronized.
What am I doing wrong?
You basically create a new Thread that runs as long as the button is selected and exits when the button is not selected.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ThreadApp extends Application {
public class Worker implements Runnable{
private boolean active;
public void setActive(boolean active) {
this.active = active;
}
#Override
public void run() {
while(active)
{
System.out.println("Active! " + System.currentTimeMillis());
}
}
}
public class WorkerToggle extends ToggleButton {
Worker worker;
public WorkerToggle(String text) {
super(text);
this.worker = new Worker();
setOnAction((event) -> {
if(isSelected())
{
worker.setActive(true);
Thread thread = new Thread(worker);
thread.setDaemon(true);
thread.start();
}else
{
worker.setActive(false);
}
});
}
}
#Override
public void start(Stage primaryStage) {
BorderPane rootPane = new BorderPane();
Scene scene = new Scene(rootPane);
rootPane.setCenter(new WorkerToggle("toggle me"));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This should work fine, but creating Threads can be expensive, so you might want to look into ThreadPoolExecutor if you notice some performance problems there.
In JavaFX you have the ability to use a scheduled service to run things off the main FX thread. Here is simple sample that might help.
public class JavaFXApplication3 extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
PollingService service = new PollingService();
service.setPeriod(Duration.millis(1000)); // sysout every second
ToggleButton tb = new ToggleButton("Start Process");
tb.setOnAction(event -> {
System.out.println(tb.isSelected());
if(tb.isSelected()){
service.reset();
service.start();
}else {
service.cancel();
}
});
VBox vbox = new VBox(tb);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();;
}
private class PollingService extends ScheduledService<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() {
System.out.print(".#.");
return null;
}
};
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Using the non-static method getUI().getPage() return an error : IllegalArgumentException

I've a basic question.
I'm using Vaadin 8.
When I use :
Page.getCurrent().setTitle(IStringConstants.HOMEPAGE_LABEL);
This is working without issue.
And, when I use
getUI().getPage().setTitle(IStringConstants.HOMEPAGE_LABEL);
I receive an error :java.lang.IllegalArgumentException: Unable to create an instance of {0}. The constructor threw an exception.
Because getUI() return null.
My code is pretty simple, in my home page :
public class HomepageView extends CustomComponent implements View {
public HomepageView() {
getUI().getPage().setTitle(IStringConstants.HOMEPAGE_LABEL);
VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
setCompositionRoot(layout);
}
}
And my UI is :
#Theme("mytheme")
public class myUI extends UI {
private static final long serialVersionUID = 1L;
private Navigator navigator;
#Override
protected void init(VaadinRequest vaadinRequest) {
navigator = new Navigator(this, this);
navigator.addView("", HomepageView.class);
}
#WebServlet(urlPatterns = "/*", name = "myUIServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = myUI.class, productionMode = false)
public static class myUIServlet extends VaadinServlet {
private static final long serialVersionUID = 1L;
}
}
getUI() returns null in your case, because the instance you are creating has no parent yet. The parent is set when you add your HomepageView Component to a layout or Panel for instance. getUI() traverses the parents up to the UI root or returns null no such parent is found. You can call getUI().getPage().setTitle(IStringConstants.HOMEPAGE_LABEL); in the attach listener:
public HomepageView() {
this.addAttachListener(e -> {
getUI().getPage().setTitle(IStringConstants.HOMEPAGE_LABEL);
});
VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
setCompositionRoot(layout);
// show something
layout.addComponent(new Label("Hello World!"));
}
Edit
As Morfic pointed out the more common and cleaner solution would be to use the View's enter method instead of the constructor. As you can see the listener is not necessary anymore, since the component already got attached to the UI tree:
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
getUI().getPage().setTitle("This is a title");
VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
setCompositionRoot(layout);
layout.addComponent(new Label("Hello World!"));
}

Can I add a method inside MyUi.java in vaadin?

I've tried to add a method inside MyUi.java class in vaadin, but all I get are errors. So, is it actually possible to do so? The reason why I ask is this: basically MyUi.java class has a button which when clicked opens up another window (the code for this other window sits in a different class). This button is removed (button.setVisible(false);) when clicked and I added a addCloseListener to the new window so that when that window is closed it fires an event and calls a function which will allow me to re-display the button. This function needs to sit inside the MyUi class as I can't figure out how to access the button which currently sits inside MyUi class from a different class.
Some code to make things a little clearer: MyUi.java contains the button
public class MyUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
final NewWindow newWindow = new NewWindow();
final UploaderComponent uploaderComponent = new UploaderComponent();
//final TextField name = new TextField();
// name.setCaption("Type your name here:");
final Button button = new Button("Click Me");
button.addClickListener(new Button.ClickListener()
{
#Override
public void buttonClick(ClickEvent event)
{
//button.setVisible(false);
// TODO Auto-generated method stub
getUI().addWindow(newWindow.getWindow());
newWindow.getWindow().setContent(uploaderComponent.formLayout);
}
});
layout.addComponents(button );
layout.setMargin(true);
layout.setSpacing(true);
setContent(layout);
public void testMethod(){//produces errors
}
}
}
And the window class:
public class NewWindow
{
//public static final Sizeable.Unit PIXELS;
Window window = new Window();
public NewWindow(){
window.setStyleName("wrappingWindow");
window.setWidth("620px");
window.center();
window.addCloseListener(new CloseListener()
{
#Override
public void windowClose(CloseEvent e)
{
System.out.println("window closed");
}
});
}
public Window getWindow(){
return window;
}
}

call spring bean from vaadin button click

I've got window class implementation with annotation #Component. Inside this class I declare object with annotation #Autowired.
On my window form I've got a button Create which should read data from TextFields, create new object and store it in the database.
#Component("newProjectWindow")
public class NewProjectWindow {
private Window createProjectWindow;
#Autowired
private ProjectService service;
public Window createWindow() {
createProjectWindow = new Window("New project");
initWindow();
fillWindow();
return createProjectWindow;
}
private void initWindow() {
createProjectWindow.setSizeUndefined();
createProjectWindow.setResizable(false);
createProjectWindow.setModal(true);
createProjectWindow.addCloseListener(new CloseListener(){
#Override
public void windowClose(CloseEvent e) {
Notification.show("Closed");
}
});
}
private void fillWindow() {
final TextField projectName = new TextField("Project name");
final TextField projectOwner = new TextField("Project owner");
Button create = new Button("Create");
create.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Project newProject = new Project();
newProject.setProjectName(projectName.getValue());
newProject.setProjectOwner(projectOwner.getValue());
//save it somehow
}
});
Button close = new Button("Cancel");
close.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
createProjectWindow.close();
}
});
HorizontalLayout layout = new HorizontalLayout(create, close);
FormLayout formLayout = new FormLayout(projectName, projectOwner, layout);
formLayout.setMargin(true);
createProjectWindow.setContent(formLayout);
}
}
However the problem is how to store object in the database. I've got no access to instantiated ProjectService(which uses ProjectRepisitory which uses SqlSessionTemplate and etc.) because it is under control of Spring - and anonymous ClickListener is not.
But how to store object?
I tend not to use anonymous inner methods for click listeners, but instead get my own classes to implement the ClickListner. So in your example I would change the class like this:
#Component("newProjectWindow")
public class NewProjectWindow {
private Window createProjectWindow implements Button.ClickListener;
#Autowired
private ProjectService service;
private Button create = new Button("Create", this);
private Button cancel new Button("Cancel", this);;
public Window createWindow() {
createProjectWindow = new Window("New project");
initWindow();
fillWindow();
return createProjectWindow;
}
private void initWindow() {
createProjectWindow.setSizeUndefined();
createProjectWindow.setResizable(false);
createProjectWindow.setModal(true);
createProjectWindow.addCloseListener(new CloseListener(){
#Override
public void windowClose(CloseEvent e) {
Notification.show("Closed");
}
});
}
private void fillWindow() {
final TextField projectName = new TextField("Project name");
final TextField projectOwner = new TextField("Project owner");
HorizontalLayout layout = new HorizontalLayout(create, close);
FormLayout formLayout = new FormLayout(projectName, projectOwner, layout);
formLayout.setMargin(true);
createProjectWindow.setContent(formLayout);
}
#Override
public void buttonClick(ClickEvent event) {
if (event.getButton() == cancel)
{
createProjectWindow.close();
}
else
{
Project newProject = new Project();
newProject.setProjectName(projectName.getValue());
newProject.setProjectOwner(projectOwner.getValue());
//save it somehow
}
}
}
To access service from listener in your example, consider following solutions:
Anonymous inner classes can reference outer class (using OuterClassName.this syntax - in your case NewProjectWindow.this.service).
You can declare (inner) class and pass appropriate references to it.
You can use Chris M suggestion of parent class implementing listener interface itself.

Pagenavigation and value passing with afterburner fx in JavaFX?

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);

Categories