Raymond Nagel
Raymond Nagel

Reputation: 81

Extending JavaFX Stage content with Popup(?)

It's my first post here - please be nice :)

What I'm trying to do is have controls/windows that:

  1. belong to a JavaFX Stage
  2. will NOT steal focus from the owning Stage when clicked
  3. can be dragged beyond the boundaries of the owning Stage

I've been able to implement this in two ways; however, both are flawed. First, I've made draggable controls within the Stage's content; however, they cannot be dragged outside the Stage's area. Second, I've created them as draggable Popups anchored to the Stage; however, they remain on top of EVERYTHING, even when other windows are moved on top of the owning Stage.

What I'm asking is: Is there any way to either drag a control outside the bounds of it's owning Stage; or to make a Popup NOT always appear on top of everything?

I've found a similar question here, explaining the issue with the Popup being on top (Javafx popup won't hide behind other application upon losing focus). But there was no acceptable solution there (I don't want the Popup to "hide" when the Stage or Application loses focus).

Thanks for reading. If you could suggest anything, it would be appreciated.

Below is an MCV example of what I tried with MoveablePopup. It works fine, except for when you bring additional windows over the top.

package moveablepopuptest;

import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Popup;
import javafx.stage.Stage;

public class MoveablePopupTest extends Application
{    

    @Override
    public void start(Stage primaryStage)
    {        
        // Set up Primary Stage:
        StackPane root = new StackPane();        
        Scene scene = new Scene(root, 400, 400);        
        primaryStage.setTitle("MoveablePopup Test");
        primaryStage.setScene(scene);
        primaryStage.show();


        // Show a MoveablePopup for an associated node in the Stage:
        MoveablePopup popup = new MoveablePopup();
        popup.setTitle("Extension");
        Pane popupContent = new Pane();
        popupContent.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
        popupContent.setMinSize(200, 200);
        popup.setContent(popupContent);
        popup.show(root, 0, 0);        

        // When Primary Stage is moved, re-anchor the popup:
        ChangeListener<Number> windowMoveListener = (ObservableValue<? extends Number> observable, Number oldValue, Number newValue) ->
        {
             popup.anchorToOwner();
        };       
        primaryStage.xProperty().addListener(windowMoveListener);
        primaryStage.yProperty().addListener(windowMoveListener);
    }

    public static void main(String[] args)
    {
        launch(args);
    }

    public class MoveablePopup extends Popup
    {    
        private final BorderPane bgPane = new BorderPane(); 
        private final DoubleProperty ownerXAnchorProperty = new SimpleDoubleProperty(0);
        private final DoubleProperty ownerYAnchorProperty = new SimpleDoubleProperty(0);        
        private final Label titleLabel = new Label("Title");
        private Point2D lastMouse = null;      
        private Node content = null;

        public MoveablePopup()    
        {   
            // Don't want Esc to close the Popup:
            setHideOnEscape(false);

            // Create a border:
            bgPane.setBorder(new Border(new BorderStroke(Color.DARKORANGE, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2))));

            // Create a title bar for the top, which will also function as a handle to move the Popup:
            StackPane barPane = new StackPane();  
            titleLabel.setTextFill(Color.WHITE);
            barPane.getChildren().add(titleLabel);            
            barPane.setCursor(Cursor.MOVE);
            barPane.setBackground(new Background(new BackgroundFill(Color.DARKORANGE, CornerRadii.EMPTY, Insets.EMPTY)));
            barPane.setMaxSize(Double.MAX_VALUE, 24);
            bgPane.setTop(barPane);                         

            // Add drag/anchoring functionality to the Popup
            barPane.setOnMousePressed((MouseEvent event) ->
            {
                lastMouse = new Point2D(event.getScreenX(), event.getScreenY());
            });
            barPane.setOnMouseDragged((MouseEvent event) ->
            {
                double moveX = event.getScreenX() - lastMouse.getX();
                double moveY = event.getScreenY() - lastMouse.getY();
                ownerXAnchorProperty.set(ownerXAnchorProperty.doubleValue()+moveX);
                ownerYAnchorProperty.set(ownerYAnchorProperty.doubleValue()+moveY);
                lastMouse = new Point2D(event.getScreenX(), event.getScreenY());
                anchorToOwner();
            });
            getContent().add(bgPane);        

        }

        @Override
        protected void show()
        {
            super.show();
            anchorToOwner();
        }

        public void setContent(Node content)
        {
            this.content = content;
            bgPane.setCenter(content);
        }

        public void setTitle(String title)
        {
            titleLabel.setText(title);
        }

        // Repositions the MoveablePopup so that it's relationship to
        // the owning Node's location is maintained:
        public final void anchorToOwner()
        {
            if (getOwnerNode() != null)
            {
                Point2D screenPoint = getOwnerNode().localToScreen(0, 0);
                setX(screenPoint.getX() + ownerXAnchorProperty.doubleValue());
                setY(screenPoint.getY() + ownerYAnchorProperty.doubleValue());
            }
        }

    }   
}

Upvotes: 2

Views: 1518

Answers (2)

Siloft
Siloft

Reputation: 126

Create a new stage which will be the 'popup':

Stage childStage = new Stage();

childStage.initModality(Modality.WINDOW_MODAL);
childStage.setTitle("Title of popup");

childStage.setScene(YOUR_SCENE);
childStage.sizeToScene();
childStage.setResizable(false);

childStage.show();

Upvotes: 0

AlmasB
AlmasB

Reputation: 3407

As you have found out, everything that happens within the stage, stays within the stage (excuse the pun). So if you need some content shown outside of the stage, we can rule out everything except Stage itself. You can create a new stage and assign owner your initial stage. Stage, being a representation of a native window, will steal focus on click. However, it is possible to request focus back by the initial stage. That should tick your requirement boxes.

I am not sure of the use case of what you are trying to implement, but JFXtras provides some extensions to existing JavaFX API, including notifications and MDI windows. See if this is of any help to you.

Upvotes: -1

Related Questions