用户释放鼠标左键

时间:2017-05-24 13:28:01

标签: java javafx

我已经足够google了,但仍然可以找到解决方案,只有当用户释放鼠标左键时才能获得单个调整大小事件。例如,来自here

的以下解决方案
stage.titleProperty().bind(
        scene.widthProperty().asString().
        concat(" : ").
        concat(scene.heightProperty().asString()));

当用户点击鼠标左键并开始调整舞台大小时,我会在调整大小时获得很多事件(使用属性侦听器)。但是,我想只获得一个事件 - 当用户完成调整大小并释放鼠标左键时。

另一个解决方案是here此解决方案显着减少了事件的数量,但仍然不允许只获得一个。

如何在用户释放鼠标按钮后只获得一个resize事件?

2 个答案:

答案 0 :(得分:1)

据我所知,调整阶段大小的鼠标事件处理程序是本机管理的,因此无法纯粹在JavaFX中访问 - 以您描述的方式执行此操作需要编写本机库并挂钩它们

如果你正在做一些繁重的计算(或其他需要很长时间的工作)以响应舞台大小的变化,你最好的选择可能就是编写一次只能处理一个变化的代码,而且只是在可能的情况下处理最后的已知更改。

这方面的一个例子是:

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class StageResizeThrottling extends Application {

    private Random rng = new Random();

    @Override
    public void start(Stage primaryStage) {

        BlockingQueue<Point2D> dimensionChangeQueue = new ArrayBlockingQueue<>(1);
        ChangeListener<Number> dimensionChangeListener = (obs, oldValue, newValue) -> {
            dimensionChangeQueue.clear();
            dimensionChangeQueue.add(new Point2D(primaryStage.getWidth(), primaryStage.getHeight()));
        };
        primaryStage.widthProperty().addListener(dimensionChangeListener);
        primaryStage.heightProperty().addListener(dimensionChangeListener);

        Thread processDimensionChangeThread = new Thread(() -> {
            try {
                while (true) {
                    System.out.println("Waiting for change in size");
                    Point2D size = dimensionChangeQueue.take();
                    System.out.printf("Detected change in size to [%.1f, %.1f]: processing%n", size.getX(), size.getY());
                    process(size, primaryStage);
                    System.out.println("Done processing");
                }
            } catch (InterruptedException letThreadExit) { }
        });
        processDimensionChangeThread.setDaemon(true);
        processDimensionChangeThread.start();

        Scene scene = new Scene(new StackPane(), 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void process(Point2D stageDimension, Stage stage) throws InterruptedException {
        // simulate slow process:
        Thread.sleep(500 + rng.nextInt(1000));
        final String title = String.format("Width: %.0f Height: %.0f", stageDimension.getX(), stageDimension.getY());
        Platform.runLater(() -> stage.setTitle(title));
    }

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

请注意,这将始终立即处理第一个更改,然后在每个先前处理的更改完成处理时处理最新更改。如果没有进一步的更改,它将等待一个确实发生,然后立即处理。如果您愿意,可以将此与您链接的基于计时器的技术结合起来,以便合并监听器中的更改,这通常会删除处理的第一个更改(这通常是多余的,因为它几乎总是跟随后续更改) 。以下更改将等待,直到300ms没有发生调整,然后将其提交到队列进行处理(线程仍然以相同的方式运行 - 它将处理最新的更改,并且当处理完成时,等待另一个): / p>

    BlockingQueue<Point2D> dimensionChangeQueue = new ArrayBlockingQueue<>(1);

    PauseTransition coalesceChanges = new PauseTransition(Duration.millis(300));
    coalesceChanges.setOnFinished(e -> {
        dimensionChangeQueue.clear();
        dimensionChangeQueue.add(new Point2D(primaryStage.getWidth(), primaryStage.getHeight()));           
    });

    ChangeListener<Number> dimensionChangeListener = (obs, oldValue, newValue) -> 
        coalesceChanges.playFromStart();

    primaryStage.widthProperty().addListener(dimensionChangeListener);
    primaryStage.heightProperty().addListener(dimensionChangeListener);

这里有一些调整,这是处理变化的延迟和过度渴望之间的权衡。您可能希望暂停转换持续时间比屏幕大小更改的平均处理时间短,但不会缩短一个数量级。

代码保证一次只能处理一个以上的更改,并且如果不再发生更改,最终将会处理最新的更改。这可能与您在不访问本机用户事件的情况下一样好。 (它还会处理舞台大小的程序化更改,鼠标处理程序无法处理。)

答案 1 :(得分:0)

我试图创建一个例子来实现你想要的东西,我最终得到了它,它并不完美但是当我测试它时,看起来它可能有所帮助:

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;

public class OneEventJavaFX extends Application{
    double originalWidth = 400;  // the initial width of Scene when the program starts
    double originalHeight = 400; // the initial height of Scene when the program starts

    // boolean property to be observed in order to know the completion of stage resize
    BooleanProperty completedProperty = new SimpleBooleanProperty(false); 
    Timeline timeline; 

  @Override
  public void start(Stage stage) throws Exception {

    Pane root = new Pane(); // simple root as example just for test purpose
    Scene scene = new Scene(root, 400,400);
    stage.setScene(scene);
    stage.setTitle("OneEventJavaFX");
    stage.show();


    // because I could not find a way to implement MouseEvent.MOUSE_RELEASED
    // on the stage to notify the completion on resizing, I had to use a TimeLine 
    // the duration should consider the time the user usually take to finish every resize 
    // duration is tricky, Very Slow Resizing V.S Very Fast Resizing!

    timeline = new Timeline(new KeyFrame(Duration.seconds(1), e ->{
                        System.out.println("Resizing Should Be Completed By Now!");
                        originalWidth = scene.getWidth(); // record the new scene size
                        originalHeight = scene.getHeight();
                        completedProperty.setValue(false);
                        }));

    // change listener, to be added to and removed from the scene
    ChangeListener<Number> changeListener= (observable, oldValue, newValue) ->{
        System.out.println("I am Detecting an Event!"); // test

        // once the size changed
        if(originalWidth-scene.getWidth()>1 || scene.getWidth()-originalWidth>1 ||
            originalHeight-scene.getHeight()>1 || scene.getHeight()-originalHeight>1){

            completedProperty.set(true); // notify that completion should be considered
            System.out.println("I Stopped! No More Events!");
            timeline.play(); // and start counting the time 
        }};


     // add the change listener when the program starts up
     scene.widthProperty().addListener(changeListener); 
     scene.heightProperty().addListener(changeListener);
     System.out.println("ChangeListener Added At Startup!");


     // now listen to the change of the boolean property value
     // instead of the size changes, it should NOT take a lot of work
     // then accordingly add and remove change listener!
     completedProperty.addListener(new ChangeListener<Boolean>() {

       @Override
       public void changed(ObservableValue<? extends Boolean> observable, 
                                  Boolean notComplete, Boolean complete) {
             if (complete) {
                 scene.widthProperty().removeListener(changeListener);
                 scene.heightProperty().removeListener(changeListener);
                 System.out.println("ChangeListener Removed!");
             }
             else{
                 scene.widthProperty().addListener(changeListener);
                 scene.heightProperty().addListener(changeListener);
                 System.out.println("ChangeListener Added Back!");
             }
         }
     });
 }

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

}

调整大小时进行测试

ChangeListener Added At Startup!
I am Detecting an Event!
I am Detecting an Event!
ChangeListener Removed!
I Stopped! No More Events!
Resizing Should Be Completed By Now!
ChangeListener Added Back!

<强>更新

我一直在努力解决这个问题,我相信这种方法可以达到你想要的效果。

这个想法如下:

  1. 创建 UNDECORATED 舞台并将其设为可调整大小
  2. 创建标题栏并将其添加到舞台
  3. 现在可以在舞台边框检测 (因为基本上它发生在场景)。
  4. 舞台宽度高度创建双重属性,然后添加更改侦听器< / strong>倾听变化。
  5. 舞台宽度和舞台的变化只有当拖动的开始以及释放鼠标时,高度才会记录
  6. 评论中的解释。
  7. 可以找到整个解决方案here作为存档文件(为什么?因为我试图将其完全发布在此处,但是Body Body是 30000 字符!)。
  8. OneEventStage类:

    import javafx.application.Platform;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.concurrent.Task;
    import javafx.scene.Scene;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Screen;
    import javafx.stage.Stage;
    import javafx.stage.StageStyle;
    
    
    /**
     * This class customize a given Stage to record the changes 
     * of its size only when user starts and finishes resizing (recording one event)
     * @author Yahya Almardeny
     * @version 28/05/2017
     */
    public class OneEventStage{
        private double originalWidth;  // the initial width of Scene when the program starts
        private double originalHeight; // the initial height of Scene when the program starts
        private TitleBar titleBar; // can be customized by the setter method (by default I made it for Windows 10 style)
        private boolean started, alreadyFullScreen;
        private DoubleProperty widthChange, heightChange; // record the changes in size
        public Scene s;
        public BorderPane scene; // this will be considered as a Scene when used in the program
    
        public OneEventStage(Stage stage, double width, double height){
            originalWidth = width; originalHeight = height;
    
            widthChange = new SimpleDoubleProperty(originalWidth);
            heightChange = new SimpleDoubleProperty(originalHeight);
    
            started = false;
            titleBar = new TitleBar("");
    
            scene = new BorderPane();
            scene.setTop(titleBar.getTitleBar());
    
            s = new Scene(scene, originalWidth,originalHeight);
    
            stage.initStyle(StageStyle.UNDECORATED);
    
            stage.setScene(s);
    
            ResizeHelper.addResizeListener(stage);
    
            Task<Void> task = new Task<Void>(){
                @Override
                protected Void call() throws Exception {
                    Platform.runLater(new Runnable(){   
                        @Override
                        public void run() {
                            // change listener, to be added to and removed from the scene
                            ChangeListener<Number> changeListener= (observable, oldValue, newValue) ->{
                                if(isFullScreen()){
                                    widthChange.setValue(stage.getWidth());
                                    heightChange.setValue(stage.getHeight());
                                    alreadyFullScreen=true;
                                }
    
                                else if (alreadyFullScreen){ // coming from full screen mode
                                    widthChange.setValue(Screen.getPrimary().getVisualBounds().getWidth());
                                    heightChange.setValue(Screen.getPrimary().getVisualBounds().getHeight());
                                    widthChange.setValue(originalWidth);
                                    heightChange.setValue(originalHeight);
                                    alreadyFullScreen = false;
                                }
                                else if(!alreadyFullScreen && !started){
                                    started = true; // to inform the detecting Mouse Release Event is required
                                }
                            };
    
                            s.setOnMouseReleased(e->{
                                if(started){ // if this happens particularly after changing the size/dragging
                                    originalWidth = stage.getWidth(); // record the new scene size 
                                    originalHeight = stage.getHeight();
                                    widthChange.setValue(originalWidth); // add it
                                    heightChange.setValue(originalHeight);
                                    started = false;
                                }
                            });
    
    
                            // add the change listener when the program starts up
                            s.widthProperty().addListener(changeListener); 
                            s.heightProperty().addListener(changeListener);
                        }
    
                    });
                    return null;
                }};
            new Thread(task).start();
    
        }
    
        /*
         * to detected if user clicked on maximize button or double click on the title bar
         */
        private boolean isFullScreen(){
            return this.s.getWindow().getWidth()==Screen.getPrimary().getVisualBounds().getWidth() &&
                   this.s.getWindow().getHeight()==Screen.getPrimary().getVisualBounds().getHeight();
        }
    
    
        public DoubleProperty getWidthChange() {
            return widthChange;
        }
    
        public DoubleProperty getHeightChange() {
            return heightChange;
        }
    
        public TitleBar getTitleBar() {
            return titleBar;
        }
    
    
        public void setTitleBar(TitleBar titleBar) {
            this.titleBar = titleBar;
        }
    
        public void setTitle(String title){
           titleBar.getTitle().setText(title);
        }
    
    
    }
    

    OneEventStageTest类:

    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.beans.property.DoubleProperty;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.StackPane;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    
    /**
     * Implementing an Example of OneEventStage to test it
     * @author Yahya Almardeny
     * @version 28/05/2017
     */
    public class OneEventStageTest extends Application{
    
        @Override
        public void start(Stage primaryStage) throws Exception {
    
            // create stage
            OneEventStage stage = new OneEventStage(primaryStage, 400,400); 
            stage.setTitle("One Event Stage");
    
            // simple containers and its components for testing purpose
            VBox container = new VBox();
            container.setAlignment(Pos.CENTER);
    
            HBox widthInfoContainer = new HBox();
            widthInfoContainer.setAlignment(Pos.CENTER);
            Label widthChangeL = new Label("Width Changes");
            TextField widthChangeV = new TextField();
            widthChangeV.setEditable(false);
            widthInfoContainer.getChildren().addAll(widthChangeL, widthChangeV);
            HBox.setMargin(widthChangeL, new Insets(10));
            HBox.setMargin(widthChangeV, new Insets(10));
    
            HBox heightInfoContainer = new HBox();
            heightInfoContainer.setAlignment(Pos.CENTER);
            Label heightChangeL = new Label("Height Changes");
            TextField heightChangeV = new TextField();
            heightChangeV.setEditable(false);
            heightInfoContainer.getChildren().addAll(heightChangeL, heightChangeV);
            HBox.setMargin(heightChangeL, new Insets(10));
            HBox.setMargin(heightChangeV, new Insets(10));
    
            container.getChildren().addAll(widthInfoContainer, heightInfoContainer);
    
            //////////////////////////////////////////////////////////////////////////
    
            DoubleProperty widthChange = stage.getWidthChange();
            DoubleProperty heightChange = stage.getHeightChange();
    
    
            // listen to the changes (Testing)
            widthChange.addListener((obs, old, newV)->{
                Platform.runLater(new Runnable(){
                    @Override
                    public void run() {
    
                        widthChangeV.setText("From(" + old.doubleValue() + ")  To(" + newV.doubleValue() + ")");
    
                    }
    
                });
    
            });
    
            heightChange.addListener((obs, old, newV)->{
                Platform.runLater(new Runnable(){
                    @Override
                    public void run() {
    
                        heightChangeV.setText("From(" + old.doubleValue() + ") To(" + newV.doubleValue() + ")");
    
                    }
    
                });
    
            });
    
            //////////////////////////////////////////////////////////////////////////////////////
    
            // represent a root but in fact it's inside the real root (BorderPane in the OneEventStage Class!).
            StackPane root = new StackPane(); 
            root.setAlignment(Pos.CENTER);
            root.getChildren().add(container);
            stage.scene.setCenter(root);
    
            primaryStage.show();    
        }
    
    
        public static void main(String[] args) {
            launch();
        }
    
    
    }
    

    TitleBar类:

    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Cursor;
    import javafx.scene.control.Label;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;
    import javafx.scene.layout.Background;
    import javafx.scene.layout.BackgroundFill;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Pane;
    import javafx.scene.layout.Priority;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.text.Font;
    import javafx.stage.Screen;
    import javafx.stage.Stage;
    
    /**
     * This class to create a default/customized Title Bar 
     * to be added to Undecorated Stage in JavaFX Application
     * @author Yahya Almardeny
     * @version 27/05/2017
     */
    public class TitleBar {
        private HBox titleBar;
        private ImageView icon;
        private StackPane close, minimize, maximize; // represent customized components for the title bar (by using the second constructor)
        private Image maximizeBefore, maximizeAfter; // for changing maximize icon when it's full screen
        private Label title;
        private double height, stageWidth, stageHeight, x,y, offsetX, offsetY;
        private double screenWidth = Screen.getPrimary().getVisualBounds().getWidth(), 
                       screenHeight = Screen.getPrimary().getVisualBounds().getHeight();
        private Color backgroundColor;
        private StackPane maximizeButton; // for default title bar
        private Label minimizeButton, closeButton; // for default title bar
        private Stage stage;
        private boolean intialized = false, fromMax = false;
    
        public static enum Components {ICON,TITLE,MINIMIZE,MAXIMIZE,CLOSE;}
    
        /**
         * the default constructor, appearance of Windows 10
         * @param title
         */
        public TitleBar(String title){
            titleBar = new HBox();
    
            icon =  new ImageView(new Image(TitleBar.class.getResourceAsStream("/icon/icon.png")));
            icon.setFitWidth(15); this.icon.setFitHeight(13); 
    
            closeButton = new Label("×");
            closeButton.setFont(Font.font("Times New Roman", 25));
            closeButton.setPrefWidth(46);
            closeButton.setAlignment(Pos.CENTER);
    
            minimizeButton = new Label("—");
            minimizeButton.setFont(Font.font(10));
            minimizeButton.setPrefWidth(46);
            minimizeButton.setPrefHeight(29);
    
            minimizeButton.setAlignment(Pos.CENTER);
            maximizeButton = maximiazeButton();
    
            this.title = new Label(title);
    
            final Pane space = new Pane();
            HBox.setHgrow(space,Priority.ALWAYS);
    
            titleBar.getChildren().addAll(this.icon, this.title,space,this.minimizeButton, this.maximizeButton, this.closeButton);
            titleBar.setAlignment(Pos.CENTER_RIGHT);
    
            HBox.setMargin(this.icon, new Insets(0,5,0,10)); // top,right, bottom, left
    
            initalize(); // private method to get the Stage for first time
            setDefaultControlsFunctionality(); // private method to add the default controls functionality
    
        }
    
    
        /**
         * This is constructor to create a custom title bar
         * @param icon
         * @param minimize
         * @param maximize
         * @param close
         * @param title
         */
        public TitleBar(Image icon, Image minimize, Image maximizeBefore, Image maximizeAfter, Image close, String title){
            titleBar = new HBox();
    
            this.icon =  new ImageView(icon);
            this.icon.setFitWidth(15); this.icon.setFitHeight(14);  // values can be changed via setters
    
            this.close = new StackPane();
            this.close.setPrefSize(25, 20);
            this.close.getChildren().add(new ImageView(close));
            ((ImageView) this.close.getChildren().get(0)).setFitWidth(20);
            ((ImageView) this.close.getChildren().get(0)).setFitHeight(20);
    
            this.minimize = new StackPane();
            this.minimize.setPrefSize(25, 20);
            this.minimize.getChildren().add(new ImageView(minimize));
            ((ImageView) this.minimize.getChildren().get(0)).setFitWidth(20);
            ((ImageView) this.minimize.getChildren().get(0)).setFitHeight(20);
    
            this.maximizeBefore = maximizeBefore;
            this.maximize = new StackPane();
            this.maximize.setPrefSize(25, 20);
            this.maximize.getChildren().add(new ImageView(maximizeBefore));
            ((ImageView) this.maximize.getChildren().get(0)).setFitWidth(20);
            ((ImageView) this.maximize.getChildren().get(0)).setFitHeight(20);
            this.maximizeAfter = maximizeAfter;
    
            this.title = new Label(title);
    
            final Pane space = new Pane();
            HBox.setHgrow(space,Priority.ALWAYS);
    
            titleBar.getChildren().addAll(this.icon, this.title,space,this.minimize, this.maximize, this.close);
            titleBar.setAlignment(Pos.CENTER_RIGHT);
    
            HBox.setMargin(this.icon, new Insets(0,5,0,10)); // top,right, bottom, left
            HBox.setMargin(this.close, new Insets(0,5,0,0));
    
            initalize();
            setCustomizedControlsFunctionality();
        }
    
    
    
        /**
         * create the default maximize button
         * @return container
         */
        private StackPane maximiazeButton(){
            StackPane container = new StackPane();
            Rectangle rect = new Rectangle(8,8);
            rect.setFill(Color.TRANSPARENT);
            rect.setStroke(Color.BLACK);
            container.setPrefWidth(46);
            container.getChildren().add(rect);  
    
            return container;
        }
    
    
    
        /**
         * To get the Stage of the application for one time only
         * as well as adding listener to iconifiedProperty()
         */
        private void initalize(){
           titleBar.setOnMouseEntered(e->{ // the entire block will be executed only once
              if(!intialized){
                 // get the stage and assign it to the Stage field
                 stage = ((Stage)titleBar.getScene().getWindow());
    
                 // add listener toiconifiedProperty()
                 stage.iconifiedProperty().addListener(ee->{
                     if(!stage.isIconified()){ 
                        stage.setMaximized(true);
                        if(fromMax){ // if already maximized
                           stage.setWidth(screenWidth);
                           stage.setHeight(screenHeight);
                           stage.setX(0);
                           stage.setY(0);
                        }
                        else{
                            stage.setWidth(stageWidth);
                            stage.setHeight(stageHeight);
                            stage.setX(x);
                            stage.setY(y);
                        }
                        try { // to remove the flash
                             Thread.sleep(10);
                        } catch (InterruptedException ex) { 
                             ex.printStackTrace();
                        }
                        stage.setOpacity(1.0);
                     }
                });
    
    
                intialized=true;
             }
           });  
        }
    
    
        /**
         * To add functionality to title bar controls
         * via event listeners
         */
        private void setDefaultControlsFunctionality(){
    
            // Double-Click on Title Bar
            titleBar.setOnMouseClicked(e->{
                if(e.getClickCount()==2){
                    maximizefunctonality();
                }       
            });
    
            //Maximize Control
            maximizeButton.setOnMouseEntered(e->{// highlight when hover    
                maximizeButton.setBackground(
                    new Background(new BackgroundFill(Color.LIGHTGRAY,null,null)));
                    ((Rectangle)maximizeButton.getChildren().get(0)).setFill(Color.LIGHTGRAY);
                    if(maximizeButton.getChildren().size()==2){
                       ((Rectangle)maximizeButton.getChildren().get(1)).setFill(Color.LIGHTGRAY);
                    }
    
            });
            maximizeButton.setOnMouseExited(e->{ // remove highlight 
                maximizeButton.setBackground(
                    new Background(new BackgroundFill(Color.TRANSPARENT,null,null)));
                   ((Rectangle)maximizeButton.getChildren().get(0)).setFill(Color.TRANSPARENT);
                   if(maximizeButton.getChildren().size()==2){
                      ((Rectangle)maximizeButton.getChildren().get(1)).setFill(Color.WHITE);
                    }
            });
            maximizeButton.setOnMouseClicked(e->{
                maximizefunctonality();
    
            });
    
            //Close Control
            closeButton.setOnMouseEntered(e->{
                closeButton.setBackground(
                    new Background(new BackgroundFill(Color.CRIMSON,null,null)));
                    closeButton.setTextFill(Color.WHITE);
            });
            closeButton.setOnMouseExited(e->{
                closeButton.setBackground(
                    new Background(new BackgroundFill(Color.TRANSPARENT,null,null)));
                    closeButton.setTextFill(Color.BLACK);
            });
    
            closeButton.setOnMouseClicked(e->{
                stage.close();  
            });
    
    
            //Minimize Control
            minimizeButton.setOnMouseEntered(e->{
                minimizeButton.setBackground(
                    new Background(new BackgroundFill(Color.LIGHTGRAY,null,null))); 
            });
            minimizeButton.setOnMouseExited(e->{
                minimizeButton.setBackground(
                    new Background(new BackgroundFill(Color.TRANSPARENT,null,null)));
            });
            minimizeButton.setOnMouseClicked(e->{
                if(!stage.isIconified()){ // if it's not minimized
                    if(fromMax){ // check if it's already full screen(maximized)
                        stage.setOpacity(0.0);
                        stage.setIconified(true); // minimize it
                    }
                    else{ // if it's not -> record the size and position
                        stageWidth = stage.getWidth();
                        stageHeight = stage.getHeight();
                        x = stage.getX();
                        y = stage.getY();
                        stage.setOpacity(0.0);
                        stage.setIconified(true); // minimize it
                    }
                }
            });
    
            // to make title bar movable
            titleBar.setOnMousePressed(e->{
                if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
                    offsetX = e.getScreenX() - stage.getX();
                    offsetY = e.getScreenY() - stage.getY();
                }
            });
            titleBar.setOnMouseDragged(e->{
                if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
                    stage.setX(e.getScreenX() - offsetX);
                    stage.setY(e.getScreenY() - offsetY);
                }
            });
    
        }
    
    
    
        private void maximizefunctonality(){
            Rectangle rect = (Rectangle) maximizeButton.getChildren().get(0);
            if(stage.getWidth()<screenWidth||stage.getHeight()<screenHeight){
                // get the previous size + position
                stageWidth = stage.getWidth();
                stageHeight = stage.getHeight();
                x = stage.getX();
                y = stage.getY();
                // maximize it
                stage.setWidth(screenWidth);
                stage.setHeight(screenHeight);
                stage.centerOnScreen();
    
                // change the maximize button appearance
                rect.setTranslateX(2);
                rect.setTranslateY(-2);
                Rectangle rect1 = new Rectangle(8,8);
                rect1.setFill(Color.WHITE);
                rect1.setStroke(Color.BLACK);
                maximizeButton.getChildren().add(rect1);
    
                fromMax = true;
            }
            else{ // if already maximized -> return to previous size + position
                stage.setWidth(stageWidth);
                stage.setHeight(stageHeight);
                stage.setX(x);
                stage.setY(y);  
                fromMax = false;
    
                // change the maximize button appearance
                rect.setTranslateX(0);
                rect.setTranslateY(0);
                maximizeButton.getChildren().remove(1);
            }
    
        }
    
    
    
        private void setCustomizedControlsFunctionality(){
    
            //Maximize Control
            maximize.setOnMouseClicked(e->{
                if(stage.getWidth()<screenWidth||stage.getHeight()<screenHeight){
                    // get the previous size + position
                    stageWidth = stage.getWidth();
                    stageHeight = stage.getHeight();
                    x = stage.getX();
                    y = stage.getY();
                    // maximize it
                    stage.setWidth(screenWidth);
                    stage.setHeight(screenHeight);
                    stage.centerOnScreen();
    
                    // change the maximize button appearance
                    ((ImageView) maximize.getChildren().get(0)).setImage(maximizeAfter);
    
                    fromMax = true;
                }
                else{ // if already maximized -> return to previous size + position
                    stage.setWidth(stageWidth);
                    stage.setHeight(stageHeight);
                    stage.setX(x);
                    stage.setY(y);  
                    fromMax = false;
    
                    // change the maximize button appearance
                    ((ImageView) maximize.getChildren().get(0)).setImage(maximizeBefore);
                }
    
            });
    
            close.setOnMouseClicked(e->{
                stage.close();  
            });
    
    
            //Minimize Control
            minimize.setOnMouseClicked(e->{
                if(!stage.isIconified()){ // if it's not minimized
                    if(fromMax){ // check if it's already full screen(maximized)
                        stage.setOpacity(0.0);
                        stage.setIconified(true); // minimize it
                    }
                    else{ // if it's not -> record the size and position
                        stageWidth = stage.getWidth();
                        stageHeight = stage.getHeight();
                        x = stage.getX();
                        y = stage.getY();
                        stage.setOpacity(0.0);
                        stage.setIconified(true); // minimize it
                    }
                }
            });
    
            // to make title bar movable
            titleBar.setOnMousePressed(e->{
                if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
                    offsetX = e.getScreenX() - stage.getX();
                    offsetY = e.getScreenY() - stage.getY();
                }
            });
            titleBar.setOnMouseDragged(e->{
                if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
                    stage.setX(e.getScreenX() - offsetX);
                    stage.setY(e.getScreenY() - offsetY);
                }
            });
    
        }
    
    
    
        /**
         * To change margins/insets to the Title Bar components
         * @param component
         * @param top
         * @param right
         * @param bottom
         * @param left
         */
        public void setInsets(Components component, double top, double right, double bottom, double left){
            switch(component){
            case TITLE:
                HBox.setMargin(title, new Insets(top, right, bottom ,left));
                break;
            case ICON:
                HBox.setMargin(icon, new Insets(top, right, bottom ,left));
                break;
            case CLOSE:
                HBox.setMargin(close, new Insets(top, right, bottom ,left));
                break;
            case MAXIMIZE:
                HBox.setMargin(maximize, new Insets(top, right, bottom ,left));
                break;
            case MINIMIZE:
                HBox.setMargin(minimize, new Insets(top, right, bottom ,left));
                break;
            }
        }
    
    
    
        public void setControlsSpace(Components component, double width, double height){
            switch(component){
    
            case CLOSE:
                close.setPrefSize(width, height);
                break;
            case MAXIMIZE:
                maximize.setPrefSize(width, height);
                break;
            case MINIMIZE:
                minimize.setPrefSize(width, height);
                break;
            case TITLE:
                //do nothing
                break;
            case ICON:
                // do nothing
                break;
            }
        }
    
    
        public void addHoverEffect(Components component, Color defaultColor, Color onHover, Cursor cursor){
    
        }
    
        //reset of the class
        {...}
    
    }
    

    ResizeHelper类:

    {....}
    

    <强>测试 enter image description here