create custom component for javafx scene builder - javafx

I've searched the entire web (metaphorically) to find an explanation on how to create the jar file that can then be imported by the scene builder to add extra, custom components. Currently, I am trying to create a slider with a textField that displays it's value, with a biDirectional link using a NumberStringConverter. I have the classes all setup, but now I need to bundle them in a jar file, and that's the part that doesn't work for me. These are the classes:
FXML:
<fx:root type="javafx.scene.layout.HBox" xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" fx:controller="main.java.valueSlider">
<Slider fx:id="slider" HBox.hgrow="ALWAYS"/>
<Label fx:id="label" text="value"/>
</fx:root>
Now I can just import this FXML file and use it as a shortcut to create a slider with a label, but I want there to be a controller involved for the biDirectional link. This is the controller class:
public class valueSlider extends HBox {
#FXML
private TextField value;
#FXML
private Slider slider;
private DoubleProperty sliderPos = new SimpleDoubleProperty();
private DoubleProperty
sliderMin = new SimpleDoubleProperty(),
sliderMax = new SimpleDoubleProperty();
public valueSlider() {
try {
FXMLLoader l = new FXMLLoader(getClass().getResource("valueSlider.fxml"));
l.setController(this);
l.setRoot(this);
l.load();
}
catch (IOException e) {
e.printStackTrace();
}
}
#FXML
private void initialize() {
slider.minProperty().bindBidirectional(sliderMin);
slider.maxProperty().bindBidirectional(sliderMax);
slider.valueProperty().bindBidirectional(sliderPos);
value.textProperty().bindBidirectional(sliderPos, new NumberStringConverter());
}
public double getSliderPos() {
return sliderPos.get();
}
public DoubleProperty sliderPosProperty() {
return sliderPos;
}
public void setSliderPos(double sliderPos) {
this.sliderPos.set(sliderPos);
}
}
So the question now is: how do I export these two classes in a jar file and so that I can load it into the scene builder?
Quick question: I made a new FXML file in the same package and tried to use the component like so:
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.121" fx:controller="main.java.controller">
<children>
<valueSlider sliderPos="75"/>
</children>
</AnchorPane>
But when I tried to open the scene builder in the IDE (intelliJ IDEA) it said:
Failed to open the file in the Scene Builder
java.lang.ClassNotFoundException: Unresolved import
javafx.fxml.LoadException:
/G:/GitHub/customParts/src/main/resources/test.fxml
But it doesn't provide any more information, but when I remove the valueSlider component it works perfectly fine.
Please let me know if you know how to solve this problem.
Thanks in advance,
Lenardjee

Related

converting java fx code into java scenebuilder

I cant convert this code into scene builder...
The problem is in event handler....
I am not getting how to use the confirmCloseEventHandler event handeler in java
fx scene builder...
thanks in advance.
mainly i cant use those event handlers... in fxml controllers...
public class Javafxpopupmessage extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(100));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button)
closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be
seen.
// closeConfirmation.setX(mainStage.getX());
//closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}
Registering some handler for the primary stage via fxml could only be done with a bad hack, since FXMLLoader only has access to objects it creates itself.
You could add a listener to the Node.scene property of some node in your scene and add a listener to the window property of that scene as soon as it's set and access the window as soon as it's assigned, which is quite complex for something that could be done using much simpler code in the start method.
Other than that hack you won't get around registering that event handler in the start method (or passing the Stage to the controller resulting in more complex code than the one posted).
As for the close button onAction event: You can use a method of your controller as handler:
fxml
<StackPane xmlns:fx="http://javafx.com/fxml" fx:controller="mypackage.MyController">
<children>
<Button text="Close Application" onAction="#close"/>
</children>
<padding>
<Insets topRightBottomLeft="100"/>
</padding>
</StackPane>
controller
package mypackage;
...
public class MyController {
#FXML
private void close(ActionEvent event) {
Node source = (Node) event.getSource();
Window window = source.getScene().getWindow();
window.fireEvent(new WindowEvent(
window,
WindowEvent.WINDOW_CLOSE_REQUEST));
}
}

JavaFX animation on button hover

I would like to make an animation when I hover a button but if I use CSS, there is no transitions, the properties change instantly. I tried to extend the button class and set the onMouseEntered property but if I do that, I can't open the FXML file with SceneBuilder anymore because it doesn't know my subclass extends the Button class. So what can I do to have all of the buttons have a transition on hover or click ?
There are several ways you can accomplish this, and even let it work on Scene Builder.
I'd start by subclassing the button skin, where you can add the event handlers with your animations. In this case let's have a fade in/fade out animation:
MyButtonSkin.java
public class MyButtonSkin extends ButtonSkin {
public MyButtonSkin(Button control) {
super(control);
final FadeTransition fadeIn = new FadeTransition(Duration.millis(100));
fadeIn.setNode(control);
fadeIn.setToValue(1);
control.setOnMouseEntered(e -> fadeIn.playFromStart());
final FadeTransition fadeOut = new FadeTransition(Duration.millis(100));
fadeOut.setNode(control);
fadeOut.setToValue(0.5);
control.setOnMouseExited(e -> fadeOut.playFromStart());
control.setOpacity(0.5);
}
}
Now you can apply this custom skin to a regular JavaFX Button:
One way, by code:
MyApplication.java
#Override
public void start(Stage primaryStage) throws IOException {
Button btn = new Button("Button");
btn.setSkin(new MyButtonSkin(btn));
StackPane root = new StackPane(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
Another option is via css, let's add a style.css file:
style.css
.button {
-fx-skin: 'your.package.name.MyButtonSkin';
}
and now:
MyApplication.java
#Override
public void start(Stage primaryStage) throws IOException {
Button btn = new Button("Button");
StackPane root = new StackPane(btn);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
In both cases, you will have the button animations working when you run your application:
Scene Builder
If you add now a regular JavaFX Button to an FXML file:
FXML.fxml
<AnchorPane id="AnchorPane" prefWidth="300" prefHeight="250" stylesheets="#style.css" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="your.package.name.FXMLController">
<children>
<Button layoutX="100" layoutY="100" text="Button" />
</children>
</AnchorPane>
Scene Builder won't know about the custom skin (whether it was applied by code or via css), and it won't display or preview it.
In this case, this solution is good enough, since after all Scene Builder is just a designer tool, and the animation will work when you run your application.
But if you really want to preview the animation from Scene Builder, you still can do it, but for this you need to:
Subclass Button, and
Import this class (jar) into Scene Builder
So let's create MyButton class:
MyButton.java
public class MyButton extends Button {
public MyButton() {
super();
}
public MyButton(String text) {
super(text);
}
#Override
protected Skin<?> createDefaultSkin() {
return new MyButtonSkin(this);
}
}
Now build your project. At least should contain MyButton and MyButtonSkin), but it can contain a demo application class to test it:
#Override
public void start(Stage primaryStage) throws IOException {
MyButton btn = new MyButton("Button");
StackPane root = new StackPane(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
Import the project into Scene Builder:
Open Scene Builder, and select JAR/FXML Manager -> Add Library/FXML from file system. Locate your jar, and click Import Component:
Now you'll be able to drag a MyButton control from the Custom panel to the top left.
FXML.fxml
<?import javafx.scene.layout.AnchorPane?>
<?import your.package.name.MyButton?>
<AnchorPane prefHeight="250.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<MyButton layoutX="100" layoutY="100" text="Button" />
</children>
</AnchorPane>
Finally, when you click Preview -> Show Preview in Window, you will be able to see the animation.

Javafx adding dynamically pane to vbox Duplicate Children error

I have a pane with a label, a text field and a combo box inside a VBox in fxml file. Let´s call it tempPane.
In the same stage I have a button.
Once the button is pressed I need to add to the VBox a pane exactly the same as tempPane. This is, adding dynamically a pane to the VBOX.
I am able to add individual controls such as buttons or labels or text fields to the VBox, but I can´t obtain the same results when trying to add this new pane.
Part of the controller code:
#FXML
private Pane tempPane;
#FXML
private Button btnAddNewPane;;
#FXML
private VBox vBox;
#FXML
void addNewPane(ActionEvent event) {
...
Pane newPane = new Pane();
newPane = tempPane;
// New ID is set to the newPane, this String (NewID) should be
//different each time button is pressed
newPane.setId(newID);
vBox.getChildren().add(newPane);
...
}
And the error I´m getting is:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = VBox[id=filterBox]
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:580)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at com.sener.dbgui.controller.SearchController$1.run(SearchController.java:53)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:417)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:175)
at java.base/java.lang.Thread.run(Thread.java:844)
So, why am I getting this duplicate children error? I´m changing the newPane ID before adding it to the VBox.
Pane newPane = new Pane();
newPane = tempPane;
...
vBox.getChildren().add(newPane);
This code does create a new Pane (first line) but immediately drops the new instance by overwriting it with the old one (second line).
The error happens since the contract of Node does not allow it to be placed twice in a scene and you're adding the same Pane that is already a child of vBox again. Modifying the id property does not change that fact.
You need to create a new copy of the subscene rooted at tempPane if this is supposed to work.
You could create a custom Pane for this scene:
subFXML.fxml
<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.Pane">
<!-- content of tempPane from old fxml goes here -->
...
<Button fx:id="btnAddNewPane" />
...
</fx:root>
public class MyPane extends Pane {
public MyPane() {
FXMLLoader loader = getClass().getResource("subFXML.fxml");
loader.setRoot(this);
loader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
#FXML
private Button btnAddNewPane;
public void setOnAction(EventHandler<ActionEvent> handler) {
btnAddNewPane.setOnAction(handler);
}
public EventHandler<ActionEvent> getOnAction() {
return btnAddNewPane.getOnAction();
}
}
old fxml
Be sure to import MyPane.
...
<VBox fx:id="vBox">
<children>
<!-- replace tempPane with MyPane -->
<MyPane onAction="#addNewPane"/>
</children>
</VBox>
...
old controller
#FXML
private VBox vBox;
#FXML
void addNewPane(ActionEvent event) {
...
MyPane newPane = new MyPane();
newPane.setId(newID); // Don't know why setting the CSS id is necessary here
newPane.setOnAction(this::addNewPane); // set onAction property
vBox.getChildren().add(newPane);
...
}
It is written in your comments already why you are getting duplicate ID.
// New ID is set to the newPane, this String (NewID) should be
//different each time button is pressed
You are passing the same string as a parameter.
newPane.setId("NewID");
try using a dynamically generated and unique id for each pane.
String newId; //generate the id by user input or internally
newPane.setId(newId);

JavaFX - creating custom dialogs using fxml

I'm new in javafx and I was trying to create custom dialogs/alerts. The thing is that I'm using Scene Builder to design the GUI, and I want to modify the dialog each time I load the fxml file (i.e change the title, label text, etc.), so I wanted to know if there's a way to send parameters and modify the stage/scene, or any other way I can achieve this.
To be more specific, let's say there's an error I want to handle anywhere in my program, so I load a new fxml file that represents the error dialog I created, and I modify the components inside it, depending on the type of error I need to handle, similar to, for example, JOptionPane.showMessageDialog(...) in swing.
For the use case you describe, you can just use the Dialog API, or the specialized Alert class that is part of that.
For the more general question you ask:
I wanted to know if there's a way to send parameters and change the stage/scene
the way to do this is to use the custom component mechanism described in the documentation.
In short, make a subclass of the UI type you need that loads the FXML file, and defines the properties you need, e.g.
public class ExceptionPane extends BorderPane {
private final ObjectProperty<Exception> exception ;
public ObjectProperty<Exception> exceptionProperty() {
return exception ;
}
public final Exception getException() {
return exceptionProperty().get();
}
public final void setException(Exception exception) {
exceptionProperty().set(exception);
}
#FXML
private final TextArea stackTrace ;
#FXML
private final Label message ;
public ExceptionPane() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
loader.setRoot(this);
loader.setController(this);
loader.load();
exception.addListener((obs, oldException, newException) -> {
if (newException == null) {
message.setText(null);
stackTrace.setText(null);
} else {
message.setText(newException.getMessage());
StringWriter sw = new StringWriter();
newException.printStackTrace(new PrintWriter(sw));
stackTrace.setText(sw.toString());
}
});
}
}
Then define the FXML using a "dynamic root":
<!-- imports etc -->
<fx:root type="BorderPane" ...>
<center>
<TextArea fx:id="stackTrace" editable="false" wrapText="false" />
</center>
<top>
<Label fx:id="message" />
</top>
</fx:root>
Now you can use this directly in either Java or in FXML:
try {
// some code...
} catch (Exception exc) {
ExceptionPane excPane = new ExceptionPane();
excPane.setException(exc);
Stage stage = new Stage();
stage.setScene(new Scene(excPane));
stage.show();
}
or
<fx:define fx:id="exc"><!-- define exception somehow --></fx:define>
<ExceptionPane exception="${exc}" />

javaFX cannot access Scene elements

I am new to javafX, have run through various tutorials, and googled extensively. I am attempting to write a multiscreen javaFX program whose second screen has a split pane, whose right pane should display a grid and a button. The framework I am using for multiple screens is copied form Angela Caiedo's MultipleScreens Framework tutorial ( https://github.com/acaicedo/JFX-MultiScreen/tree/master/ScreensFramework) .
What works: The screens framework is successful in terms of flipping between my multiple screens.
My problem: I click the button on screen 1 to move to screen 2. Screen 2 successfully shows. On screen 2 I click a button to populate data in my vBox. The code in my controller class for screen 2 is unable to access the vbox (or any other defined containers on this screen).
My main class sets up the controller screens.
public class FieldMapCreator extends Application {
public static String mainID = "main";
public static String mainFile = "MainMapFXMLDocument.fxml";
public static String initialMapID = "initialMap";
public static String initialMapFile = "FXMLMapPicture.fxml";
#Override
public void start(Stage stage) throws Exception {
ScreensController mainContainer = new ScreensController();
mainContainer.loadScreen(FieldMapCreator.mainID, FieldMapCreator.mainFile);
mainContainer.loadScreen(FieldMapCreator.initialMapID, FieldMapCreator.initialMapFile);
mainContainer.setScreen(FieldMapCreator.mainID);
Group root = new Group();
root.getChildren().addAll(mainContainer);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
I get to the second screen by clicking a button on screen 1. Button1 code looks like:
#FXML
private void initialMapButtonAction(ActionEvent event) {
myController.setScreen(FieldMapCreator.initialMapID);
}
Screen 2 has a button that when clicked executes a method that creates data for the vbox. The fxml for screen 2 is this:
<AnchorPane id="AnchorPane" fx:id="mainAnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FieldMapCreator.FXMLMapPictureController">
<children>
<SplitPane fx:id="mapSplitPane" dividerPositions="0.29797979797979796" layoutX="3.0" layoutY="7.0" prefHeight="400.0" prefWidth="600.0">
<items>
<AnchorPane fx:id="anchorPaneLeft" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Button fx:id="backButton" layoutX="14.0" layoutY="348.0" mnemonicParsing="false" onAction="#goToMainScreen" text="Back" />
</children>
</AnchorPane>
<AnchorPane fx:id="anchorPaneRight" minHeight="0.0" minWidth="0.0" prefHeight="364.0" prefWidth="401.0">
<children>
<Button fx:id="vBoxShowMap" layoutX="24.0" layoutY="346.0" mnemonicParsing="false" onAction="#buildMapFromNurseries" prefHeight="26.0" prefWidth="91.0" text="show map" />
<VBox fx:id="mapVBox" layoutX="24.0" layoutY="24.0" onMouseClicked="#vBoxMouseClicked" prefHeight="200.0" prefWidth="309.0" />
</children></AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
The code that fails is in the "showInitialMap()" method of the controller class for screen 2. When executed, the last line of this method "mapVBox.getChildren().add(root)", results in a null pointer exception. I expected this to be resolved via the fxml injection. I also tried the commented out code to retrieve "mapVBox" using scene lookup, but it also results in a null pointer exception. The controller code is below:
public class FXMLMapPictureController implements Initializable, ControlledScreen {
ScreensController myController;
static MyNode[][] mainField ;
#FXML
private AnchorPane mainAnchorPane;
#FXML
private static SplitPane mapSplitPane;
#FXML
private AnchorPane anchorPaneLeft;
#FXML
private static AnchorPane anchorPaneRight;
#FXML
private static VBox mapVBox;
#FXML
private Button vBoxShowMap;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// Should something be in here ??
}
public void goToMainScreen() {
myController.setScreen(FieldMapCreator.mainID);
}
public void showInitialMap() {
int row = userFieldMap.getNumRows();
int col = userFieldMap.getNumCols();
double gridWidth = 309/col;
double gridHeight = 200/row;
Group root = new Group();
// initialize the field
for (int idx = 0; idx < col; idx++) {
for (int jdx = 0; jdx < row; jdx++) {
// create plot
Plot plot = new Plot(idx, jdx, "Plot" + idx + "/" + jdx);
// Create node to hold the Plot
MyNode node = new MyNode(idx*gridWidth, jdx*gridHeight, gridWidth, gridHeight, plot);
// add plot to group
root.getChildren().add(node);
}
}
//Scene myScene = myController.getScene();
//VBox mapVBox = (VBox)myScene.lookup("#mapVBox");
mapVBox.getChildren().add(root);
}
#Override
public void setScreenParent(ScreensController screenParent) {
myController = screenParent;
}
Is there something missing from the initialize() method? I'm wondering if I am not setting something correctly when I change screens. I am able to access the initial screen's containers without problem from all screen 1 methods including the initialize class. When I attempt the same in screen 2, I get the message "screen hasn't been loaded !!!"
Thanks for any help you can provide.
You are using static fields as FXML injection targets which don't work in Java 8 and should not have been implemented to work in any other version, as static UI elements just don't make sense. Here's a bit more detailed answer. Having resorted to a static field most of the time means the software design of the application needs to be reconsidered.

Resources