Very new to JavaFX and lacking a bit of knowledge in the way controllers work but here it goes.
My problem is easy. I need to update a Label on the screen during runtime.
This problem has been addressed on this site before:
Java FX change Label text
Java FX change Label text 2
Passing Parameters
Also, are these links describing the same thing but done differently?
But my program is a little different.
The flow of the program is as follows:
The Main Stage has several Objects that extends Pane with a Label inside. These Objects can be right clicked which opens a context menu. An option in the context menu opens a new window with RadioButtons.
The idea is to select one of the RadioButtons and use that string to rewrite the Label back on the Main Stage.
However my code only works once, the first time. All subsequent changes are not shown on the screen. I can even output the Label that was changed to the Console and it shows the correct value, but never updates the Label on the Stage.
Class that has the Label on the screen:
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
public class CoursePane extends Pane {
private Label courseID;
public CoursePane(Label courseID) {
this.courseID = courseID;
}
public String getCourseID() {
return courseID.getText();
}
public Label getCourseLabel() {
return courseID;
}
public void setCourseID(String ID) {
courseID.setText(ID);
}
}
The Context Menu Class that invokes the menu:
public class CourseContext {
static String fxmlfile;
private static Object paneSrc; //the CoursePane that was clicked on
public static void start(CoursePane pane, String courseSrc) {
//Context Menu
ContextMenu contextMenu = new ContextMenu();
//MenuItems
MenuItem item4 = new MenuItem("option");
//add items to context menu
contextMenu.getItems().addAll(item4);
pane.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.isSecondaryButtonDown()) {
//the coursePane that was right clicked on
paneSrc = event.getSource().toString();
contextMenu.show(pane, event.getScreenX(), event.getScreenY());
item4.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("my fxml file for the radio Buttons"));
Parent root= loader.load();
ElectiveController electiveController = loader.getController();
electiveController.start( "pass the coursePane that was right clicked on" );
Scene scene = new Scene(root);
Stage stage = new Stage();
stage.setScene(scene);
stage.setTitle("Set Elective");
stage.show();
}
catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
});
}
}
And finally, the class that has the value that Label is supposed to be set to:
public class ElectiveController {
#FXML
private Button setButton;
private RadioButton chk;
//the pane that was right clicked on
private static String courseSource;
public void start(Course courseSrc) { //courseSrc: the Pane you right clicked on
courseSource = courseSrc.getCoursenamenumber().getValue();
}//end start
//sets the course pane with the selected elective radio button
#FXML
private void setElective() {
chk = (RadioButton)humElectiveGroup.getSelectedToggle();
//This is supposed to set the value for the coursePane Object to show on the screen!
MainStage.getCoursePanes().get(courseSource).setCourseID(chk.getText());
Stage stage = (Stage) setButton.getScene().getWindow();
stage.close();
}
}
I have looked into dependency injection, tried binding and passing parameters but getting the same results. I know this is straight forward, any help is appreciated! Thanks.
Here is an mcve of how you could wire up the different parts.
- It can be copy pasted into a single file and invoked.
- Note that it is not meant to represent or mock your application. It is meant to demonstrate a (very basic and simplistic) solution for the issue
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
//main class
public class UpdateViewByMenu extends Application {
private Controller controller;
#Override
public void start(Stage stage) throws Exception {
BorderPane root = new BorderPane();
controller = new Controller();
root.setTop(controller.getMenu());
root.setBottom(controller.getView());
Scene scene = new Scene(root, 350,200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) { launch(args);}
}
//controller which "wires" view to model
class Controller {
private Model model;
private View view;
private TopMenu menu;
public Controller() {
model = new Model();
view = new View();
menu = new TopMenu();
//wire up menu to model : menu changes update model
menu.getMenuTextProperty().addListener(
e-> model.setCourseID(menu.getMenuTextProperty().get()));
//wire model to view: change in model update view
view. geLabelTextProerty().bind(model.getCourseIDProperty());
//set initial value to show
menu.getMenuTextProperty().set("Not set");
}
Model getModel() {return model;}
Pane getView() { return view;}
MenuBar getMenu() { return menu; }
}
//model which represent the data, in this case label info
class Model{
SimpleStringProperty courseIdProperty;
Model(){
courseIdProperty = new SimpleStringProperty();
}
StringProperty getCourseIDProperty() {
return courseIdProperty;
}
void setCourseID(String id) {
courseIdProperty.set(id);
}
}
//represents main view, in this case a container for a label
class View extends HBox {
private Label courseID;
View() {
courseID = new Label();
getChildren().add(courseID);
}
StringProperty geLabelTextProerty() {
return courseID.textProperty();
}
}
//menu
class TopMenu extends MenuBar{
SimpleStringProperty menuTextProperty;
TopMenu() {
menuTextProperty = new SimpleStringProperty();
Menu menu = new Menu("Select id");
MenuItem item1 = getMenuItem("10021");
MenuItem item2 = getMenuItem("10022");
MenuItem item3 = getMenuItem("10023");
MenuItem item4 = getMenuItem("10024");
menu.getItems().addAll(item1, item2, item3, item4);
getMenus().add(menu);
}
MenuItem getMenuItem(String text) {
MenuItem item = new MenuItem(text);
item.setOnAction(e -> menuTextProperty.set(item.textProperty().get()));
return item;
}
StringProperty getMenuTextProperty() {
return menuTextProperty;
}
}
Do not hesitate to ask for clarifications as needed.
I am trying to capture key press events (page up and down) but there are no key events received at all. Here is the relevant code:
Constructor:
private MainLayout() {
imageView = new ImageView();
root = new StackPane();
root.getChildren().add(imageView);
root.setFocusTraversable(true); //no effect
//root.requestFocus(); //also no effect
registerEvents();
}
Both lines regarding the focus don't have an effect. The stack pane is directly added to scene.
There are no other nodes than Scene->StackPane->ImageView.
I am able to capture key events on the scene, but i need them captured in the stack pane
Here is registerEvents(), all other events are captured fine!:
private void registerEvents() {
OnScroll onScroll = new OnScroll();
root.setOnScroll(onScroll);
OnResize onResize = new OnResize();
root.heightProperty().addListener(onResize);
root.widthProperty().addListener(onResize);
OnMouseDown onMouseDown = new OnMouseDown();
root.setOnMousePressed(onMouseDown);
root.setOnMouseReleased((event) -> fitImage());
root.setOnDragOver((event) -> dragOver(event));
root.setOnDragDropped((event) -> dropFile(event));
root.setOnKeyPressed((event) -> {
LOG.debug("Key captured.");
if(event.getCode() == KeyCode.PAGE_UP){
imageView.setImage(ip.prev());
event.consume();
} else if(event.getCode() == KeyCode.PAGE_DOWN){
imageView.setImage(ip.next());
event.consume();
}
if(event.isConsumed()){
fitImage();
}
});
I don't see the log out put and a break point is also not caught. So how to catch and handle key events correctly?
Meanwhile i found the solution thanks to this answer. The trick is to setFocusTraversable(true) on ImageView (child of stack pane). Here is the working code:
#Inject
private MainLayout(ImageProvider ip) {
this.ip = ip;
imageView = new ImageView();
imageView.setFocusTraversable(true);
imageView.requestFocus();
root = new StackPane();
root.getChildren().add(imageView);
registerEvents();
}
I don't know if this answer will satisfy you, but I would move handling events from this class to the class where you initialize your scene, and attach the events to the scene itself (since StackPane is, in a way, the scene). I'm guessing that, since the constructor in your code is private, you are instantiating the class via public static method from another class.
public class MainClass extends Application {
private Scene scene = new Scene(MainLayout.getMainLayout());
#Override
public void start(Stage primaryStage) throws Exception {
registerEvents();
primaryStage.setScene(scene);
primaryStage.show();
}
private void registerEvents() {
OnScroll onScroll = new OnScroll();
scene.setOnScroll(onScroll);
OnResize onResize = new OnResize();
scene.heightProperty().addListener(onResize);
scene.widthProperty().addListener(onResize);
OnMouseDown onMouseDown = new OnMouseDown();
scene.setOnMousePressed(onMouseDown);
scene.setOnMouseReleased((event) -> fitImage());
scene.setOnDragOver((event) -> dragOver(event));
scene.setOnDragDropped((event) -> dropFile(event));
scene.setOnKeyPressed((event) -> {
LOG.debug("Key captured.");
if (event.getCode() == KeyCode.PAGE_UP) {
imageView.setImage(ip.prev());
event.consume();
} else if (event.getCode() == KeyCode.PAGE_DOWN) {
imageView.setImage(ip.next());
event.consume();
}
if (event.isConsumed()) {
fitImage();
}
});
}
}
Alternatively, if you want to keep the code for handling events in the MainLayout class, consider making registerEvents a public (or package local, depending on your design) method accepting Scene as a param.
I have a JavaFX application that has child windows or stages. The way I handle how all stages interact with each other is like this: I have a static collection of classes called GuiContainer in my Main class. The GuiContainer looks like this:
public class GuiContainer {
private final Stage stage;
private final Scene scene;
private final FXMLLoader loader;
private final Controller controller;
private final Parent parent;
public GuiContainer(Stage stage, Scene scene, FXMLLoader loader, Controller controller, Parent parent) {
this.stage = stage;
this.scene = scene;
this.loader = loader;
this.controller = controller;
this.parent = parent;
}
public Stage getStage() {
return stage;
}
public Scene getScene() {
return scene;
}
public FXMLLoader getLoader() {
return loader;
}
public Controller getController() {
return controller;
}
public Parent getParent() {
return parent;
}
public static final GuiContainer createMain(Stage stage, String title, int width, int height, String pathToView) throws Exception {
FXMLLoader loaderInner = new FXMLLoader(GuiContainer.class.getResource(pathToView));
Parent parentInner = loaderInner.load();
final Controller controllerInner = loaderInner.getController();
Scene sceneInner = new Scene(parentInner, width, height);
stage.setTitle(title);
stage.setScene(sceneInner);
GuiContainer guiContainer = new GuiContainer(
stage, sceneInner, loaderInner, controllerInner, parentInner
);
controllerInner.start(guiContainer);
return guiContainer;
}
public static final GuiContainer createModal(Window owner, String title, int width, int height, String pathToView) throws Exception {
if (owner == null) {
Log.error(GuiContainer.class.getSimpleName(), "Unable to create instance, missing window owner!");
return null;
}
Stage stageInner = new Stage();
FXMLLoader loaderInner = new FXMLLoader(GuiContainer.class.getResource(pathToView));
Parent parentInner = loaderInner.load();
Controller controllerInner = loaderInner.getController();
Scene sceneInner = new Scene(parentInner, width, height);
stageInner.setTitle(title);
stageInner.setScene(sceneInner);
stageInner.initStyle(StageStyle.DECORATED);
stageInner.initModality(Modality.WINDOW_MODAL);
stageInner.initOwner(owner);
GuiContainer guiContainer = new GuiContainer(
stageInner, sceneInner, loaderInner, controllerInner, parentInner
);
controllerInner.start(guiContainer);
return guiContainer;
}
}
This way I have access to any stage or controller from anywhere.
All possible GuiContainers are created only once at boot (in static main(String[] args)) and can then be statically accessed from anywhere, by anyone.
Now... in the main application (scene inside the primary stage) I have a TreeView that has a custom cell factory, and when a cell is right clicked an appropriate context menu is shown. Inside this context menu I open a child/modal stage, like so:
String containerName = "guiName";
GuiContainer container = Main.getGuiContainers().getOrDefault(containerName, null);
if(container == null) {
Log.error(getClass().getSimpleName(), "Unable to find GuiContainer: " + containerName);
return;
}
container.getStage().showAndWait();
Now, here comes the problem. JavaFX doesn't request focus on the child stage. For instance, I can't type into a TextField (on the child stage) because I have an onKeyPressed event registered on the primary stage and it captures all the keys I press. On this child stage I also have a ComboBox, and when I select an item from that ComboBox, the child stage finally comes into focus and the primary stage no longer captures the keys I press.
I also tried to change the modality to all posible values and read what they do on oracle.com, but none of the information helped me...
Is there something wrong with my code? Or could this be an issue with JavaFX? Any ideas?
EDIT: Here is my Application overriden start method: (I hope this provides more information)
#Override
public void start(Stage stage) throws Exception {
GuiContainer guiWindow1 = GuiContainer.createMain(stage, "Window1", 900, 460,
"/com/project/app/gui/views/Window1.fxml");
GuiContainer guiWindow2 = GuiContainer.createModal(stage, "Window2", 320, 240,
"/com/project/app/gui/views/Window2.fxml");
GuiContainer guiWindow3 = GuiContainer.createModal(stage, "Window3", 320, 240,
"/com/project/app/gui/views/Window3.fxml");
GuiContainer guiWindow4 = GuiContainer.createModal(stage, "Window4", 420, 360,
"/com/project/app/gui/views/Window4.fxml");
GuiContainer guiWindow5 = GuiContainer.createModal(stage, "Window5", 380, 460,
"/com/project/app/gui/views/Window5.fxml");
guiWindow5.getStage().setResizable(false);
guiContainers.put("guiWindow1", guiWindow1);
guiContainers.put("guiWindow2", guiWindow2);
guiContainers.put("guiWindow3", guiWindow3);
guiContainers.put("guiWindow4", guiWindow4);
guiContainers.put("guiWindow5", guiWindow5);
guiWindow1.getStage().show();
}
EDIT2: This is how I register the onKeyPressed listener: (in my parent controller init() method)
getGuiContainer().getScene().setOnKeyPressed((e) -> {
Log.info(getClass().getSimpleName(), "Key pressed: " + e.getCode().getName());
Macro macro = Main.getProject().getSelectedMacro();
if(macro == null) {
Log.error(getClass().getSimpleName(), "Unable to find selected macro");
return;
}
if (e.getCode() == KeyCode.ESCAPE) {
macro.getNodePlacement().reset();
macro.cancelSelect();
macro.repaint();
} else if(e.getCode() == KeyCode.DELETE) {
macro.deleteSelectedNodes();
}
});
EDIT3: This is how my abstract controller class looks like - I hope this provides a better insight
public abstract class Controller {
private GuiContainer guiContainer;
public final GuiContainer getGuiContainer() {
return guiContainer;
}
public final void start(GuiContainer guiContainer) {
this.guiContainer = guiContainer;
init();
}
public abstract void init(); // this is where the onKeyPressed is registered, when extending the abstract class
public abstract void reset();
public abstract void refresh();
}
IMPORTANT EDIT: This only happens on the MAC platform, I ran the same application on windows and there were no such problems.
could you please post the code of the constructor of your stage?
Just a note, from the doc:
Note that showing a modal stage does not necessarily block the caller.
The show() method returns immediately regardless of the modality of
the stage. Use the showAndWait() method if you need to block the
caller until the modal stage is hidden (closed). The modality must be
initialized before the stage is made visible.
EDIT - My mistake, I didn't notice the createModal() method.
What I ended up doing in some of my custom stages is:
Platform.runLater(() -> somecontrol.requestFocus());
(java 8, obviously, override run() method if lambda is not available with your current language level (< 8)).
But I only do this for non-modal stage. Modal stage should take the focus automatically.
According to the doc of Modality, APPLICATION_MODAL is supposed to do what you expect (including Blocking the events for other windows):
APPLICATION_MODAL Defines a modal window that blocks events from being
delivered to any other application window.
You call initModality with WINDOW_MODAL before calling initOwner. Please see:
WINDOW_MODAL public static final Modality WINDOW_MODAL Defines a modal
window that block events from being delivered to its entire owner
window hierarchy. Note: A Stage with modality set to WINDOW_MODAL, but
its owner is null, is treated as if its modality is set to NONE.
If requestFocus() (called after the modal stage has been displayed, and in the UI thread right?) doesn't take the focus, I guess your main window is automatically taking it back.
I'm trying to implement a game on JavaFX. Moreover, I'm dealing with an FXML file so I have a main class and controller class. My question is how can I reach the objects of the main class from the controller class. To be more clear I will share a simple code.
This is main class:
public class JavaFXApplication1 extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("Risk3.fxml"));
// Main Pane
BorderPane borderPane = new BorderPane();
borderPane.setCenter(root);
// Main scene
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
So for example I want to reach root or borderPane from controller class which is:
public class SampleController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Should I make root and borderPane global and static or is there any another way to reach them ?.
The root panel can simply reached from the FXML controller using
#FXML tag like any component.
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:id="root">
...
</BorderPane>
I'm working on a SmartGWT project where I'd like my main navigation to be done via a treegrid. The treegrid renders proprerly and its DataSource is functioning appropriately as well. The treegrid is correctly situated to the left of the mainView Canvas.
What I can't seem to figure out is how to switch the contents of the mainView Canvas based on what is selected in the NavigationTree. I've mimicked the functionality I'd like by adding new windows to the existing Canvas, but I can't find an example demonstrating how to clear the canvas entirely and replace it with a new Window.
Am I on the right track here? Can anyone point me at an example that shows roughly what I'm trying to accomplish?
public class NavigationTree extends TreeGrid {
public NavigationTree(Canvas mainView)
{
setDataSource(NavigationDataSource.getInstance());
setAutoFetchData(true);
setShowHeader(false);
addNodeClickHandler(new NavClickHandler(mainView));
}
// Handler for clicking an item on the Navigation Tree.
private class NavClickHandler implements NodeClickHandler
{
private Canvas mainView;
public NavClickHandler(Canvas mainView)
{
super();
this.mainView = mainView;
}
#Override
public void onNodeClick(NodeClickEvent event)
{
Window window = new Window();
window.setWidth(300);
window.setHeight(230);
window.setCanDragReposition(true);
window.setCanDragResize(true);
window.setTitle(event.getNode().getAttribute("name"));
window.addItem(new Label("huzzah!"));
window.setParentElement(mainView);
window.redraw();
}
}
}
You can keep the mainView canvas, clear its children (if any is set) and then set the newly created window as its new child. Something like the following as the body of your click handler:
Window window = new Window();
window.setWidth(300);
window.setHeight(230);
window.setCanDragReposition(true);
window.setCanDragResize(true);
window.setTitle(event.getNode().getAttribute("name"));
window.addItem(new Label("huzzah!"));
for (Canvas child: mainView.getChildren()) {
mainView.removeChild(child);
}
mainView.addChild(window);
I managed to accomplish what I needed with the following change to the event handler code:
public NavClickHandler(UI ui) //UI extends HLayout
{
this.ui = ui;
}
#Override
public void onNodeClick(NodeClickEvent event) {
Window window = new Window();
window.setWidth100();
window.setHeight100();
window.setHeaderControls(HeaderControls.HEADER_LABEL);
window.setTitle(event.getNode().getAttribute("name"));
window.addItem(new Label("Huzzah!"));
ui.setMainView(window);
}
...and the following change to my main UI layout:
public void setMainView(Canvas canvas)
{
mainView.destroy();
mainView = canvas;
addMember(mainView);
this.redraw();
}