Wizard with JavaFX Accordions - javafx

I'm writing a small configuration-wizard-style JavaFX application. I want to force the user through different steps of configuration panes. So I've chosen an accordion container with a few panels. The user is only allowed to proceed to the NEXT or PREVIOUS accordion panel, but may not jump from panel 1 to 3.
I achived this by adding the "Previous" and "Next" button under the accordion and I set the mouse transparency of the accordion to true. Everything works fine so far. The user can walk from panel 1 to 5 and back...
Unfortunately (for any strange reason) the user cannot configure nothing inside the different panels. Wait... I set the mouse transparency to "true", right... But when I set it to false, the user can jump through the panels without any order.
Any suggestions?

One possible way is to set the collapsible property of all the panes to false, and to set each one to true just when it needs to be expanded or collapsed (which you can do programmatically). This is a little ugly, but here's an example:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class AccordionBasedWizard extends Application {
#Override
public void start(Stage primaryStage) {
Accordion accordion = new Accordion();
for (int i = 0 ; i < 5 ; i++) {
TitledPane pane = new TitledPane("Page "+(i+1), new Label("Wizard page "+(i+1)));
accordion.getPanes().add(pane);
pane.setCollapsible(false);
}
accordion.setExpandedPane(accordion.getPanes().get(0));
Button previousButton = new Button("Previous");
previousButton.disableProperty().bind(accordion.expandedPaneProperty().isEqualTo(accordion.getPanes().get(0)));
previousButton.setOnAction(e -> {
TitledPane current = accordion.getExpandedPane();
int index = accordion.getPanes().indexOf(current);
TitledPane previous = accordion.getPanes().get(index - 1);
current.setCollapsible(true);
previous.setCollapsible(true);
accordion.setExpandedPane(previous);
previous.setCollapsible(false);
current.setCollapsible(false);
});
Button nextButton = new Button("Next");
nextButton.disableProperty().bind(accordion.expandedPaneProperty().isEqualTo(accordion.getPanes().get(accordion.getPanes().size()-1)));
nextButton.setOnAction(e -> {
TitledPane current = accordion.getExpandedPane();
int index = accordion.getPanes().indexOf(current);
TitledPane next = accordion.getPanes().get(index + 1);
current.setCollapsible(true);
next.setCollapsible(true);
accordion.setExpandedPane(next);
next.setCollapsible(false);
current.setCollapsible(false);
});
HBox buttons = new HBox(5, previousButton, nextButton);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(5));
BorderPane root = new BorderPane(accordion);
root.setBottom(buttons);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For what it's worth, I probably wouldn't try to use an Accordion as a wizard, and would just use a BorderPane, with the current "wizard page" in the center and next and previous buttons in the bottom. You can add a breadcrumb trail too if you need.

Related

Popup with ScrollPane makes main Stage TextField unresponsive to key events like LEFT, RIGHT, HOME, END

I want to create a Popup for main Stage TextField. Popup contains ScrollPane which holds possible options as Buttons.
After Popup shown TextField key events like left, rigth, home, end have no effect. Key events are received on TextField.
Is there any reasonable solution or workaround for this issue.
To reproduce please type in some text and try to press left arrow.
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class PopupApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
TextField textField = new TextField();
StackPane stackPane = new StackPane(textField);
stage.setScene(new Scene(stackPane));
stage.show();
ScrollPane scrollPane = new ScrollPane(new VBox(new Button("Option1"), new Button("Option2")));
Popup popup = new Popup();
popup.getContent().add(scrollPane);
Point2D pinPoint = textField.localToScreen(0., textField.getHeight());
popup.show(textField, pinPoint.getX(), pinPoint.getY());
textField.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
System.out.println("KEY_RELEASED " + event);
});
textField.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
System.out.println("KEY_PRESSED " + event);
});
}
}
Actually, the KEY_PRESSED event is not being received by your textField when pressing Keys like Left, Right, ... This is shown by the output from your program.
You are facing this problem because Events are being redirected to Popup. If these events are consumed while dispatching, then the effect of these events are not shown by textField. In your case it is due to the presence of Button on Popup. (Most likely due to the presence of node that can receive focus. Not sure though). To solve this you can set your own EventDispatcher. Something like this before popup.show():
popup.setEventDispatcher((event, tail) -> {
if (event.getEventType() != RedirectedEvent.REDIRECTED) {
tail.dispatchEvent(event);
}
return null;
});
All the redirected events into the Popup will now be discarded and further dispatching does not occur. You can tweak this to suit your requirement.

JavaFX Slider : How to drag the thumb only by increments

I am trying to implement the Slider such that user can drag only by given increments. I tried in different ways by using the Slider API, but didnt get the desired results. Below is a quick demo of what I had tried. I am expecting to drag the thumb only in increments of 10 not with intermediate values. snapToTicks is doing what I required, but only after finishing the drag. I am trying to not move the thumb till the next desired block increment is reached.
Can anyone let me know how can i achieve this. Below is the screenshot while dragging.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SliderDemo extends Application {
public static void main(String... args){
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Label label = new Label();
label.setStyle("-fx-font-size:30px");
Slider slider = new Slider(5,240,5);
slider.setBlockIncrement(10);
slider.setMajorTickUnit(10);
slider.setMinorTickCount(0);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.setSnapToTicks(true);
slider.valueProperty().addListener((obs,old,val)->label.setText((int)Math.round(val.doubleValue())+""));
VBox root = new VBox(slider,label);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
root.setSpacing(20);
Scene scene = new Scene(root,600,200);
stage.setScene(scene);
stage.show();
}
}
The solution is to set the value of the slider directly inside of the listener. The listener will not be called again
final ChangeListener<Number> numberChangeListener = (obs, old, val) -> {
final double roundedValue = Math.floor(val.doubleValue() / 10.0) * 10.0;
slider.valueProperty().set(roundedValue);
label.setText(Double.toString(roundedValue));
};
slider.valueProperty().addListener(numberChangeListener);
If you use Math.floor() instead of round you get a more intuatuive behavior of the thumb.

Java FX TextField blur

Can anyone tell me why sometimes JavaFX displays the content of a TextField with a blur effect on it? It seems to be random and occurs in any of my TextFields. Please see the image attached.
Focusing on the intermittent rendering artifact mentioned here, the 2 glyph looks like it's been rendered twice, with one copy shifted horizontally relative to the other. Such apparently random anomalies are notoriously difficult to identify. Myriad causes may include incorrect synchronization, improper layout, defects in the host platform's rendering pipeline, etc. For reference, the example below may allow you to test on disparate platforms.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/a/53989899/230513
*/
public class TextFieldTest extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("TextFieldTest");
BorderPane root = new BorderPane();
root.setCenter(createContent());
root.setBottom(createVersion());
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private Node createContent() {
HBox row1 = new HBox(4);
Label channelsLabel = new Label("Channels:");
TextField channelsText = new TextField("2");
channelsText.setPrefWidth(32);
Label separatorLabel = new Label("Separator:");
TextField separatorText = new TextField("!");
separatorText.setPrefWidth(32);
row1.setPadding(new Insets(8));
row1.getChildren().addAll(
channelsLabel, channelsText, separatorLabel, separatorText);
HBox row2 = new HBox(4, new Label("Label:"), new TextField());
row2.setPadding(new Insets(8));
return new VBox(row1, row2);
}
private Label createVersion() {
Label label = new Label(
System.getProperty("os.name") + " v"
+ System.getProperty("os.version") + "; Java v"
+ System.getProperty("java.version"));
label.setPadding(new Insets(8));
return label;
}
public static void main(String[] args) {
launch(args);
}
}
As shown in the Modena example, an intentional blur effect indicates that the text field is focused:
The detail that gives rise to the blurred effect in your image is a compound border, seen below at 2x:
Comparable effects are seen here for buttons (top row) and default buttons (bottom row):

How to vertically center content of Alert/DialogPane?

I created an Alert that is undecorated and I want the content to be centered in the alert. However, there seems to be some padding at the bottom that I can not get rid of.
Here is an MCVE:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Alert alert = new Alert(Alert.AlertType.NONE);
DialogPane pane = new DialogPane();
HBox contentPane = new HBox(10);
contentPane.setPadding(new Insets(10));
contentPane.setAlignment(Pos.CENTER);
// Add progress indicator and label to contentPane
contentPane.getChildren().addAll(
new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS) {{
setPrefSize(30, 30);
}},
new Label("Loading ...")
);
// Add border to demonstate the lower padding
contentPane.setStyle("-fx-border-color: black");
pane.setPadding(new Insets(10));
pane.setStyle("-fx-border-color: black");
pane.setContent(contentPane);
alert.setDialogPane(pane);
alert.initStyle(StageStyle.UNDECORATED);
alert.showAndWait();
}
}
The result is this window:
How do I create an Alert or DialogPane without that gap at the bottom?
Using:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
private static Border createBorder(Paint stroke) {
return new Border(new BorderStroke(stroke, BorderStrokeStyle.SOLID,
null, new BorderWidths(2)));
}
#Override
public void start(Stage primaryStage) throws Exception {
ProgressIndicator indicator = new ProgressIndicator();
indicator.setPrefSize(30, 30);
Label label = new Label("Loading...");
label.setMinWidth(Label.USE_PREF_SIZE);
HBox content = new HBox(10, indicator, label);
content.setBorder(createBorder(Color.BLUE));
content.setPadding(new Insets(10));
content.setAlignment(Pos.CENTER);
Alert alert = new Alert(Alert.AlertType.NONE);
alert.initStyle(StageStyle.UNDECORATED);
alert.getDialogPane().getStylesheets()
.add(getClass().getResource("Main.css").toExternalForm());
alert.getDialogPane().setPadding(new Insets(10));
alert.getDialogPane().setContent(content);
alert.getDialogPane().setBorder(createBorder(Color.RED));
alert.show();
}
}
Where this is Main.css:
.dialog-pane .button-bar .container {
-fx-padding: 0px;
}
.dialog-pane:no-header .graphic-container {
-fx-padding: 0px;
}
Resulted in the following:
Some of the needed style-classes I got from here (JavaFX CSS Reference). However, I mostly figured this out from looking at modena.css (where they have the styles for dialog-pane).
If you don't want to use external CSS you can replace alert.getDialogPane().getStylesheets().add(...) with:
alert.getDialogPane().applyCss(); // Seems to stop the lookup calls
// from returning null
alert.getDialogPane()
.lookup(".button-bar")
.lookup(".container")
.setStyle("-fx-padding: 0px;");
alert.getDialogPane().lookup(".graphic-container").setStyle("-fx-padding: 0px;");
alert.show();
Update following your comments.
I initially tried this using Java 10.0.2 but I just tried using Java 8u181 and it still worked for me. I notice in your MCVE you are using a new DialogPane whereas I am using the DialogPane that initially comes with the Alert. Note that the applyCss() method will only work if the Node is a part of a Scene. Apparently the DialogPane is made part of a Scene as soon as it's part of the Alert. So one way you could avoid the NullPointerExceptions is to use the DialogPane that comes with the Alert, as I do.
If you still want to use your own DialogPane you just have to call alert.setDialogPane before calling applyCss(). Note that when I tried this, however, the call lookup(".graphic-container") still returned null. I guess that Node is only present on the original DialogPane. Removing that lookup call fixed it and the other lookup call (for .button-bar > .container) still worked as expected.
All this seems like implementation behavior that should not be relied upon but it works for me on both Java 8 and Java 10. I'd watch this code when changing Java versions just in case though.

Javafx make combobox expand upward

I have a window which has a combobox at the bottom. When I click on it, not all the options are visible because they are not inside the screen area anymore. How can I make the drop-down list display upwards instead of downards?
I have the ComboBox defined in SceneBuilder. I define it in my code this way:
#FXML
ComboBox fontsComboBox;
In my initialize() method of the controller assigned to that window I have set some properties:
fontComboBox.setVisibleRowCount(3);
fontComboBox.getItems().addAll(fontList);
fontComboBox.setValue(fontList[0]);
I'm pretty sure I need to add something here in order to make the dropdown list fo up.
Thanks,
Serban
This was a known bug till Java 8u45. It is fixed in Java 8u60.
https://bugs.openjdk.java.net/browse/JDK-8092942 (fixed in 8u60)
You can download JDK/JRE 8u60ea (early access) here to give it a try: https://jdk8.java.net/download.html
Java 8 Update 60 is scheduled GA for August 2015.
This example shows the problem. Simply open the combobox, even if you move the window down to screen, it won't show the list above the box.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ComboBoxTester extends Application {
#Override
public void start(Stage primaryStage) {
List<Integer> values = new ArrayList<>();
for (int i = 1; i < 100; i++) {
values.add(i);
}
ObservableList<Integer> items = FXCollections.observableArrayList(values);
ComboBox<Integer> comboBox = new ComboBox<>(items);
comboBox.getSelectionModel().clearAndSelect(0);
comboBox.setVisibleRowCount(5);
BorderPane root = new BorderPane();
root.setBottom(comboBox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Java 8 Update 45 behaviour
Java 8 Update 60 ea behaviour

Resources