I am trying to create a button in JavaFX using Scene Builder but I am having some issues. Here is my code :
MainDesign.java
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainDesign extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = (BorderPane) FXMLLoader.load(getClass().getResource("application/Design.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);
}
}
DesignController.java:-
package application;
import java.awt.Button;
import javafx.fxml.FXML;
public class DesignController {
#FXML private Button b1;
}
Design.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.DesignController">
<center>
<Button fx:id="b1" mnemonicParsing="false" text="Button" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
Error:
Exception in Application start method
java.lang.reflect.InvocationTargetException
Caused by: java.lang.RuntimeException: Exception in Application start method
Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module #0x22e6b9c) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module #0x22e6b9c
Exception running application application.MainDesign
Thus , it's generating an exception in MainDesign.java file , I guess its regarding FXML loader.
Please help me if anyone has a solution for such issue!!!
There is a confusion with the Button component; in Design.fxml you're using javafx.scene.control.Button and in DesignController.java you're using java.awt.Button. JavaFX and Java AWT are independant libraries, so it raises a problem when FXML loader is reading this line:
#FXML private Button b1;
because it cannot make a connection between b1 in your FXML file and b1 in your controller. You have to import javafx.scene.control.Button in your controller.
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.
Hi if I use a custom component in SceneBuilder that uses an external library in the constructor or the initialize method, I get an java.lang.ClassNotFoundException exception when try to open the FXML with JavaFX SceneBuilder 8.5.0.
I followed the instructions from here: https://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm.
The custom component is part of my Eclipse project. I don't want to export it as an external jar file.
When I run this application in Eclipse everything works fine. The external library "FontAwesomeIcon" is part of my build path in Eclipse.
When I try to open the FXML of the custom component with SceneBuilder (with the Terminal to get debug logs), I get the following output and no file is loaded:
/Applications/SceneBuilder.app/Contents/MacOS/SceneBuilder /Users/phillip/MEGA/Eclipse/JTunes/src/test/TestWindow.fxml
Jan 09, 2021 6:31:44 PM com.oracle.javafx.scenebuilder.app.SceneBuilderApp logTimestamp
INFORMATION: JavaFX Scene Builder started
TestComponent()
TestComponent: Init
Jan 09, 2021 6:31:45 PM com.oracle.javafx.scenebuilder.app.SceneBuilderApp$SceneBuilderUncaughtExceptionHandler uncaughtException
SCHWERWIEGEND: An exception was thrown:
java.lang.NoClassDefFoundError: de/jensd/fx/glyphs/fontawesome/FontAwesomeIcon
at test.TestComponent.initialize(TestComponent.java:28)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
at test.TestComponent.<init>(TestComponent.java:22)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1009)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2425)
at com.oracle.javafx.scenebuilder.kit.fxom.FXOMLoader.load(FXOMLoader.java:93)
at com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument.<init>(FXOMDocument.java:89)
at com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument.<init>(FXOMDocument.java:106)
at com.oracle.javafx.scenebuilder.kit.editor.EditorController.updateFxomDocument(EditorController.java:2540)
at com.oracle.javafx.scenebuilder.kit.editor.EditorController.setFxmlTextAndLocation(EditorController.java:761)
at com.oracle.javafx.scenebuilder.app.DocumentWindowController.loadFromFile(DocumentWindowController.java:385)
at com.oracle.javafx.scenebuilder.app.SceneBuilderApp.performOpenFiles(SceneBuilderApp.java:672)
at com.oracle.javafx.scenebuilder.app.SceneBuilderApp.access$100(SceneBuilderApp.java:98)
at com.oracle.javafx.scenebuilder.app.SceneBuilderApp$1.invalidated(SceneBuilderApp.java:524)
at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:137)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:72)
at javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:103)
at javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:110)
at javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:144)
at com.oracle.javafx.scenebuilder.kit.library.user.UserLibrary.lambda$updateFirstExplorationCompleted$6(UserLibrary.java:352)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
Caused by: java.lang.ClassNotFoundException: de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 36 more
Jan 09, 2021 6:31:46 PM com.oracle.javafx.scenebuilder.app.SceneBuilderApp logTimestamp
INFORMATION: JavaFX Scene Builder stopped
Can someone explain the whole thing to me? I don't understand why SceneBuilder has to call the constructor or the initialize method.
If I didn't understand the use of custom components in JavaFX and SceneBuilder correctly, what is the right way to do this?
Thanks for your help and greetings from Germany.
Following the content of my files...
Main class (Initializes the JavaFX application and opens the main window):
package test;
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage = new TestWindow();
primaryStage.show();
}
}
Test window class (Loads the test window FXML). This window uses the custom component:
package test;
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TestWindow extends Stage {
public TestWindow() throws IOException {
System.out.println("TestWindow()");
FXMLLoader loader = new FXMLLoader();
loader.setController(this);
loader.setLocation(this.getClass().getResource("TestWindow.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
this.setScene(scene);
}
}
FXML of test window:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import test.TestComponent?>
<VBox alignment="CENTER" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TestComponent />
</children>
</VBox>
Custom component class (Uses an external library "FontAwesomeIcon" that crashes SceneBuilder - corresponding line is bold):
package test;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.layout.VBox;
public class TestComponent extends VBox implements Initializable {
public TestComponent() throws IOException {
System.out.println("TestComponent()");
FXMLLoader loader = new FXMLLoader();
loader.setController(this);
loader.setRoot(this);
loader.setLocation(this.getClass().getResource("TestComponent.fxml"));
loader.load();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println("TestComponent: Init");
System.out.println("TestComponent: "+FontAwesomeIcon.ADJUST);
}
#FXML
private void click() {
System.out.println("Click");
}
}
FXML of custom component:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<fx:root alignment="CENTER" type="VBox" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="TestComponent" />
<Button onMouseClicked="#click" text="Button" />
</children>
</fx:root>
Update:
When I add the .jar file for the class "de/jensd/fx/glyphs/fontawesome/FontAwesomeIcon" to the "app.classpath" option in the SceneBuilder configuration file, the fxml opens without any exception.
Do I really need to add every external library to this setting?
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'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();
}
}