JavaFX - Trying to create my own custom button class - javafx

I am trying to create Button class with specific styling.
public class RoundBTN extends Button{
public RoundBTN(String name){
Button roundButton = new Button(name);
roundButton.setStyle("-fx-background-color: #20B2AA; -fx-background-radius: 15px; -fx-text-fill: #ffffff");
getChildren().add(roundButton);
}
}
Then when I go to my application class try to build a new button:
#Override
public void start(Stage stage) throws Exception {
StackPane layout = new StackPane();
layout.setPadding(new Insets(5));
layout.getChildren().add(new RoundBTN("test"));
stage.setScene(new Scene(layout,200,200));
stage.show();
}
When I run the program I get an empty normal button with no styling.
Sorry for the noob question, but I cannot get it to work.

You are creating a new Button in the constructor of class RoundBTN. This does not change RoundBTN at all.
The first thing you need to do in the constructor of class RoundBTN is to call the superclass constructor. Then you do not create a new Button but rather just set the style.
import javafx.scene.control.Button;
public class RoundBTN extends Button {
public RoundBTN(String name){
super(name);
setStyle("-fx-background-color: #20B2AA; -fx-background-radius: 15px; -fx-text-fill: #ffffff");
}
}
But if all you want to do is just change the styling, you don't need to extend class Button, just create a regular Button and set its styling.
Button b = new Button("test");
b.setStyle("-fx-background-color: #20B2AA; -fx-background-radius: 15px; -fx-text-fill: #ffffff");

Related

How to chain various MouseEvent on 1 click?

On javafx, I have a GridPane with buttons. Each button has a number on them that increases when a leftclick on the button happens and it decreases when a rightclick happens.
I want to know how can I make it so that when the number of one button reaches a certain value (5 for example), then the mouseEvent activates on the buttons that surround the button.
Also if one of those buttons reaches that certain value too, then the mouseEvent activates on its surrounding buttons as well and so on and so forth.
For increasing, decreasing and showing the number of the buttons, I wrap the Button class in an inner class called NumberButton.
Here is my code:
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Main extends Application {
BorderPane root= new BorderPane();
GridPane grid= new GridPane();
class NumberButton extends Button{
private int innerNumber;
public NumberButton(){
this.innerNumber=0;
showNumber();
}
public void showNumber(){
this.setText(String.valueOf(innerNumber));
}
public void increaseInnerNumber(){
this.innerNumber++;
showNumber();
}
public void decreaseInnerNumber(){
this.innerNumber--;
showNumber();
}
public int getInnerNumber() {
return this.innerNumber;
}
}
#Override
public void start(Stage primaryStage) throws Exception{
NumberButton[][] buttons= new NumberButton[10][10];
for (int i=0; i<buttons.length;i++){
for (int j=0; j<buttons[i].length;j++){
buttons[i][j]= new NumberButton();
NumberButton button= buttons[i][j];
button.setOnMouseClicked(mouseEvent -> {
if (mouseEvent.getButton()== MouseButton.PRIMARY){
button.increaseInnerNumber();
if (button.getInnerNumber()==5){
/*all surrounding buttons activate the mouseEvent
* and mouseEvent.getButton()== MouseButton.PRIMARY is true */
}
}else if (mouseEvent.getButton()==MouseButton.SECONDARY){
button.decreaseInnerNumber();
}
});
button.setMinSize(30,30);
button.setMaxSize(30,30);
grid.add(button,i,j);
}
}
root.setCenter(grid);
primaryStage.setTitle("Number Buttons!");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have tried using button.setOnAction() but then I don't know how to get if the event was triggered by a leftclick or a rightclick and when I tried using both button.setOnAction() and button.setOnMouseClicked() then the event triggered twice and I don't want that.

Why is my javafx label not showing after it being changed?

I'm new to javafx programming, and i dont understand why my javafx Text isn't getting updated, when it is changed.
I want to make a timer, that counts from 60 to 0. I'm trying to change the timeCounter Text, for every second that has passed.
Help would be appreciated!
Here's my controller code:
public class Controller {
TimerUtil timerUtil;
#FXML
private Button startButton;
#FXML
private Text timeCounter;
#FXML
private Text pointCounter;
#FXML
private Circle circle;
#FXML
private void handleStartButtonClick(ActionEvent event) {
timerUtil = new TimerUtil();
}
private class TimerUtil extends Pane {
private int tmp = 60;
private Timeline animation;
public TimerUtil(){
getChildren().add(timeCounter);
animation = new Timeline(new KeyFrame(Duration.seconds(1), e -> timeLabel()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
}
private void timeLabel(){
if(tmp > 0){
tmp--;
}
timeCounter.setText(String.valueOf(tmp));
System.out.println(tmp);
}
}
}
Your error occurs because the label has been silently removed from it's displayed parent node:
You have your TimerUtil class extend Pane (I have no idea why).
You add the timeCounter text to the TimeUtil pane (again, I have no idea why).
Adding the timeCounter text to the TimeUtil pane will silently remove it from the parent which the FXML loader injected it into.
You are probably only displaying the parent which the FXML loader injected.
You are never displaying the TimerUtil pane.
Therefore, even though the text is getting updated by your timeline, you never see it.
To better understand your error, read:
JavaFX - Why does adding a node to a pane multiple times or to different panes result in an error?
From the Node javadoc:
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent.
Once you fix your error, the basic concept works for me. Here is the runnable example I created from your code:
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Timer extends Application {
private int tmp = 60;
private Text counter = new Text();
private Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(1), e -> updateCounter())
);
#Override
public void start(Stage stage) {
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
StackPane layout = new StackPane(counter);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
private void updateCounter() {
if (tmp > 0){
tmp--;
} else {
animation.stop();
}
counter.setText(String.valueOf(tmp));
System.out.println(tmp);
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX Dynamic Form Field UI

Does anyone know how to imitate the functionality from the UI components shown below? I want to replicate adding form fields when text is entered into the TextField box. I don't need the dropdown button, just the dynamic adding of the forms.
You could modify the children of a GridPane adding a new TextField & Button every time one of the buttons is activated. Listen to the text properties to enable/disable the Button and save the results.
private static void insertRow(GridPane grid, List<String> values, int index) {
// increment index of children with rowIndex >= index
for (Node n : grid.getChildren()) {
int row = GridPane.getRowIndex(n);
if (row >= index) {
GridPane.setRowIndex(n, row + 1);
}
}
TextField text = new TextField();
Button add = new Button("+");
add.setDisable(true);
add.setOnAction(evt -> {
insertRow(grid, values, GridPane.getRowIndex(add) + 1);
});
values.add(index, "");
text.textProperty().addListener((a, oldValue, newValue) -> {
add.setDisable(newValue.isEmpty());
values.set(GridPane.getRowIndex(add), newValue);
});
grid.addRow(index, text, add);
}
#Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
List<String> list = new ArrayList<>();
insertRow(grid, list, 0);
Button print = new Button("print");
print.setOnAction(evt -> {
System.out.println(list);
});
grid.add(print, 0, 1);
Scene scene = new Scene(grid, 300, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
This may not be exactly what you're looking for and may not be the best way to do this, but should be easy to adapt it to your needs.
Basically, you will need a list of HBox objects to be added to a VBox in your application. You could create the list yourself and bind it to the children of your VBox, or just add/remove the HBoxes to/from the VBox using the getChildren().add() and getChildren().remove() methods.
Here is a complete little application to demonstrate the concept. I created an internal class to handle the HBox with the fields you need. This could be adapted to be more felixable:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private static VBox mainPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
mainPane = new VBox(5);
mainPane.setPadding(new Insets(10));
mainPane.setAlignment(Pos.TOP_CENTER);
mainPane.getChildren().add(new UIForms());
primaryStage.setScene(new Scene(mainPane));
primaryStage.show();
}
static void addField() {
mainPane.getChildren().add(new UIForms());
}
static void removeField(UIForms field) {
if (mainPane.getChildren().size() > 1) {
mainPane.getChildren().remove(field);
}
}
}
class UIForms extends HBox {
private TextField textField1;
private TextField textField2;
private Button btnAddField;
private Button btnRemoveField;
public UIForms() {
// Setup the HBox layout
setAlignment(Pos.CENTER_LEFT);
setSpacing(5);
// Create the UI controls
textField1 = new TextField();
textField2 = new TextField();
btnAddField = new Button("+");
btnRemoveField = new Button("-");
// Setup button actions
btnAddField.setOnAction(e -> Main.addField());
btnRemoveField.setOnAction(e -> Main.removeField(this));
// Add the UI controls
getChildren().addAll(
textField1, textField2, btnAddField, btnRemoveField
);
}
}

Select text from GridPane

I have this very simple example of GridPane.
GridPane playerGrid = new GridPane();
Text title = new Text("Top Scorers in English Premier League");
title.setFont(Font.font("Arial", FontWeight.BOLD, 20));
playerGrid.add(title, 0,0,4,1);
How I can select the text with the mouse and copy it when the program is running?
Text nodes in JavaFX are not selectable.
If you want to have text selectable, use a selection aware control.
The fact that the text is eventually placed in a GridPane is irrelevant to this question.
For example, use a read only TextField:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class SelectableTextSample extends Application {
#Override public void start(final Stage stage) throws Exception {
stage.setScene(
new Scene(
new SelectableText(
"Top Scorers in English Premier League"
)
)
);
stage.show();
}
class SelectableText extends TextField {
SelectableText(String text) {
super(text);
setEditable(false);
setPrefColumnCount(20);
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
Alternate solution
You could use a WebView if you wish. For some situations that may be a better solution, but for others it may not.

JavaFX - Unique scene per stage

I have a class that extends Application and calls the primary stage. This primary stage has a Next Button, that will call another stage (options stage). The options stage has a Previous Button.
I'd like to get the instance of the primary stage, in the state it was before the user clicked Next Button, for example: a textfield with input data or combobox with selected item.
How can I do that?
Main class:
public class MainClass extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
final FXMLLoader loader = new FXMLLoader(getClass().getResource("interfaceOne.fxml"));
final Parent root = (Parent)loader.load();
final MyController controller = loader.<MyController>getController();
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
Platform.setImplicitExit(false);
controller.setStage(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MyController:
public class MyController{
// Some declarations ...
Stage stage = null;
public void setStage(Stage stage) {
this.stage = stage;
}
// Next button's action
#FXML
public void handleNextAction(ActionEvent event) {
try {
Parent root = FXMLLoader.load(getClass().getResource("optionInterface.fxml"));
stage.initStyle(StageStyle.TRANSPARENT);
stage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
stage.setScene(new Scene(root));
stage.show();
// Hide the current screen
((Node)(event.getSource())).getScene().getWindow().hide();
} catch (Exception exc) {
System.out.println("Error: " + exc.getMessage());
}
}
}
Options Controller:
public class OptionsController implements Initializable {
public void handlePreviousAction(ActionEvent event) {
try {
Parent root = FXMLLoader.load(getClass().getResource("interfaceOne.fxml"));;
MyController controller = MyController.getInstance();
stage.initStyle(StageStyle.TRANSPARENT);
stage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
stage.setScene(new Scene(root));
controller.setStage(stage);
controller.isLocationLoaded(false);
stage.show();
// Hide the current screen
((Node)(event.getSource())).getScene().getWindow().hide();
} catch (IOException exc) {
System.out.println("Error: " + exc.getMessage());
}
}
}
Recommended Approach
Don't use multiple stages for this, instead use a single stage and multiple scenes or layered Panes.
Sample References
Angela Caicedo's sophisticated Scene switching tutorial.
A wizard style configuration.
Background
Read over a discussion of the theater metaphor behind JavaFX to help understand the difference between a Stage and a Scene and why you want to probably be changing scenes in and out of your application rather than stages.
Simple Sample
I created a simple sample based upon your application description which just switches back and forth between a main scene and an options scene. As you switch back and forth between the scenes, you can see that the scene state is preserved for both the main scene and the options scene.
For the sample, there is just a single stage reference, which is passed to the application in it's start method and the stage reference is saved in the application. The application creates a scene for the main screen and another for the options screen, saving both scene references switches the currently displayed scene back and forth between these references as required using stage.setScene.
The demo is deliberately simple to make it easy to understand and does not persist any of the data used or make use of a MVC style architecture or FXML as might be done in a more realistic demo.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class RoomReservationNavigator extends Application {
public static void main(String[] args) { Application.launch(args); }
private Scene mainScene;
private Scene optionsScene;
private Stage stage;
#Override public void start(Stage stage) {
this.stage = stage;
mainScene = createMainScene();
optionsScene = createOptionsScene();
stage.setScene(mainScene);
stage.show();
}
private Scene createMainScene() {
VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
layout.getChildren().setAll(
LabelBuilder.create()
.text("Room Reservation System")
.style("-fx-font-weight: bold;")
.build(),
HBoxBuilder.create()
.spacing(5)
.children(
new Label("First Name:"),
new TextField("Peter")
)
.build(),
HBoxBuilder.create()
.spacing(5)
.children(
new Label("Last Name:"),
new TextField("Parker")
)
.build(),
new Label("Property:"),
ChoiceBoxBuilder.<String>create()
.items(FXCollections.observableArrayList(
"The Waldorf-Astoria",
"The Plaza",
"The Algonquin Hotel"
))
.build(),
ButtonBuilder.create()
.text("Reservation Options >>")
.onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
stage.setScene(optionsScene);
}
})
.build(),
ButtonBuilder.create()
.text("Reserve")
.defaultButton(true)
.onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
stage.hide();
}
})
.build()
);
return new Scene(layout);
}
private Scene createOptionsScene() {
VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: azure; -fx-padding: 10;");
layout.getChildren().setAll(
new CheckBox("Breakfast"),
new Label("Paper:"),
ChoiceBoxBuilder.<String>create()
.items(FXCollections.observableArrayList(
"New York Times",
"Wall Street Journal",
"The Daily Bugle"
))
.build(),
ButtonBuilder.create()
.text("Confirm Options")
.defaultButton(true)
.onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
stage.setScene(mainScene);
}
})
.build()
);
return new Scene(layout);
}
}

Resources