I had some problems in a project with KeyEvent. A combo box that switch values from one to another when key Enter is pressed.
After some attempts the problem showed was that the Enter key generates 2 times the event, so the combo box apparently didn't change.
Then, I tried to make a simple test project with this snippet:
window.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode().equals(KeyCode.ENTER))
System.out.println("print");
}
});
The program prints twice every enter. Is it possible that KeyCode.ENTER refers to more than one key or something like this?
If i change KeyCode.ENTER in something else (tried with SPACE or some letters) it works properly.
Is possible to do something to solve this issue?
I made a simple project with 3 files :
Main
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Main extends Application {
private static Stage window;
#Override
public void start(Stage primaryStage) throws Exception{
window = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static Stage getWindow(){return window;}
public static void main(String[] args) {
launch(args);
}
}
Controller:
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
#FXML
TextField textField;
Stage window;
#Override
public void initialize(URL location, ResourceBundle resources) {
window = Main.getWindow();
window.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode().equals(KeyCode.ENTER))
System.out.println("print");
}
});
}
}
Sample :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity"
prefHeight="400.0" prefWidth="600.0"
fx:controller="Controller"
xmlns="http://javafx.com/javafx/8.0.121"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<TextField fx:id="textField" layoutX="14.0" layoutY="14.0" />
</children>
</AnchorPane>
It prints twice when Enter is pressed. Before the insertion of the TextField instead worked as expected.
Change this line
window.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
to
window.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
Edit code for #James_D
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
#FXML
TextField textField;
Stage window;
#Override
public void initialize(URL location, ResourceBundle resources) {
window = Main.getWindow();
window.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
System.out.println("Key Code:"+event.getCode()+" Target:"+event.getTarget());
});
// textField.setOnAction(event -> {});
}
}
When this runs you get the output of
Key Code:ENTER Target:TextField[id=textField, styleClass=text-input text-field]
Key Code:ENTER Target:AnchorPane#7a78d90b[styleClass=root]
If you uncomment the textField Line the output is
Key Code:ENTER Target:TextField[id=textField, styleClass=text-input text-field]
#James_D Initially I did not but after some looking around and I think it has to do with the preset setOnAction command the description is "The action handler associated with this text field, or null if no action handler is assigned. The action handler is normally called when the user types the ENTER key." So when you change the .setOnAction to consume or nothing it prevents this error which I think is because it is no longer kicking out the enter command to the anchorPane surrounding. Now as to why the KeyEvent.KEY_RELEASED works I think this is because the enter Key can only be released once per press down as opposed to the press which can send the command multiple times. This is all complete speculation because I have no proof of what I'm talking about if you can figure out a more reasonable explanation why I would love to know. This was supposed to be a comment but got to long.
I had the same issue. Every key would trigger the EventFilter once, except ENTER, which would trigger it twice.
A more simple and elegant solution was to simply add e.consume() into your block that respons to enter into the EventFilter.
if(e.getCode().toString().equals("ENTER")){
e.consume();
}
Related
I have been endeavouring to implement a JavaFX application using FXML which includes a WebView.
However, when run this results in a ClassNotFoundException:javafx.scene.web.WebView leading to a javafx.fxml.LoadException, but I am perplexed by this.
Consequently, I have created a simplified application as follows. The Controller is empty in this example. If anybody can inform me how to successfully implement a WebView it will be much appreciated.
Main.java
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("wv.fxml"));
primaryStage.setTitle("WebView Test");
primaryStage.setScene(new Scene(root, 500, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
wv.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.web.WebView?>
<FlowPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<WebView fx:id="WV" prefWidth="200" prefHeight="200"></WebView>
</FlowPane>
module-info.java
module WebViewTest {
requires javafx.fxml;
requires javafx.controls;
opens sample;
}
Have a look at your module-info and wonder why you don't have javafx.web in there.
I'm currently looking into the easiest way to save my SQL Connection within my JavaFX project in order to use it in all of my Controllers.
Since I'm creating the Controller in a SideBar FXML file it is not possible to pass the Object from one Controller to the other.
Therefore I wanted to use the Node.setUserData() method and just save the Connection Object to the root node. Unfortunately I get NullPointers when I want to call it.
Saving it works fine:
myStage.getScene().getRoot().setUserData(con);
And calling it from the same stage variable works fine as well:
... = (Connection) myStage.getScene().getRoot().getUserData();
But I'm accessing the stage within my Sidebar.fxml via
Stage stage = (Stage) myButton.getScene().getWindow();
what then leads to NullPointers while accessing the UserData via
stage.getScene().getRoot().getUserData();
I see that the reason for this is, that it is not the "exact same" stage variable. But it has to be the same stage (when I display a new view there it is displayed on the same stage as before).
How can I find the exact same Node I've saved the UserData before? Or is there a way to access the same Node from another context where I do not have the stage?
EDIT: I've put a MCVE here to show what my Problem is: https://github.com/lud-hu/myJavaFxMcve/
EDIT: Code is working in Github now, I'll post the Code with the initial problem here:
MyMcveStarter.java
package myMcve;
import myMcve.controller.LoginController;
import javafx.application.Application;
import javafx.stage.Stage;
public class MyMcveStarter extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
LoginController controller = new LoginController(primaryStage);
controller.displaySceneOn(primaryStage);
}
}
LoginController.java
package myMcve.controller;
import myMcve.view.LoginSceneView;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class LoginController {
private LoginSceneView view;
private Parent scene;
Stage myStage;
String defaultUrl;
String defaultName;
String defaultPassword;
public LoginController(Stage stage) {
defaultUrl = "jdbc:mysql://localhost:3306/db";
defaultName = "root";
defaultPassword = "localhost";
myStage = stage;
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("../view/LoginScene.fxml"));
try {
scene = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
view = loader.getController();
}
public void displaySceneOn(Stage stage) {
stage.setTitle("login");
Scene myScene = new Scene(scene, 1250, 650);
stage.setScene(myScene);
stage.show();
try {
initializeDbConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
private void initializeDbConnection() throws SQLException {
try {
DriverManager.setLoginTimeout(15);
Connection con = DriverManager.getConnection(defaultUrl, defaultName, defaultPassword);
UserManagementController controller = new UserManagementController(myStage, con);
controller.displaySceneOn(myStage);
} catch (Exception e) {
}
}
}
SideBarController.java
package myMcve.controller;
import myMcve.controller.LevelManagementController;
import myMcve.controller.LoginController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class SideBarController{
#FXML
private Button levelManBtn;
public Button getLevelManBtn() {
return levelManBtn;
}
#FXML
private void levelMan(ActionEvent event){
//start other Controller from here (SideBar)
//how do I access the DB Connection here?
Stage stage = (Stage) levelManBtn.getScene().getWindow();
//LevelManagementController controller = new LevelManagementController(stage, con);
//controller.displaySceneOn(stage);
}
}
UserManagementController.java
package myMcve.controller;
import com.sun.prism.impl.Disposer;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import javafx.util.Callback;
import myMcve.view.UserManagementView;
import java.io.IOException;
import java.sql.*;
public class UserManagementController{
private UserManagementView view;
private Parent scene;
Stage myStage;
Connection con;
public UserManagementController(Stage stage, Connection con){
myStage = stage;
this.con = con;
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));
try {
scene = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
view = loader.getController();
}
public void displaySceneOn(Stage stage){
stage.setTitle("user management");
Scene myScene = new Scene(scene, 1250, 650);
stage.setScene(myScene);
stage.show();
}
}
LoginSceneView.java
package myMcve.view;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class LoginSceneView {
#FXML
private Label label;
public Label getLabel() {
return label;
}
}
LoginScene.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.112-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.LoginSceneView">
<Label fx:id="label" text="login Buttons etc..." />
</AnchorPane>
SideBar.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="sidebar" prefHeight="650.0" prefWidth="250.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.controller.SideBarController">
<children>
<Button fx:id="levelManBtn" layoutX="10.0" layoutY="123.0" prefHeight="50.0" prefWidth="350.0" text="Level Management" onAction="#levelMan"/>
</children>
</VBox>
UserManagementView.java
package myMcve.view;
import javafx.fxml.FXML;
import javafx.scene.control.*;
public class UserManagementView {
#FXML
private Label label;
public Label getLabel() {
return label;
}
}
UserManagementScene.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.UserManagementView">
<left>
<!-- SideBar import -->
<fx:include fx:id="sidebar" source="SideBar.fxml" />
</left>
<center>
<Label fx:id="label" text="user managemnt tableview and Buttons etc..." />
</center>
</BorderPane>
I tried using a design like the one you are using, in which you implement a traditional MVC architecture by using the FXML/JavaFX Controller pair as the MVC view, and create a separate MVC controller. In the end I dismissed it as too convoluted.
The architecture implicit in the FXML-controller design is really a variant of MVC called MVP ("Model-View-Presenter") and if you read the Martin Fowler article on UI architectures, it's close to the variant he calls "Passive View". In this architecture, the FXML file represents the view, which is essentially completely passive and just defines layout. The JavaFX Controller represents the "Presenter", which observes and updates the model, and responds to changes in it by modifying the view. My main recommendation would be to refactor your design so it conforms to this: so remove one of the "controller" layers entirely, and consider the "JavaFX controller" to be fulfilling the general role of controller/presenter in one of the MVC variants. (I posted a fuller answer to this here.)
In particular, your design struggles when you use <fx:include>. The issue is your design is "controller-centric", in the sense that you favor creating the controllers and letting the controllers create the views. Using <fx:include> basically creates a new view for you (by loading another FXML file), which then creates the controller for you. So this is more "view-centric". The conflict between these two makes it tricky to share a model (or services) among the controllers.
One way to get this to work is to set a controller factory on the FXMLLoader. The controller factory is a function that maps a class (defined by the fx:controller attribute in the FXML file) to the JavaFX controller instance. So you can use this factory to create the controller with the connection already initialized in the controller. (BTW, the connection is playing the role of the MVC model in your application: you might want to refactor that to something more robust at a later stage. You should at least factor all the database-specific code to a Data Access Object, and share that object instead of the raw connection.)
First, define a constructor in SideBarController that takes a Connection:
public class SideBarController{
#FXML
private Button levelManBtn;
private final Connection con ;
public SideBarController(Connection con) {
this.con = con ;
}
// existing code (which obviously can now access the connection)...
}
Now when you load your UserManagementView, specify a controller factory that calls the constructor taking a Connection, if one exists, and calls the no-argument constructor otherwise. The approach shown here uses reflection, and is basically akin to the way dependency injection (which is actually what you are trying to do here) frameworks are implemented. There are other possibilities too.
public UserManagementController(Stage stage, Connection con){
myStage = stage;
this.con = con;
FXMLLoader loader = new FXMLLoader();
// note that this resource name will likely not work if you bundle the app as a jar....
loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));
loader.setControllerFactory((Class<?> controllerType) -> {
try {
// check constructors of controllerType to see if one takes a Connection:
for (Constructor<?> c : controllerType.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Connection.class)) {
// found matching constructor, invoke it with the connection as parameter:
return c.newInstance(con);
}
}
// no matching constructor, just invoke default constructor:
return controllerType.newInstance();
} catch (Exception e) {
// fatal...
throw new RuntimeException(e);
}
});
try {
scene = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
view = loader.getController();
}
Controller factories are propagated to the load process for <fx:include>d FXML files. So when UserManagementScene.fxml is loaded, the specified controller is UseManagementView. That has no constructor taking a Connection, so the default constructor will be invoked. When the <fx:include> for SideBar.fxml is encountered, it specifies a controller of SideBarController, which does (now) have a constructor taking a Connection, so that constructor is invoked.
Note that your design is a little different for the side bar than it is for the rest of your application, in that in the other components, your view is the FXML-controller pair with a separate controller class. For the side bar, you use the JavaFX approach where the view is the FXML file, and the controller is specified as the fx:controller. Again, I would probably refactor the application so that everything follows that second design, rather than the first.
I have a Spinner in controller:
#FXML
private Spinner<Integer> spnMySpinner;
and a SimpleIntegerPropertyin controller:
private static final SimpleIntegerProperty myValue =
new SimpleIntegerProperty(3); //load a default value
I have bound them together in controller's initialize method:
spnMySpinner.getValueFactory().valueProperty().bindBidirectional(myValueProperty().asObject());
But the bindings work correctly only after second time the controller initializes. Here's how I can reproduce it:
I open the stage with the associated controller, it loads the default value, specified in the myValue property correctly (a number 3).
I click the increment button on the spinner to make it a 4. It changes the value in the spinner's value property, but the bound property myValue is left intact with the number 3.
I close the stage/window.
I reopen it, the spinner has again a value of 3.
I increment it again. Boom now the binding works and I have a "4" both inside the spinner and the bound property.
Entire minimalistic, but launchable/reproducible code:
Main.java:
package spinnerpoc;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("MainWindow.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainWindow.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="myRoot" id="AnchorPane" prefHeight="231.0" prefWidth="337.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.60" fx:controller="spinnerpoc.MainWindowController">
<children>
<Button fx:id="btnOpenSpinnerWindow" layoutX="102.0" layoutY="103.0" mnemonicParsing="false" text="Open SpinnerWindow" onAction="#onOpenSpinnerWindow"/>
</children>
</AnchorPane>
MainWindowController.java:
package spinnerpoc;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class MainWindowController implements Initializable {
#FXML
private Button btnOpenSpinnerWindow;
#FXML
private AnchorPane myRoot;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
private void onOpenSpinnerWindow(ActionEvent event) throws IOException{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("SpinnerWindow.fxml"));
Parent root = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initOwner(myRoot.getScene().getWindow());
stage.initModality(Modality.WINDOW_MODAL);
stage.setTitle("SpinnerWindow");
stage.setScene(new Scene(root));
stage.show();
}
}
SpinnerWindow.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.control.SpinnerValueFactory.DoubleSpinnerValueFactory?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<ScrollPane xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.60" fx:controller="spinnerpoc.SpinnerWindowController">
<content>
<VBox maxWidth="1.7976931348623157E308">
<children>
<Spinner fx:id="spnMySpinner" editable="true" prefWidth="50.0" max="10" min="1" />
</children>
</VBox>
</content>
</ScrollPane>
SpinnerWindowController.java:
package spinnerpoc;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Spinner;
public class SpinnerWindowController implements Initializable {
private static final SimpleIntegerProperty myValue = new SimpleIntegerProperty(3);
public static SimpleIntegerProperty myValueProperty() {
return myValue;
}
public static Integer getMyValue() {
return myValue.getValue();
}
public static void setMyValue(int value) {
myValue.set(value);
}
#FXML
private Spinner<Integer> spnMySpinner;
#Override
public void initialize(URL url, ResourceBundle rb) {
spnMySpinner.getValueFactory().valueProperty().bindBidirectional(myValueProperty().asObject());
}
}
(Code also available at a BitBucket repo.)
What am I missing?
You are running into the "premature garbage collection" problem. See a description of this here. You will likely find that it's not always every other time you show the spinner that it fails, but is just sporadic, and that the behavior will vary from one machine to another. If you limit the memory available to the JVM, you might find that it never works.
When you call IntegerProperty.asObject(), it
Creates an ObjectProperty that bidirectionally bound to this IntegerProperty.
Now note that a bidirectional binding has this feature to prevent accidental memory leaks:
JavaFX bidirectional binding implementation use weak listeners. This means bidirectional binding does not prevent properties from being garbage collected.
So the bidirectional binding you explicitly create does not prevent the thing it is bound to (the ObjectProperty<Integer> created by asObject()) from being garbage collected. Since you keep no references to it, it is eligible for garbage collections as soon as you exit the initialize() method in the SpinnerWindow Controller. Obviously, once the value to which your spinner value is bidirectionally bound is garbage collected, the binding will not work any more.
Just for demonstration purposes, you can see this by putting in a hook to force garbage collection. E.g. do
<ScrollPane onMouseClicked="#gc" xmlns:fx="http://javafx.com/fxml/1" ...>
in SpinnerWindow.fxml and
#FXML
private void gc() {
System.out.println("Invoking GC");
System.gc();
}
in SpinnerWindowController. If you do this, then clicking in the scroll pane will force garbage collection, and changing the spinner value will not update the property.
To fix this, retain a reference to the property you get from asObject():
public class SpinnerWindowController implements Initializable {
private static final SimpleIntegerProperty myValue = new SimpleIntegerProperty(3);
public static SimpleIntegerProperty myValueProperty() {
return myValue;
}
public static Integer getMyValue() {
return myValue.getValue();
}
public static void setMyValue(int value) {
myValue.set(value);
}
#FXML
private Spinner<Integer> spnMySpinner;
private ObjectProperty<Integer> spinnerValue = myValueProperty().asObject();
#Override
public void initialize(URL url, ResourceBundle rb) {
spnMySpinner.getValueFactory().valueProperty().bindBidirectional(spinnerValue);
}
}
I'm using Scene Builder to build a javafx gui, and I want a ComboBox where something will happen "On Showing." It looks simple enough, but it crashes when I try to implement it. I created a simple version having nothing but a combobox, and it still crashes.
Here is the Main.java module:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
// BorderPane root = new BorderPane();
BorderPane root = (BorderPane) FXMLLoader.load(Main.class.getResource("Comboscreen.fxml"));
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Here is the fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<center>
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="400.0" BorderPane.alignment="CENTER">
<children>
<ComboBox fx:id="ComboTestBox" onAction="#ComboDo" onShowing="#Showaction" prefWidth="150.0" promptText="Testbox" />
</children>
</VBox>
</center>
</BorderPane>
and here is the Controller:
package application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
public class Controller {
#FXML
public ComboBox<String> ComboTestBox;
#FXML
void ComboDo(ActionEvent event) {
System.out.println(" Option Chosen");
}
ObservableList<String> options =
FXCollections.observableArrayList(
"Option 1",
"Option 2",
"Option 3"
);
#FXML
void Showaction(ActionEvent event) {
System.out.println(" TestAction");
}
public void initialize() {
ComboTestBox.setItems(options);
}
}
It looks simple enough, but it crashes every time. If I remove the "onshowing" action, it works just fine. Any advice is appreciated, as I am pretty new at this.
Assuming by "crashing" you mean it gives you a runtime exception (for future reference, please include the stack trace, which has lots of information for you to diagnose the problem, in your question):
The onShowing handler is an EventHandler<Event>, not an EventHandler<ActionEvent>, so you need:
#FXML
void Showaction(Event event) {
System.out.println(" TestAction");
}
Note that if you're not using the Event parameter, you can omit it, and the FXMLLoader will still be able to map to the correct handler method:
#FXML
void Showaction() {
System.out.println(" TestAction");
}
However, if you include the parameter, it must be of the correct type.
As an aside, you should use proper naming conventions, i.e.
#FXML
void showAction(Event event) {
System.out.println(" TestAction");
}
with the corresponding change to your FXML file.
It seems there is an issue in setting focus (and handling keyboard events) on the JavaFX scenes that have only shapes (e.g. Rectangle, Circle, etc). Since formatting FXML is not easy at stackoverflow, I described the issue and a workaround in my blog:
http://yakovfain.com/2015/02/13/javafx-8-the-keyboard-events-are-not-being-processed-if-a-scene-has-only-shapes/
I'd appreciate if someone could find a better solution or explain what do I do wrong.
Thanks
This works for me.
The only change I made from Yakov's original solution is to remove the button work around hack and make the request focus call after the stage had been shown.
I think in Yakov's original implementation, the group containing the shapes was not shown on a stage at the time focus was requested for it - so it never got focus. By ensuring that we wait until the stage is displayed before requesting focus, everything works fine.
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Group?>
<?import javafx.scene.shape.Rectangle?>
<Group fx:id="theGroup" onKeyPressed="#keyHandler" focusTraversable="true"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="Controller">
<Rectangle fill="BLUE" height="300.0" stroke="BLACK"
strokeType="INSIDE" width="300.0"/>
</Group>
Main.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("sample.fxml"));
Parent root = loader.load();
primaryStage.setScene(new Scene(root, 300, 300));
primaryStage.show();
Controller controller = loader.<Controller>getController();
controller.focus();
}
public static void main(String[] args) {
launch(args);
}
}
Controller.java
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.input.KeyEvent;
public class Controller {
#FXML
private Group theGroup;
public void keyHandler(KeyEvent event) {
System.out.println("A Key was pressed");
}
public void focus() {
theGroup.requestFocus();
}
}