1
votes

In a JavaFX program I'm developing, I have a StackPane. The first child is a BorderPane with other stuff in it.

I want to be able to show pop-ups which darken everything but the pop-up, to create extra focus. That's why I created a "DarkenStackPane" which extends StackPane. On creation (in constructor), it adds a Rectangle with 0.0 opacity to its children. It has two functions, darken() and brighten(). darken() has a FadeTransition from 0.0 to 0.5, brighten() the opposite.

I add a Pane to the StackPane, this calls the darken() function. The darken() function first brings the black Rectangle to front, followed by bringing the Pane to front.

It works fine, but StackPane resizes all of its children to fit its size... I don't want this to happen. Instead I want the pop-up Pane with its own width and height to be centered in the StackPane.

I know I can use StackPane.setMargin (it works with hard-coded values), but I can't determine the correct margins 'cause on creation the Pane doesn't have a parent yet.

Is there any method that gets called when your Pane is getting added to a parent?

I tried doing this with a ChangeListener, but I couldn't bind the properties in the anonymous function.

1

1 Answers

5
votes

Consider using ControlsFX

The ControlsFX library has a feature called "lightweight dialogs" which performs this same dialog overlay effect. Using a pre-built library for this kind of solution is often a better option than rolling your own implementation.

lightweight

If you choose to roll your own solution instead of using ControlsFX, then read on . . .

Solution

Place the overlay child pane of your StackPane in a Group.

StackPane stack = new StackPane(mainPane, new Group(popupPane)); 

Why this Works

A Group is not resizable, so the effect will be that the mainPane is resizable, and the popupPane will assume it's internal preferred size and placed within a Group which will be centered on top of the mainPane by the StackPane.

Demonstration

The DialogFactory in a project I wrote a long time ago demonstrates this approach to overlay dialogs on top of a WebView. The screenshot below shows the output of running the willow-0.3-prerelease-jar under Java 8 and navigating to http://www.w3schools.com/js/tryit.asp?filename=tryjs_alert, then clicking on the "Try it" button. What it does is overlay the alert box (rendered and programmed in JavaFX, not JavaScript) centered on top of a WebView control which is disabled and darkened while the alert is displayed.

alert

Sample Source

This is selected source from the willow project, it is not standalone, but you will need to adjust it slightly to adopt it to your situation. The code is just presented here to demonstrate the technique and pattern.

/**
 * Overlay a dialog on top of the WebView.
 *
 * @param dialogNode the dialog to overlay on top of the view.
 */
private void overlayView(Node dialogNode) {
    // if the view is already overlaid we will just ignore this overlay call silently . . . todo probably not the best thing to do, but ok for now.
    if (!(webView.getParent() instanceof BorderPane)) return;

    // record the view's parent.
    BorderPane viewParent = (BorderPane) webView.getParent();

    // create an overlayPane layering the popup on top of the webview
    StackPane overlayPane = new StackPane();
    overlayPane.getChildren().addAll(webView, new Group(dialogNode));
    webView.setDisable(true);

    // overlay the popup on the webview.
    viewParent.setCenter(overlayPane);
}

/**
 * Removes an existing dialog overlaying a WebView.
 */
private void removeViewOverlay() {
    BorderPane viewParent = (BorderPane) webView.getParent().getParent();
    viewParent.setCenter(webView);
}

public EventHandler<WebEvent<String>> createAlertHandler() {
    return stringWebEvent -> {
        AlertHandler alertHandler = new AlertHandler(
                stringWebEvent.getData(),
                event -> {
                    webView.setDisable(false);
                    removeViewOverlay();
                }
        );
        overlayView(alertHandler);

        // todo block until the user accepts the alert.
    };
}

. . .

// add an effect for disabling and enabling the view.
getView().disabledProperty().addListener(new ChangeListener<Boolean>() {
    final BoxBlur soften = new BoxBlur();
    final ColorAdjust dim = new ColorAdjust();
    {
        dim.setInput(soften);
        dim.setBrightness(-0.5);
    }

    @Override
    public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue) {
        if (newValue) {
            getView().setEffect(dim);
        } else {
            getView().setEffect(null);
        }
    }
});

. . . 

public class AlertHandler extends VBox {
    public AlertHandler(String message, EventHandler<ActionEvent> confirmHandler) {
        super(14);

        // add controls to the popup.
        final Label promptMessage = new Label(message);
        final ImageView alertImage = new ImageView(ResourceUtil.getImage("alert_48.png"));
        alertImage.setFitHeight(32);
        alertImage.setPreserveRatio(true);
        promptMessage.setGraphic(alertImage);
        promptMessage.setWrapText(true);
        promptMessage.setPrefWidth(350);

        // action button text setup.
        HBox buttonBar = new HBox(20);
        final Button confirmButton = new Button(getString("dialog.continue"));
        confirmButton.setDefaultButton(true);

        buttonBar.getChildren().addAll(confirmButton);

        // layout the popup.
        setPadding(new Insets(10));
        getStyleClass().add("alert-dialog");
        getChildren().addAll(promptMessage, buttonBar);

        final DropShadow dropShadow = new DropShadow();
        setEffect(dropShadow);

        // confirm and close the popup.
        confirmButton.setOnAction(confirmHandler);
    }
}

. . . 

getView().getEngine().setOnAlert(dialogFactory.createAlertHandler());

Really, the only reason I wrote the code above was because there was no equivalent library to ControlsFX available at the time. If I were developing a project from scratch, I would first try to use ControlsFX.