Is it possible to have a transparent utility stage

2019-01-12 04:42发布

问题:

This question already has an answer here:

  • A javaFX Stage could be both StageStyle.UTILITY and StageStyle.TRANSPARENT? 5 answers

I know that you can set a stage to have a utility style "Stage.InitStyle(StageStyle.UTILITY);" and you can set it to have a transparent style "Stage.InitStyle(StageStyle.TRANSPARENT);" but can you have both in the same stage? I am tiring to make it so that the stage does not show as a window down in the start menu and I would like the stage to be invisible so that you can only see the scene.

回答1:

You can always do it the old way using Swing where that feature was available. And Swing allows you to embed JavaFX. Of course it would be preferred to have a clean mechanism without Swing, but afaik it doesn't exist (yet).

Example:

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.Background;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import java.awt.geom.GeneralPath;

public class Widget extends JFrame {

    class DragContext { 
        double x;
        double y; 
    } 

    public Widget() {

        // decoration
        setType(Type.UTILITY);
        setUndecorated(true);

        setSize(200, 200);

        toBack();

        // position
        // setLocation(100, 100);
        setLocationRelativeTo(null); // centers on screen

        // frame operations
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // frame shape (a star)
        double points[][] = { { 0, 85 }, { 75, 75 }, { 100, 10 }, { 125, 75 }, { 200, 85 }, { 150, 125 }, { 160, 190 }, { 100, 150 }, { 40, 190 }, { 50, 125 }, { 0, 85 } };
        GeneralPath star = new GeneralPath();
        star.moveTo(points[0][0], points[0][1]);
        for (int k = 1; k < points.length; k++)
            star.lineTo(points[k][0], points[k][2]);
        star.closePath();

        setShape(star);

        // embed fx into swing
        JFXPanel fxPanel = new JFXPanel();

        Widget.this.getContentPane().add(fxPanel);

        Platform.runLater(new Runnable() {
            @Override
            public void run() {

                // set scene in JFXPanel
                fxPanel.setScene( createFxScene());

                // show frame
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {

                        Widget.this.setVisible(true);

                        // send it to the desktop, behind all other existing windows
                        // Widget.this.toBack();
                        // Widget.this.repaint();
                    }
                });
            }
        });

    }

    private Scene createFxScene() {

        StackPane rootPane = new StackPane();
        rootPane.setBackground(Background.EMPTY);

        // add some node
        Label label = new Label("Bright & Shiny");
        label.setTextFill(Color.RED);

        rootPane.getChildren().add(label);

        // create scene
        Scene scene = new Scene(rootPane);

        // gradient fill
        RadialGradient radialGradient = new RadialGradient( 270, 0.8, 0.5, 0.5, 0.7, true, CycleMethod.NO_CYCLE, new Stop( .5f, Color.YELLOW), new Stop( .7f, Color.ORANGE), new Stop( .9f, Color.ORANGERED));
        scene.setFill(radialGradient);

        // context menu with close button
        ContextMenu contextMenu = new ContextMenu();

        MenuItem closeMenuItem = new MenuItem("Close");
        closeMenuItem.setOnAction(actionEvent -> System.exit(0));

        contextMenu.getItems().add(closeMenuItem);

        // set context menu for scene
        scene.setOnMousePressed(mouseEvent -> {
            if (mouseEvent.isSecondaryButtonDown()) {
                contextMenu.show(rootPane, mouseEvent.getScreenX(), mouseEvent.getScreenY());
            }
        });

        // allow the frame to be dragged around
        final DragContext dragDelta = new DragContext();

        rootPane.setOnMousePressed(mouseEvent -> {

            dragDelta.x = Widget.this.getLocation().getX() - mouseEvent.getScreenX();
            dragDelta.y = Widget.this.getLocation().getY() - mouseEvent.getScreenY();

        });

        rootPane.setOnMouseDragged(mouseEvent -> Widget.this.setLocation((int) (mouseEvent.getScreenX() + dragDelta.x), (int) (mouseEvent.getScreenY() + dragDelta.y)));

        return scene;
    }

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

}

Here's a screenshot of the widget that's not showing up in the task bar. Drag it with the left mouse button. Right mouse button offers a context menu with a close button.

The code above uses the swing frame's shape. The code below uses the javafx control's shape.

Here's a version in which only the control is visible. I use a reflecting label control.

If you want to send the control directly to the desktop, you need to activate the toBack() invocation. You can zoom the control with the mouse wheel. The maximum zoom size is limited to the size of the jframe.

All you need to do for your custom control is implement the code in createFxControl()

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.effect.Reflection;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class LabelWidget extends JFrame {

    class DragContext { 
        double x;
        double y; 
    } 

    public LabelWidget() {

        // decoration
        setType(Type.UTILITY);
        setUndecorated(true);

        // make frame transparent, we only want the control to be visible
        setBackground(new java.awt.Color(0,0,0,0));

        setSize(400, 400);

        // position
        // setLocation(100, 100);
        setLocationRelativeTo(null); // centers on screen

        // frame operations
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // embed fx into swing
        JFXPanel fxPanel = new JFXPanel();

        LabelWidget.this.getContentPane().add(fxPanel);

        Platform.runLater(new Runnable() {
            @Override
            public void run() {

                // set scene in JFXPanel
                fxPanel.setScene( createFxScene());

                // show frame
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {

                        LabelWidget.this.setVisible(true);

                        // send it to the desktop, behind all other existing windows
                        // ClockWidget.this.toBack();
                        // ClockWidget.this.repaint();
                    }
                });
            }
        });

    }

    private Scene createFxScene() {

        StackPane rootPane = new StackPane();

        //  make pane transparent, we only want the control to be visible
        rootPane.setBackground(Background.EMPTY);

        // add control
        Control control = createFxControl();
        rootPane.getChildren().add( control);

        // create scene
        Scene scene = new Scene(rootPane);

        // make scene transparent, we only want the control to be visible
        scene.setFill( Color.TRANSPARENT);

        // context menu with close button
        ContextMenu contextMenu = new ContextMenu();

        MenuItem closeMenuItem = new MenuItem("Close");
        closeMenuItem.setOnAction(actionEvent -> System.exit(0));

        contextMenu.getItems().add(closeMenuItem);

        control.setContextMenu(contextMenu);

        // allow the frame to be dragged around
        makeDraggable( control);

        // allow zooming
        makeZoomable( control);

        return scene;
    }
    /**
     * Create the JavaFX control of which we use the shape.
     * @return
     */
    private Control createFxControl() {

        Label label = new Label( "I'm a Label");
        label.setFont(new Font("Tahoma", 24));
        label.setEffect(new Reflection());

        return label;
    }



    /**
     * Allow dragging of the stage / control on the desktop
     * @param control
     * @param stage
     */
    public void makeDraggable( Control control) {

        final DragContext dragDelta = new DragContext();

        control.setOnMousePressed(mouseEvent -> {

            dragDelta.x = LabelWidget.this.getLocation().getX() - mouseEvent.getScreenX();
            dragDelta.y = LabelWidget.this.getLocation().getY() - mouseEvent.getScreenY();

        });

        control.setOnMouseDragged(mouseEvent -> LabelWidget.this.setLocation((int) (mouseEvent.getScreenX() + dragDelta.x), (int) (mouseEvent.getScreenY() + dragDelta.y)));

    }

    /**
     * Allow zooming
     * @param control
     */
    public void makeZoomable( Control control) {

        // note: in order to make it larger, we'd have to resize the stage/frame => we limit the size to 1.0 for now and allow only making the control smaller
        final double MAX_SCALE = 1.0;
        final double MIN_SCALE = 0.1;
        control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
            @Override
            public void handle(ScrollEvent event) {
                double delta = 1.2;
                double scale = control.getScaleX();
                if (event.getDeltaY() < 0) {
                    scale /= delta;
                } else {
                    scale *= delta;
                }
                scale = clamp(scale, MIN_SCALE, MAX_SCALE);
                control.setScaleX(scale);
                control.setScaleY(scale);
                event.consume();
            }
        });

    }

    /**
     * Limit bounds of value
     * @param value
     * @param min
     * @param max
     * @return
     */
    public static double clamp( double value, double min, double max) {
        if( Double.compare(value, min) < 0)
            return min;
        if( Double.compare(value, max) > 0)
            return max;
        return value;
    }

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

}

Or if you have the awesome Enzo library from https://github.com/HanSolo/Enzo at hand, you can use this code:

private Control createFxControl() {

    // create a clock using the enzo library from https://github.com/HanSolo/Enzo
    Clock clock = ClockBuilder.create()
            // .prefSize(400, 400)
            .design(Clock.Design.DB)
            .running(true)
            .text("Berlin")
            .autoNightMode(true)
            .build();


    return clock;
}

to create this:



回答2:

Did find a simple way to do it: Create a utility window and make it completely transparent so you cannot see or interact with it. Then create the wanted window and init the owner to the utility window - this will cause it to not appear in the task bar.