How can I create my own icon with propertise in JavaFX - javafx

Maybe somebody knows the answer and try help me.
I am creating own button.
<fx:root maxHeight="100.0" maxWidth="100.0" minHeight="50.0" minWidth="50.0" prefHeight="80.0" prefWidth="80.0" style="-fx-background-color: red;" type="StackPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" >
<children>
<ImageView fx:id="baseImage" fitHeight="66.0" fitWidth="72.0" pickOnBounds="true" preserveRatio="true" StackPane.alignment="TOP_CENTER" />
<Label fx:id="textBtn" alignment="BOTTOM_LEFT" prefHeight="17.0" prefWidth="75.0" textFill="WHITE" textOverrun="CLIP" StackPane.alignment="BOTTOM_LEFT" />
</children>
</fx:root>
So I need to change my button (Image and Label), when I am creating this in FXML file.
<MyButton layoutX="200.0" layoutY="162.0" />
e.g
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
Can somebody help me ?
My Java Code
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
init();
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public Label getTextBtn() {
return textBtn;
}
public void setTextBtn(Label textBtn) {
this.textBtn = textBtn;
}
public ImageView getBaseImage() {
return baseImage;
}
public void setBaseImage(Image location) {
this.baseImage.setImage(location);
}
public void setButton(Label textBtn, Image location){
this.baseImage.setImage(location);
this.textBtn = textBtn;
}
But I care about icon are changed in FXML file, not JavaCode
}

If you want to set properties in FXML:
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
you must define those properties in the corresponding class. In particular, MyButton must define setImage(...) and setText(...) methods (it already has setLayoutX(...) and setLayoutY(...) which are inherited from StackPane). It's hard to know exactly what functionality you want here, but you probably want to set these up as JavaFX Properties. If the intention is to map these into the Label and ImageView defined in the FXML file, you can just expose the relevant properties from the controls. You might need to work a bit to map the string from the image property into the correct thing.
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
// not sure what this is:
// init();
// note that if you define
// public void initialize() {...}
// it will be called
// automatically during the FXMLLoader.load() method
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public StringPropergty textProperty() {
return textBtn.textProperty();
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
// similarly expose a property for image, but you need to be able to coerce it from a String
}
(Incidentally, I assume this is just an example for the purposes of understanding how to do this. Everything you have in the example can be done using a regular button. Just wanted to make that clear for any others reading this post.)

Related

JavaFX FXML include fxml causes NullPointerException

I want to extract a button to a new fxml file and change the main label with it. Without extraction it works perfectly.
main.fxml:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<Button onAction="#changeLabel" text="sayHello" />
</VBox>
MainController:
public class MainController {
#FXML
private Label label;
#FXML
private void changeLabel() {
label.setText("Changed");
}
}
With extraction I get NullPointerException in MainController.changeLabel()
main.fxml with include:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
button.fxml:
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Button onAction="#changeLabel" text="sayHello" />
</AnchorPane>
What can cause this NPE?
You should (almost?) always use a different class for controllers for different FXML files. (The only exception I can think of is if you want to define different FXML files to represent different layouts for the same controls.)
One approach is to inject the controller for the included FXML (the "Nested Controller") into the main controller. (See documentation.)
public class MainController {
#FXML
private Label label;
#FXML
private ButtonController buttonController ;
#FXML
private void initialize() {
buttonController.setOnButtonPressed(this::changeLabel);
}
private void changeLabel() {
label.setText("Changed");
}
}
public class ButtonController {
private Runnable onButtonPressed ;
public void setOnButtonPressed(Runnable onButtonPressed) {
this.onButtonPressed = onButtonPressed ;
}
public Runnable getOnButtonPressed() {
return onButtonPressed ;
}
#FXML
private void changeLabel() {
if (onButtonPressed != null) {
onButtonPressed.run();
}
}
}
And then the FXML files look like
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include fx:id="button" source="button.fxml"/>
</VBox>
and
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.ButtonController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
Generally speaking, it's a bad idea for controllers to have references to each other, as it breaks encapsulation and adds unnecessary dependencies. A better approach is to use a MVC design.
public class Model {
private final StringProperty text = new SimpleStringProperty() ;
public StringProperty textProperty() {
return text ;
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
}
Now you can do
public class MainController {
#FXML
private Label label;
private final Model model ;
public MainController(Model model) {
this.model = model ;
}
#FXML
private void initialize() {
label.textProperty().bind(model.textProperty());
}
}
and
public class ButtonController {
private final Model model ;
public ButtonController(Model model) {
this.model = model ;
}
#FXML
private void changeLabel() {
model.setText("Changed");
}
}
The FXML files are as above, and you need to specify a controller factory when you load the FXML (so that the controllers are instantiated by passing the model instance to the constructors):
final Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/main.fxml");
loader.setControllerFactory(type -> {
if (type.equals(MainController.class)) return new MainController(model);
if (type.equals(ButtonController.class)) return new ButtonController(model);
throw new IllegalArgumentException("Unexpected controller type: "+type);
});
Parent root = loader.load();
// ...

JavaFX controller communication with dynamic content

Here is a general structure of a JavaFX android mobile application I am creating.
Using (AppBar or AppBarSearch these interchange dynamically) as nested controllers within Primary Application FXML.
ParentController
- AppBarController
- AppBarSearchController
primary.fxml
- appBar.fxml / appBarSearch.fxml
<AnchorPane fx:id="appBarPane" prefHeight="56.0" prefWidth="350.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<children>
<fx:include source="appbar.fxml" fx:id="appBar" />
<!-- initially shows AppBar but appBarSearch can also be here after clicking search button -->
</children>
</AnchorPane>
A button in each of the child fxml is responsible for changing between the fxml content from appBar/AppBarSearch.
The issue arises when I am dynamically change the content of the appBar to appBarSearch and back. I want the appBar menu button to communicate to the NavigationMenu to slide in and out.
I have been looking into whether I should somehow have an instance of the parentController inside the AppBarController.
I did use the following:
#FXML
private AppBarController appBarController; // injected via <fx:include fx:id="child" ... />
<fx:include source="appbar.fxml" fx:id="appBar" /> <- dynamically changes
//to dynamically change content in Panes
public static void setView(View view, Pane pane) {
try {
pane.getChildren().clear();
pane.getChildren().setAll((Node) FXMLLoader.load(Main.class.getResource(view.getTemplate()), resources));
} catch (IOException e) {
logger.error(e.getMessage());
}
}
#FXML
public void initialize() {
appBarController.setParentController(this);
}
#FXML
private void menuButtonClick (ActionEvent event) {
this.parentController.triggerMenu();
}
Initially the above works but after switching between appBarSearch and appBar it gives me a nullPointer to the parentController instance.
It may be that after switching between controllers dynamically it would not recognise the child controller.
I want the menuBtn in AppBar to open a navigationMenu so it would require to call triggerMenu() from within PrimaryController to start animations of it sliding in and out after the button in AppBar is clicked.
Thanks very much to the comments I was able to fix the issue and improve my understanding of how to use Controllers and included fxml.
Below is the general code I used to connect the child controllers with my primary controller and change views dynamically.
#FXML
private AppBarController appBarController; // injected via <fx:include fx:id="child" ... />
#FXML
private NavMenuController navMenuController; // injected via <fx:include fx:id="child" ... />
#FXML
private MainContentController mainContentController; // injected via <fx:include fx:id="child" ... />
#FXML
public void initialize() {
appBarController.setScreenParent(this);
navMenuController.setScreenParent(this);
mainContentController.setScreenParent(this);
}
/**
* Set View on a Pane Javafx component
*/
public <T extends Pane> boolean setView(View view, T pane) {
try {
FXMLLoader myLoader = new FXMLLoader(getClass().getResource(view.getTemplate()), resources);
pane.getChildren().clear();
pane.getChildren().setAll((Node) myLoader.load());
ControlledScreen childController = myLoader.getController();
childController.setScreenParent(this);
return true;
} catch (IOException e) {
logger.error(e.getMessage());
return false;
}
}
public void setMainContentView(View view) {
setView(view, mainContentPane);
}
public void setAppBarView(View view) {
setView(view, appBarPane);
}
public void setNavMenuView(View view) {
setView(view, navMenuPane);
}
public void triggerNavMenu() {
if (navMenuPane.getTranslateX() != 0) {
openNav.play();
appBarController.setMenuClosedImage();
} else {
closeNav.setToX(-(navMenuPane.getWidth()));
closeNav.play();
appBarController.setMenuClosedImage();
}
}

Concatenate Javafx fx:Id

I'm kinda new to JavaFX and currently trying to do a Calendar application for a school project. I was wondering if there was a way to concatenate a fx:id such a
#FXML
private Label Box01;
(In function)
String ExampleNum = "01";
(Box+ExampleNum).setText("Test");
In addition to the methods mentioned by #jewelsea here are 2 more ways to do this:
Create & inject a Map containing the boxes as values from the fxml:
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.Controller">
<children>
<Label text="foo" fx:id="a"/>
<Label text="bar" fx:id="b"/>
<Spinner fx:id="number">
<valueFactory>
<SpinnerValueFactory.IntegerSpinnerValueFactory min="1" max="2"/>
</valueFactory>
</Spinner>
<Button text="modify" onAction="#modify"/>
<fx:define>
<HashMap fx:id="boxes">
<box1>
<fx:reference source="a"/>
</box1>
<box2>
<fx:reference source="b"/>
</box2>
</HashMap>
</fx:define>
</children>
</VBox>
Controller
public class Controller {
private Map<String, Label> boxes;
#FXML
private Spinner<Integer> number;
#FXML
private Label box1;
#FXML
private Label box2;
#FXML
private void modify(ActionEvent event) {
boxes.get("box"+number.getValue()).setText("42");
}
}
Pass the namespace of the FXMLLoader, which is a Map<String, Object> mapping fx:ids to the associated Objects, to the controller:
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.Controller">
<children>
<Label text="foo" fx:id="box1"/>
<Label text="bar" fx:id="box2"/>
<Spinner fx:id="number">
<valueFactory>
<SpinnerValueFactory.IntegerSpinnerValueFactory min="1" max="2"/>
</valueFactory>
</Spinner>
<Button text="modify" onAction="#modify"/>
</children>
</VBox>
Controller
public class Controller implements NamespaceReceiver {
private Map<String, Object> namespace;
#FXML
private Spinner<Integer> number;
#FXML
private Label box1;
#FXML
private Label box2;
#FXML
private void modify(ActionEvent event) {
((Label)namespace.get("box" + number.getValue())).setText("42");
}
#Override
public void setNamespace(Map<String, Object> namespace) {
this.namespace = namespace;
}
}
public interface NamespaceReceiver {
public void setNamespace(Map<String, Object> namespace);
}
Code for loading the fxml:
public static <T> T load(URL url) throws IOException {
FXMLLoader loader = new FXMLLoader(url);
T result = loader.load();
Object controller = loader.getController();
if (controller instanceof NamespaceReceiver) {
((NamespaceReceiver) controller).setNamespace(loader.getNamespace());
}
return result;
}
Various possible solutions:
You could use reflection, but that would be ugly and I wouldn't advise it.
Normally, if you have a lot of things, you put them in a collection like a list or array. The label will be a child of some layout pane, so you can get the children of the pane and lookup an item by index with something like:
((Label) parent.getChildren().get(0)).setText("Text");
If the label has been assigned a css id then you can use that to lookup the label.
For example, in your FXML define:
<Label text="hello" fx:id="Box01" id="Box01"/>
Then you can lookup the label using:
String boxNum = "01";
Label box = (Label) parent.lookup("#Box" + boxNum);
Just refer to the item by it's reference:
#FXML private Label box01;
box01.setText("Test");
Aside: Please use camel case as per standard Java conventions.

Add setOnAction method to custom Scene Builder Control

So I followed this blog (part 2 in particular) to create a custom control and import it into Scene Builder.
This is what it looks like:
Here is the fxml File:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="30.0" prefWidth="150.0" type="AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<HBox alignment="CENTER" layoutX="-65.0" layoutY="-56.0" spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button fx:id="previousButton" minWidth="25.0" mnemonicParsing="false" prefWidth="25.0" text="<" />
<Label fx:id="label" alignment="CENTER" contentDisplay="CENTER" prefWidth="500.0" text="Label" />
<Button fx:id="nextButton" minWidth="25.0" mnemonicParsing="false" prefWidth="25.0" text=">" />
</children>
</HBox>
</children>
</fx:root>
Here is the class File:
package swipeselector;
import java.io.IOException;
import java.util.ArrayList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
/**
*
* #author patrickb
*/
public class SwipeSelector extends AnchorPane {
#FXML
private Button previousButton;
#FXML
private Label label;
#FXML
private Button nextButton;
ArrayList<String> items;
int selectedIndex;
private boolean repetitive;
public SwipeSelector() {
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("swipeSelectorFXML.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
items = new ArrayList<>();
selectedIndex = 0;
previousButton.setOnAction((ActionEvent event) -> {
setPrevious();
});
nextButton.setOnAction((ActionEvent event) -> {
setNext();
});
}
public void setItems(ArrayList<String> items) {
this.items = items;
selectFirst();
}
public void setPrevious() {
updateItem(selectedIndex - 1);
}
public void setNext() {
updateItem(selectedIndex + 1);
}
public void selectFirst() {
updateItem(0);
}
private void selectLast() {
updateItem(items.size() - 1);
}
private void updateItem() {
if (items.isEmpty()) {
label.setText("");
} else {
if (selectedIndex < 0) {
if (repetitive) {
selectedIndex = items.size() - 1;
} else {
selectedIndex = 0;
}
}
if (selectedIndex >= items.size()) {
if (repetitive) {
selectedIndex = 0;
} else {
selectedIndex = items.size() - 1;
}
}
label.setText(items.get(selectedIndex));
}
}
private void updateItem(int selectedIndex) {
this.selectedIndex = selectedIndex;
updateItem();
}
public void setRepetitive(boolean cyclesThrough) {
this.repetitive = cyclesThrough;
}
}
Everything works fine, BUT: When I click the next or previous button, I want something to happen in my original project. I would usually do this by adding a setOnAction(new EventHandler ... method to the object. This method does not exist though, so I somehow need to add this to my custom control. How do I make my custom control invoke a ActionEvent every time one of the two buttons is clicked, and how do I create a setOnAction Method for my object that will work?
You'll need to create a custom event. Unfortunately, there is little information about this. You can have a look at the source code of ButtonBase to get a sample.
You'll need to define an onAction property:
ObjectProperty<EventHandler<ActionEvent>> onAction
I've implemented a utility class for this: SimpleEventHandlerProperty
You can find a complete sample using SimpleEventHandlerProperty here: LeftTestPane.java
You can get the library from Maven Central:
<dependency>
<groupId>org.drombler.commons</groupId>
<artifactId>drombler-commons-fx-core</artifactId>
<version>0.6</version>
</dependency>
I found a way to make it work. The following code is added to the Class File (it is basically copied from ButtonBase.
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
#Override protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}
#Override
public Object getBean() {
return SwipeSelector.this;
}
#Override
public String getName() {
return "onAction";
}
};
Also, whenever something happens where you want that Action Event to be fired, invoking the method fireEvent(new ActionEvent()); will do this for you. This method goes back to the Node Class, which is inherited to this class as it extends AnchorPane (which inherited the fireEvent method from Node).
So in my case, I replaced my two onAction methods for the two buttons I have by this:
previousButton.setOnAction((ActionEvent event) -> {
setPrevious();
fireEvent(event);
});
nextButton.setOnAction((ActionEvent event) -> {
setNext();
fireEvent(event);
});
Now, whenever one of the two buttons are pressed, an ActionEvent is fired (I just passed on the Buttons Event for this purpose) and I can catch that in my project by adding
swipeSelector.setOnAction((ActionEvent event) -> {
System.out.println("Event fired!!!");
//DO SOMETHING
});
as usual.

On Mouse Entered Method to swap Button position

I would like to develop a mouse entered method that swaps the locations of two buttons in real time using FXML and JavaFX which I am unfortunately very new to. Relocate(x,y), get/setLayoutX/Y and below get/setTranslateX/Y all throw IllegalArgumentEceptions with not much more understandable information in the stack trace. What is the preferred Button Property to use in order to get and then set a real-time location swap?
#FXML protected void neinHover (ActionEvent evt){
double jTmpX, jTmpY, nTmpX, nTmpY;
nTmpX = neinButton.getTranslateX();
nTmpY = neinButton.getTranslateY();
jTmpX = jaButton.getTranslateX();
jTmpY = jaButton.getTranslateY();
jaButton.setTranslateX(nTmpX);
jaButton.setTranslateY(nTmpY);
neinButton.setTranslateX(jTmpX);
neinButton.setTranslateY(jTmpY);
}
I supose you want something like this:
FXML:
<fx:root onMouseClicked="#swap" type="Pane" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
<children>
<Button fx:id="b1" mnemonicParsing="false" text="1" />
<Button fx:id="b2" layoutX="90.0" mnemonicParsing="false" text="2" />
</children>
</fx:root>
Controller:
public class MockPane extends Pane {
#FXML
private Button b1;
#FXML
private Button b2;
public MockPane() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(
"MockPane.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#FXML
private void swap() {
double b1x = b1.getLayoutX();
double b1y = b1.getLayoutY();
double b2x = b2.getLayoutX();
double b2y = b2.getLayoutY();
b1.setLayoutX(b2x);
b1.setLayoutY(b2y);
b2.setLayoutX(b1x);
b2.setLayoutY(b1y);
}
}
App:
public class MockApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
MockPane root = new MockPane();
Scene scene = new Scene(root, 200, 100);
stage.setScene(scene);
stage.show();
}
}

Resources