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.
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.
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.