允许用户调整未修饰的舞台的大小

我正在使用JavaFX制作屏幕录像机,并且屏幕录像机中必需的一个实用程序是让用户定义要记录的区域。

我设法做了一个未装饰的半透明Stage ,可以拖动它来定义区域并添加一个close按钮,让用户确认要录制的区域。

现在,如何通过拖动边缘来让用户调整舞台的大小?

SSCCE:

 package draggable; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.SceneBuilder; import javafx.scene.control.Button; import javafx.scene.control.ButtonBuilder; import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPaneBuilder; import javafx.scene.paint.Color; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; public class DraggableStage extends Application{ Button close; StackPane holder; Rectangle2D maxBounds; Scene theScene; double pressedX; double pressedY; double draggedX; double draggedY; @Override public void start(Stage stage) throws Exception { final Stage theStage = stage; // determine how big the screen is maxBounds = Screen.getPrimary().getVisualBounds(); //create the close button close = ButtonBuilder .create() .text("Close") .build(); //create the StackPane holder for the button holder = StackPaneBuilder .create() .alignment(Pos.CENTER) .children(close) .build(); // you cannot resize the screen beyond the max resolution of the screen holder.setMaxSize(maxBounds.getWidth(), maxBounds.getHeight()); //you cannot resize under half the width and height of the screen holder.setMinSize(maxBounds.getWidth() / 2,maxBounds.getHeight() / 2); //the scene where it all happens theScene = SceneBuilder .create() .root(holder) .width(maxBounds.getWidth() / 2) .height(maxBounds.getHeight() / 2) .build(); // add the button listeners close.setOnAction(new EventHandler(){ @Override public void handle(ActionEvent event) { theStage.close(); } }); // add the drag and press listener for the StackPane holder.setOnMousePressed(new EventHandler(){ @Override public void handle(MouseEvent e) { pressedX = e.getX(); pressedY = e.getY(); } }); holder.setOnMouseDragged(new EventHandler(){ @Override public void handle(MouseEvent e) { draggedX = e.getX(); draggedY = e.getY(); double differenceX = draggedX - pressedX; double differenceY = draggedY - pressedY; theStage.setX(theStage.getX() + differenceX); theStage.setY(theStage.getY() + differenceY); } }); //the mandatory mumbo jumbo theScene.setFill(Color.rgb(128, 128, 128, 0.5)); stage.initStyle(StageStyle.TRANSPARENT); stage.setScene(theScene); stage.sizeToScene(); stage.show(); } public static void main(String[] args) { Application.launch("draggable.DraggableStage"); } } 

图片:
在此处输入图像描述

我创建了一个ResizeHelper类,在这种情况下可以帮助你,用法:

 ResizeHelper.addResizeListener(yourStage); 

助手类:

 import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; //created by Alexander Berg public class ResizeHelper { public static void addResizeListener(Stage stage) { ResizeListener resizeListener = new ResizeListener(stage); stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener); ObservableList children = stage.getScene().getRoot().getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, resizeListener); } } public static void addListenerDeeply(Node node, EventHandler listener) { node.addEventHandler(MouseEvent.MOUSE_MOVED, listener); node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener); node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener); if (node instanceof Parent) { Parent parent = (Parent) node; ObservableList children = parent.getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, listener); } } } static class ResizeListener implements EventHandler { private Stage stage; private Cursor cursorEvent = Cursor.DEFAULT; private int border = 4; private double startX = 0; private double startY = 0; public ResizeListener(Stage stage) { this.stage = stage; } @Override public void handle(MouseEvent mouseEvent) { EventType mouseEventType = mouseEvent.getEventType(); Scene scene = stage.getScene(); double mouseEventX = mouseEvent.getSceneX(), mouseEventY = mouseEvent.getSceneY(), sceneWidth = scene.getWidth(), sceneHeight = scene.getHeight(); if (MouseEvent.MOUSE_MOVED.equals(mouseEventType) == true) { if (mouseEventX < border && mouseEventY < border) { cursorEvent = Cursor.NW_RESIZE; } else if (mouseEventX < border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SW_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY < border) { cursorEvent = Cursor.NE_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SE_RESIZE; } else if (mouseEventX < border) { cursorEvent = Cursor.W_RESIZE; } else if (mouseEventX > sceneWidth - border) { cursorEvent = Cursor.E_RESIZE; } else if (mouseEventY < border) { cursorEvent = Cursor.N_RESIZE; } else if (mouseEventY > sceneHeight - border) { cursorEvent = Cursor.S_RESIZE; } else { cursorEvent = Cursor.DEFAULT; } scene.setCursor(cursorEvent); } else if(MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)){ scene.setCursor(Cursor.DEFAULT); } else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType) == true) { startX = stage.getWidth() - mouseEventX; startY = stage.getHeight() - mouseEventY; } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) == true) { if (Cursor.DEFAULT.equals(cursorEvent) == false) { if (Cursor.W_RESIZE.equals(cursorEvent) == false && Cursor.E_RESIZE.equals(cursorEvent) == false) { double minHeight = stage.getMinHeight() > (border*2) ? stage.getMinHeight() : (border*2); if (Cursor.NW_RESIZE.equals(cursorEvent) == true || Cursor.N_RESIZE.equals(cursorEvent) == true || Cursor.NE_RESIZE.equals(cursorEvent) == true) { if (stage.getHeight() > minHeight || mouseEventY < 0) { stage.setHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight()); stage.setY(mouseEvent.getScreenY()); } } else { if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) { stage.setHeight(mouseEventY + startY); } } } if (Cursor.N_RESIZE.equals(cursorEvent) == false && Cursor.S_RESIZE.equals(cursorEvent) == false) { double minWidth = stage.getMinWidth() > (border*2) ? stage.getMinWidth() : (border*2); if (Cursor.NW_RESIZE.equals(cursorEvent) == true || Cursor.W_RESIZE.equals(cursorEvent) == true || Cursor.SW_RESIZE.equals(cursorEvent) == true) { if (stage.getWidth() > minWidth || mouseEventX < 0) { stage.setWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth()); stage.setX(mouseEvent.getScreenX()); } } else { if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) { stage.setWidth(mouseEventX + startX); } } } } } } } } 

这是由@ Alexander.Berg发布的ResizeHelper的更新版本,它支持最小和最大舞台大小。

 package sem.helper; import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; /** * Util class to handle window resizing when a stage style set to StageStyle.UNDECORATED. * Created on 8/15/17. * * @author Evgenii Kanivets */ public class ResizeHelper { public static void addResizeListener(Stage stage) { addResizeListener(stage, 0, 0, Double.MAX_VALUE, Double.MAX_VALUE); } public static void addResizeListener(Stage stage, double minWidth, double minHeight, double maxWidth, double maxHeight) { ResizeListener resizeListener = new ResizeListener(stage); stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener); resizeListener.setMinWidth(minWidth); resizeListener.setMinHeight(minHeight); resizeListener.setMaxWidth(maxWidth); resizeListener.setMaxHeight(maxHeight); ObservableList children = stage.getScene().getRoot().getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, resizeListener); } } private static void addListenerDeeply(Node node, EventHandler listener) { node.addEventHandler(MouseEvent.MOUSE_MOVED, listener); node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener); node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener); if (node instanceof Parent) { Parent parent = (Parent) node; ObservableList children = parent.getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, listener); } } } static class ResizeListener implements EventHandler { private Stage stage; private Cursor cursorEvent = Cursor.DEFAULT; private int border = 4; private double startX = 0; private double startY = 0; // Max and min sizes for controlled stage private double minWidth; private double maxWidth; private double minHeight; private double maxHeight; public ResizeListener(Stage stage) { this.stage = stage; } public void setMinWidth(double minWidth) { this.minWidth = minWidth; } public void setMaxWidth(double maxWidth) { this.maxWidth = maxWidth; } public void setMinHeight(double minHeight) { this.minHeight = minHeight; } public void setMaxHeight(double maxHeight) { this.maxHeight = maxHeight; } @Override public void handle(MouseEvent mouseEvent) { EventType mouseEventType = mouseEvent.getEventType(); Scene scene = stage.getScene(); double mouseEventX = mouseEvent.getSceneX(), mouseEventY = mouseEvent.getSceneY(), sceneWidth = scene.getWidth(), sceneHeight = scene.getHeight(); if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) { if (mouseEventX < border && mouseEventY < border) { cursorEvent = Cursor.NW_RESIZE; } else if (mouseEventX < border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SW_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY < border) { cursorEvent = Cursor.NE_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SE_RESIZE; } else if (mouseEventX < border) { cursorEvent = Cursor.W_RESIZE; } else if (mouseEventX > sceneWidth - border) { cursorEvent = Cursor.E_RESIZE; } else if (mouseEventY < border) { cursorEvent = Cursor.N_RESIZE; } else if (mouseEventY > sceneHeight - border) { cursorEvent = Cursor.S_RESIZE; } else { cursorEvent = Cursor.DEFAULT; } scene.setCursor(cursorEvent); } else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) { scene.setCursor(Cursor.DEFAULT); } else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) { startX = stage.getWidth() - mouseEventX; startY = stage.getHeight() - mouseEventY; } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) { if (!Cursor.DEFAULT.equals(cursorEvent)) { if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) { double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2); if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent) || Cursor.NE_RESIZE.equals(cursorEvent)) { if (stage.getHeight() > minHeight || mouseEventY < 0) { setStageHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight()); stage.setY(mouseEvent.getScreenY()); } } else { if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) { setStageHeight(mouseEventY + startY); } } } if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) { double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2); if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent) || Cursor.SW_RESIZE.equals(cursorEvent)) { if (stage.getWidth() > minWidth || mouseEventX < 0) { setStageWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth()); stage.setX(mouseEvent.getScreenX()); } } else { if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) { setStageWidth(mouseEventX + startX); } } } } } } private void setStageWidth(double width) { width = Math.min(width, maxWidth); width = Math.max(width, minWidth); stage.setWidth(width); } private void setStageHeight(double height) { height = Math.min(height, maxHeight); height = Math.max(height, minHeight); stage.setHeight(height); } } } 

在我尝试“解决”它之前,我已经研究了一些讨论它的线程,但最终没有问题。

我在右下角创建了一个按钮,给Button一个OnDraggedMethod并简单地使用了mouseevent并在MousePosition上设置了Height / Width – 阶段X / Y位置。

  double newX = event.getScreenX() - stage.getX() + 13; double newY = event.getScreenY() - stage.getY() + 10; if (newX % 5 == 0 || newY % 5 == 0) { if (newX > 550) { stage.setWidth(newX); } else { stage.setWidth(550); } if (newY > 200) { stage.setHeight(newY); } else { stage.setHeight(200); } } 

我还设置了最小宽度和高度。 并且因为我认为滞后调整动画是令人讨厌的我用if包围声明并且每隔5个像素调整舞台大小。

看起来好多了!

PS:+13和+10仅适用于MousePosition。 其他的鼠标是在角落而不是按钮^^。

我能说什么……我是一个慢学习者。 我花了一些时间来仔细阅读所有代码并理解它。 它并不难……只是发生了很多事情。 我决定进行一些更改以使用绝对鼠标位置而不是相对鼠标位置,我还改变了整体类使用静态成员和诸如此类的东西。 这就是我想出来的,对自己来说效果很好。 对于那些寻找类似解决方案的人,请享受我丰富的评论……使用此类的此类版本非常简单:

ResizeListener listener = new ResizeListener(stage);

 import draco_logger.LeafLogger; import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.stage.Screen; import javafx.stage.Stage; //originally created by Alexander Berg //https://stackoverflow.com/questions/19455059/allow-user-to-resize-an-undecorated-stage // //modified by Joseph Adomatis //extracted ResizeListener class and made public, added arg for CWinMaxButton for detecting Maximized Window, using my own logger //changed MouseDragged routines and private variables to make use of screen absolute values instead of relative values //MouseDragged also updated to respect Min/Max sizes public class ResizeListener implements EventHandler { public ResizeListener(Stage stage) { LeafLogger log = new LeafLogger("ResizeListener", "Constructor(Stage)"); this.stage = stage; this._max = null; isPressed = false; cursorEvent = Cursor.DEFAULT; border = 3; stageStartH = 0; stageStartW = 0; stageStartX = 0; stageStartY = 0; this.addResizeListener(); log.Wither(); } public void AddMaxButton(CWinMaxButton max){ this._max = max; } private void addResizeListener() { LeafLogger log = new LeafLogger("ResizeListener", "addResizeListener"); this.stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, this); this.stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, this); this.stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, this); this.stage.getScene().addEventHandler(MouseEvent.MOUSE_ENTERED, this); this.stage.getScene().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, this); this.stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, this); this.stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, this); ObservableList children = this.stage.getScene().getRoot().getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child); } log.Wither(); } private void addListenerDeeply(Node node) { LeafLogger log = new LeafLogger("ResizeListener", "addListenerDeeply"); node.addEventHandler(MouseEvent.MOUSE_MOVED, this); node.addEventHandler(MouseEvent.MOUSE_PRESSED, this); node.addEventHandler(MouseEvent.MOUSE_DRAGGED, this); node.addEventHandler(MouseEvent.MOUSE_ENTERED, this); node.addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, this); node.addEventHandler(MouseEvent.MOUSE_EXITED, this); node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, this); if (node instanceof Parent) { Parent parent = (Parent) node; ObservableList children = parent.getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child); } } log.Wither(); } @Override public void handle(MouseEvent mouseEvent) { LeafLogger log = new LeafLogger("ResizeListener","handle"); // Check if we registered a maximize button if(this._max != null){ // Check with the maximize button to see if window is currently maximized if(this._max.GetMaximized()){ // We do not resize Maximized windows log.Wither(); return; } } EventType mouseEventType = mouseEvent.getEventType(); Scene scene = stage.getScene(); // set minHeight vars in such a way as to ensure that there is always a border over which we can continue to resize again // if stage MinHeight were 0 and you resized to 0, the draggable zone is gone and you cannot resize anymore // so regardless of app preference, we artificially set min sizes to leave all borders double minHeight = stage.getMinHeight() > (border*2) ? stage.getMinHeight() : (border*2); double minWidth = stage.getMinWidth() > (border*2) ? stage.getMinWidth() : (border*2); double maxHeight = stage.getMaxHeight(); double maxWidth = stage.getMaxWidth(); // capture the position of the mouse cursor relative to the stage anchor point at the time of the event double mouseEventX = mouseEvent.getSceneX(); double mouseEventY = mouseEvent.getSceneY(); // capture the current scene Height and Width double sceneHeight = scene.getHeight(); double sceneWidth = scene.getWidth(); // capture the screen max visual Height and Width double screenHeight = Screen.getPrimary().getVisualBounds().getHeight(); double screenWidth = Screen.getPrimary().getVisualBounds().getWidth(); // if MOUSE_MOVED and its new position is over one of the stage borders, we want to update the cursor to be one of the resize variety if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) { if (mouseEventX < border && mouseEventY < border) { cursorEvent = Cursor.NW_RESIZE; } else if (mouseEventX < border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SW_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY < border) { cursorEvent = Cursor.NE_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SE_RESIZE; } else if (mouseEventX < border) { cursorEvent = Cursor.W_RESIZE; } else if (mouseEventX > sceneWidth - border) { cursorEvent = Cursor.E_RESIZE; } else if (mouseEventY < border) { cursorEvent = Cursor.N_RESIZE; } else if (mouseEventY > sceneHeight - border) { cursorEvent = Cursor.S_RESIZE; } else { cursorEvent = Cursor.DEFAULT; } scene.setCursor(cursorEvent); // if MOUSE_EXITED the stage screen area and we'd pressed but did not release the mouse button, then we want to maintain our current cursor // otherwise, since the mouse is outside our stage, we return it to the default cursor } else if(MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)){ if(!isPressed){ scene.setCursor(Cursor.DEFAULT); } // similarly, if MOUSE ENTERED the stage screen area and we'd pressed but did not release the mouse button, then we want to maintain the current cursor // otherwise, since the mouse is coming back to us, we dont want to keep whatever other cursor may have been set by other windows so we return to default } else if(MouseEvent.MOUSE_ENTERED.equals(mouseEventType) || MouseEvent.MOUSE_ENTERED_TARGET.equals(mouseEventType)){ if(!isPressed){ scene.setCursor(Cursor.DEFAULT); } // if MOUSE_PRESSED we might need to keep track that we pressed it and are initiating a potential drag/resize event } else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) { // right now we dont care if mouse was pressed, but we might boolean iCare = false; // check the cursor type, if it is a resize cursor then mouse is over a border and we DO care that we pressed the mouse if(Cursor.N_RESIZE.equals(cursorEvent) || Cursor.S_RESIZE.equals(cursorEvent) || Cursor.E_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent)){ iCare = true; } else if(Cursor.NE_RESIZE.equals(cursorEvent) || Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.SE_RESIZE.equals(cursorEvent) || Cursor.SW_RESIZE.equals(cursorEvent)){ iCare = true; } // if we care that we pressed the mouse, we need to capture the initial data that will be used by our drag event handler to actually resize the window if(iCare){ stageStartH = stage.getHeight(); stageStartW = stage.getWidth(); stageStartX = stage.getX(); stageStartY = stage.getY(); mouseStartX = mouseEvent.getScreenX(); mouseStartY = mouseEvent.getScreenY(); isPressed = true; } // if MOUSE_RELEASED, we don't care what the mouse does anymore so release our flag } else if(MouseEvent.MOUSE_RELEASED.equals(mouseEventType)){ isPressed = false; // if MOUSE_DRAGGED, this handler might have something to do } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) { // if the cursor is still default, then this handler doesnt care about the drag event so ignore everything else // this handler only cares if the cursor is of the resize variety if(Cursor.DEFAULT.equals(cursorEvent)){ return; } // Check if there is a vertical component to the window resize // The only time there isn't a vertical component is if the mouse is strictly on the west or east side of the stage if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) { // There is a vertical component. // If we are resizing the north side however, we will be resetting both the Y coordinate of the stage anchor as well as the stage height if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent) || Cursor.NE_RESIZE.equals(cursorEvent)) { double mouseDifY = mouseStartY - stageStartY; // we are moving the north side // figure out where the south side of the stage is double finalY = stageStartY + stage.getHeight(); // we are free to move the north side until it reaches the point where the distance to the south side is greater than maxHeight // OR, we run into the top of the screen double minStageY = (finalY - maxHeight) > 0 ? (finalY - maxHeight): 0; double minMouseY = minStageY + mouseDifY; // we are free to move the north side until it reaches the point where the distance to the south side is less than minHeight double maxStageY = finalY - minHeight; double maxMouseY = maxStageY + mouseDifY; // capture the absolute position of the mouse at the time of the event double curMouseY = mouseEvent.getScreenY(); if(curMouseY < minMouseY){ stage.setY(minStageY); // Our mouse passed the value at which we would breach max height // We dont want the curMouseY to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position curMouseY = minMouseY; } else if(curMouseY > maxMouseY){ stage.setY(maxStageY); // Our mouse passed the value at which we would breach min height // We dont want the curMouseY to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position curMouseY = maxMouseY; } else { stage.setY(curMouseY - mouseDifY); } double newY = stage.getY(); double newHeight = finalY - newY; stage.setHeight(newHeight); // Our stage and mouse start variables were set via the mouse pressed event handle // If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released. // By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle // While dragging mouse, you aren't releasing and re-pressing it to update the variables.... stageStartY = stage.getY(); stageStartH = stage.getHeight(); mouseStartY = curMouseY; } else { // Else, we are resizing the south side, and the Y coordinate remains fixed. We only change the stage height // figure out where the current south side actually is double curFinalY = stageStartY + stageStartH; double mouseDifY = mouseStartY - curFinalY; // we are free to move the north side until it reaches the point where the distance to the south side is greater than maxHeight // OR, we run into the bottom of the screen double maxFinalY = (stageStartY + maxHeight) < screenHeight ? (stageStartY + maxHeight) : screenHeight; double maxMouseY = maxFinalY + mouseDifY; // we are free to move the south side until the point where the distance from anchor to south side is less than minHeight double minFinalY = stageStartY + minHeight; double minMouseY = minFinalY + mouseDifY; // capture the absolute position of the mouse at the time of the event double curMouseY = mouseEvent.getScreenY(); if (curMouseY < minMouseY) { stage.setHeight(minHeight); // Our mouse passed the value at which we would breach min height // We don't want the curMouseY to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position curMouseY = minMouseY; } else if(curMouseY > maxMouseY){ double newFinalY = maxMouseY - mouseDifY; double newHeight = newFinalY - stageStartY; stage.setHeight(newHeight); // Our mouse passed the value at which we would breach max height // We don't want the curMouseY to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position curMouseY = maxMouseY; } else { double newFinalY = curMouseY - mouseDifY; double newHeight = newFinalY - stageStartY; stage.setHeight(newHeight); } // Our stage and mouse start variables were set via the mouse pressed event handle // If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released. // By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle // While dragging mouse, you aren't releasing and re-pressing it to update the variables.... stageStartY = stage.getY(); stageStartH = stage.getHeight(); mouseStartY = curMouseY; } } // Check if there is a horizontal component to the window resize // The only time there isn't a horizontal component is if the mouse is strictly on the north or south side of the stage. if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) { // There is a horizontal component. // If we are resizing the west side however, we will be resetting both the X coordinate of the stage anchor as well as the stage width. if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent) || Cursor.SW_RESIZE.equals(cursorEvent)) { // we are moving the west side // figure out where the east side of the stage is double mouseDifX = mouseStartX - stageStartX; double finalX = stageStartX + stageStartW; // we are free to move the west side until it reaches the point where the distance to the east side is greater than maxWidth // OR, we run into the left of the screen double minStageX = (finalX - maxHeight) > 0 ? (finalX - maxHeight): 0; double minMouseX = minStageX + mouseDifX; // we are free to move the west side until it reaches the point where the distance to the east side is less than minWidth double maxStageX = finalX - minWidth; double maxMouseX = maxStageX + mouseDifX; // capture the absolute position of the mouse at the time of the event double curMouseX = mouseEvent.getScreenX(); if(curMouseX < minMouseX){ stage.setX(minStageX); // Our mouse passed the value at which we would breach max width // We don't want the curMouseX to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position curMouseX = minMouseX; } else if(curMouseX > maxMouseX){ stage.setX(maxStageX); curMouseX = maxMouseX; // Our mouse passed the value at which we would breach min width // We don't want the curMouseX to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position } else { stage.setX(curMouseX - mouseDifX); } double newX = stage.getX(); double newWidth = finalX - newX; stage.setWidth(newWidth); // Our stage and mouse start variables were set via the mouse pressed event handle // If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released. // By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle // While dragging mouse, you aren't releasing and re-pressing it to update the variables.... stageStartX = stage.getX(); stageStartW = stage.getWidth(); mouseStartX = curMouseX; } else { // Else, we are resizing the east side, and the X coordinate remains fixed. We only change the stage width. // figure out where the current east side actually is double curFinalX = stageStartX + stageStartW; double mouseDifX = mouseStartX - curFinalX; // we are free to move the east side until the point where the distance from anchor to east side is less than minWidth double minFinalX = stageStartX + minWidth; double minMouseX = minFinalX + mouseDifX; // we are free to move the east side until it reaches the point where the distance to the west side is greater than maxWidth // OR, we run into the right of the screen double maxFinalX = (stageStartX + maxWidth) < screenWidth ? (stageStartX + maxWidth) : screenWidth; double maxMouseX = maxFinalX + mouseDifX; // capture the absolute position of the mouse at the time of the event double curMouseX = mouseEvent.getScreenX(); if (curMouseX < minMouseX) { stage.setWidth(minWidth); curMouseX = minMouseX; // Our mouse passed the value at which we would breach min width // We don't want the curMouseX to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position } else if(curMouseX > maxMouseX){ double newFinalX = maxMouseX - mouseDifX; double newWidth = newFinalX - stageStartX; stage.setWidth(newWidth); // Our mouse passed the value at which we would breach max width // We don't want the curMouseY to update any more until the mouse is back over the border. // Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position curMouseX = maxMouseX; } else { double newFinalX = curMouseX - mouseDifX; double newWidth = newFinalX - stageStartX; stage.setWidth(newWidth); } // Our stage and mouse start variables were set via the mouse pressed event handle // If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released. // By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle // While dragging mouse, you aren't releasing and re-pressing it to update the variables.... stageStartX = stage.getX(); stageStartW = stage.getWidth(); mouseStartX = curMouseX; } } } log.Wither(); } //  private boolean isPressed; private Cursor cursorEvent; private CWinMaxButton _max; private double mouseStartX; private double mouseStartY; private double stageStartH; private double stageStartW; private double stageStartX; private double stageStartY; private final int border; private final Stage stage; //  } 

Here is an updated version of ResizeHelper posted by @Alexander.Berg , which supports window drag :

 import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; //created by Alexander Berg public class ResizeHelper { private static double xOffset = 0; private static double yOffset = 0; public static void addResizeListener(Stage stage) { ResizeListener resizeListener = new ResizeListener(stage); stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener); ObservableList children = stage.getScene().getRoot().getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, resizeListener); } } public static void addListenerDeeply(Node node, EventHandler listener) { node.addEventHandler(MouseEvent.MOUSE_MOVED, listener); node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener); node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener); if (node instanceof Parent) { Parent parent = (Parent) node; ObservableList children = parent.getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, listener); } } } static class ResizeListener implements EventHandler { private Stage stage; private Cursor cursorEvent = Cursor.DEFAULT; private int border = 4; private double startX = 0; private double startY = 0; public ResizeListener(Stage stage) { this.stage = stage; } @Override public void handle(MouseEvent mouseEvent) { EventType mouseEventType = mouseEvent.getEventType(); Scene scene = stage.getScene(); double mouseEventX = mouseEvent.getSceneX(), mouseEventY = mouseEvent.getSceneY(), sceneWidth = scene.getWidth(), sceneHeight = scene.getHeight(); if (MouseEvent.MOUSE_MOVED.equals(mouseEventType) == true) { if (mouseEventX < border && mouseEventY < border) { cursorEvent = Cursor.NW_RESIZE; } else if (mouseEventX < border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SW_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY < border) { cursorEvent = Cursor.NE_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SE_RESIZE; } else if (mouseEventX < border) { cursorEvent = Cursor.W_RESIZE; } else if (mouseEventX > sceneWidth - border) { cursorEvent = Cursor.E_RESIZE; } else if (mouseEventY < border) { cursorEvent = Cursor.N_RESIZE; } else if (mouseEventY > sceneHeight - border) { cursorEvent = Cursor.S_RESIZE; } else { cursorEvent = Cursor.DEFAULT; } scene.setCursor(cursorEvent); } else if(MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)){ scene.setCursor(Cursor.DEFAULT); } else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType) == true) { startX = stage.getWidth() - mouseEventX; startY = stage.getHeight() - mouseEventY; xOffset = mouseEvent.getSceneX(); yOffset = mouseEvent.getSceneY(); } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) == true) { if (Cursor.DEFAULT.equals(cursorEvent) == false) { if (Cursor.W_RESIZE.equals(cursorEvent) == false && Cursor.E_RESIZE.equals(cursorEvent) == false) { double minHeight = stage.getMinHeight() > (border*2) ? stage.getMinHeight() : (border*2); if (Cursor.NW_RESIZE.equals(cursorEvent) == true || Cursor.N_RESIZE.equals(cursorEvent) == true || Cursor.NE_RESIZE.equals(cursorEvent) == true) { if (stage.getHeight() > minHeight || mouseEventY < 0) { stage.setHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight()); stage.setY(mouseEvent.getScreenY()); } } else { if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) { stage.setHeight(mouseEventY + startY); } } } if (Cursor.N_RESIZE.equals(cursorEvent) == false && Cursor.S_RESIZE.equals(cursorEvent) == false) { double minWidth = stage.getMinWidth() > (border*2) ? stage.getMinWidth() : (border*2); if (Cursor.NW_RESIZE.equals(cursorEvent) == true || Cursor.W_RESIZE.equals(cursorEvent) == true || Cursor.SW_RESIZE.equals(cursorEvent) == true) { if (stage.getWidth() > minWidth || mouseEventX < 0) { stage.setWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth()); stage.setX(mouseEvent.getScreenX()); } } else { if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) { stage.setWidth(mouseEventX + startX); } } } } else if (mouseEvent.getSceneY() < 70) { stage.setX(mouseEvent.getScreenX() - xOffset); stage.setY(mouseEvent.getScreenY() - yOffset); } } } } 

}

Thanks @Alexander.Berg for providing this utility class (1st version). It helped me to get the desired functionality for my Undecorated Stage.

However, I have few queries regarding some points:

  1. What is need to traverse through all the child nodes and set the handler to each and every parent node. Is is not sufficient to put the handler on Scene only?
  2. If we are anyway checking for the EventType in handler, what is need in setting the same handler to different event types. Can it be set on one super mouse event (MouseEvent.ANY) and can skip the rest.
  3. If point#1 is handled, then there is no need to consider MOUSE_EXITED and MOUSE_EXITED_TARGET to set the default cursor back.
  4. There is every possible chance to trigger the event on underlying node(s) in the drag space(border space) if the handler is added as event handler. The most common case can be if the custom stage close button is very near to corner which comes under the border space. Trying to resize at that space will eventually trigger the close button. Should it need to be implement by adding it as filter (and consume the event when neccessary) rather than handler?

I tried to modify your code to address all the above queries. Please find below the updated ResizeHelper. I am only trying to simplify the things a bit further. Once again thanks for providing the code and letting me to think with a starting point.

 import javafx.event.EventHandler; import javafx.event.EventType; import javafx.scene.Cursor; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; /** * Helper class to set the resizing implementation for any given undecorated stage. */ public class ResizeHelper { /** * Handler to process the resizing of the the given stage. */ static class ResizeHandler implements EventHandler { /** Space to consider around the stage border for resizing */ private static final int BORDER = 10; /** Stage to which the handler is implemented */ private final Stage stage; /** Current cursor reference to the scene */ private Cursor cursor = Cursor.DEFAULT; /** X position of the drag start */ private double startX = 0; /** Y position of the drag start */ private double startY = 0; /** * Constructor. * * @param stageTmp Stage to which resizing to be set. */ public ResizeHandler(final Stage stageTmp) { stage = stageTmp; } @Override public void handle(final MouseEvent event) { final EventType eventType = event.getEventType(); final Scene scene = stage.getScene(); final double mouseEventX = event.getSceneX(); final double mouseEventY = event.getSceneY(); final double sceneWidth = scene.getWidth(); final double sceneHeight = scene.getHeight(); if (MouseEvent.MOUSE_MOVED.equals(eventType)) { setCursor(mouseEventX, mouseEventY, sceneWidth, sceneHeight); scene.setCursor(cursor); } else if (MouseEvent.MOUSE_PRESSED.equals(eventType)) { startX = stage.getWidth() - mouseEventX; startY = stage.getHeight() - mouseEventY; consumeEventIfNotDefaultCursor(event); } else if (MouseEvent.MOUSE_DRAGGED.equals(eventType) && !Cursor.DEFAULT.equals(cursor)) { consumeEventIfNotDefaultCursor(event); if (!Cursor.W_RESIZE.equals(cursor) && !Cursor.E_RESIZE.equals(cursor)) { processVerticalDrag(event); } if (!Cursor.N_RESIZE.equals(cursor) && !Cursor.S_RESIZE.equals(cursor)) { processHorizontalDrag(event); } } } /** * Consumes the mouse event if the cursor is not the DEFAULT cursor. * * @param event MouseEvent instance. */ private void consumeEventIfNotDefaultCursor(final MouseEvent event) { if (!cursor.equals(Cursor.DEFAULT)) { event.consume(); } } /** * Processes the horizontal drag movement and resizes the window width. * * @param event MouseEvent instance. */ private void processHorizontalDrag(final MouseEvent event) { final double minWidth = stage.getMinWidth() > BORDER * 2 ? stage.getMinWidth() : BORDER * 2; final double mouseEventX = event.getSceneX(); if (Cursor.NW_RESIZE.equals(cursor) || Cursor.W_RESIZE.equals(cursor) || Cursor.SW_RESIZE.equals(cursor)) { if (stage.getWidth() > minWidth || mouseEventX < 0) { stage.setWidth(stage.getX() - event.getScreenX() + stage.getWidth()); stage.setX(event.getScreenX()); } } else if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) { stage.setWidth(mouseEventX + startX); } } /** * Processes the vertical drag movement and resizes the window height. * * @param event MouseEvent instance. */ private void processVerticalDrag(final MouseEvent event) { final double minHeight = stage.getMinHeight() > BORDER * 2 ? stage.getMinHeight() : BORDER * 2; final double mouseEventY = event.getSceneY(); if (Cursor.NW_RESIZE.equals(cursor) || Cursor.N_RESIZE.equals(cursor) || Cursor.NE_RESIZE.equals(cursor)) { if (stage.getHeight() > minHeight || mouseEventY < 0) { stage.setHeight(stage.getY() - event.getScreenY() + stage.getHeight()); stage.setY(event.getScreenY()); } } else if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) { stage.setHeight(mouseEventY + startY); } } /** * Determines and sets the appropriate cursor based on the mouse position in relative to scene bounds. * * @param mouseEventX X position of mouse in the scene. * @param mouseEventY Y position of mouse in the scene. * @param sceneWidth Width of the scene. * @param sceneHeight Height of the scene. */ private void setCursor(final double mouseEventX, final double mouseEventY, final double sceneWidth, final double sceneHeight) { final Cursor cursor1; if (mouseEventX < BORDER && mouseEventY < BORDER) { cursor1 = Cursor.NW_RESIZE; } else if (mouseEventX < BORDER && mouseEventY > sceneHeight - BORDER) { cursor1 = Cursor.SW_RESIZE; } else if (mouseEventX > sceneWidth - BORDER && mouseEventY < BORDER) { cursor1 = Cursor.NE_RESIZE; } else if (mouseEventX > sceneWidth - BORDER && mouseEventY > sceneHeight - BORDER) { cursor1 = Cursor.SE_RESIZE; } else if (mouseEventX < BORDER) { cursor1 = Cursor.W_RESIZE; } else if (mouseEventX > sceneWidth - BORDER) { cursor1 = Cursor.E_RESIZE; } else if (mouseEventY < BORDER) { cursor1 = Cursor.N_RESIZE; } else if (mouseEventY > sceneHeight - BORDER) { cursor1 = Cursor.S_RESIZE; } else { cursor1 = Cursor.DEFAULT; } cursor = cursor1; } } /** * Constructor. * */ private ResizeHelper() { } /** * Adds the resize handler to the provided stage. * * @param stage Stage to which the resizing should be implemented. */ public static void addResizeHandler(final Stage stage) { ResizeHandler resizeHandler = new ResizeHandler(stage); stage.setMinHeight(stage.getHeight()); stage.setMinWidth(stage.getWidth()); stage.setMaxHeight(stage.getMaxHeight()); stage.setMaxWidth(stage.getMaxWidth()); stage.getScene().addEventFilter(MouseEvent.ANY, resizeHandler); } } 

Here is a revision @Alexander.Berg ‘s post. This correctly handles the minWidth and maxWidth properties of the Scene. His version does not do this: it completely ignores maxWidth and butchers minWidth. There is also a piece of code at the bottom that allows dragging the window.

 import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; public class ResizeHelper { public static void addResizeListener(Stage stage) { ResizeListener resizeListener = new ResizeListener(stage); stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener); stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener); ObservableList children = stage.getScene().getRoot().getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, resizeListener); } } private static void addListenerDeeply(Node node, EventHandler listener) { node.addEventHandler(MouseEvent.MOUSE_MOVED, listener); node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener); node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED, listener); node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener); if (node instanceof Parent) { Parent parent = (Parent) node; ObservableList children = parent.getChildrenUnmodifiable(); for (Node child : children) { addListenerDeeply(child, listener); } } } private static class ResizeListener implements EventHandler { private Stage stage; private Cursor cursorEvent = Cursor.DEFAULT; private int border = 4; private double startX = 0; private double startY = 0; private double startScreenX = 0; private double startScreenY = 0; public ResizeListener(Stage stage) { this.stage = stage; } @Override public void handle(MouseEvent mouseEvent) { EventType mouseEventType = mouseEvent.getEventType(); Scene scene = stage.getScene(); double mouseEventX = mouseEvent.getSceneX(); double mouseEventY = mouseEvent.getSceneY(); double sceneWidth = scene.getWidth(); double sceneHeight = scene.getHeight(); if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) { if (mouseEventX < border && mouseEventY < border) { cursorEvent = Cursor.NW_RESIZE; } else if (mouseEventX < border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SW_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY < border) { cursorEvent = Cursor.NE_RESIZE; } else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) { cursorEvent = Cursor.SE_RESIZE; } else if (mouseEventX < border) { cursorEvent = Cursor.W_RESIZE; } else if (mouseEventX > sceneWidth - border) { cursorEvent = Cursor.E_RESIZE; } else if (mouseEventY < border) { cursorEvent = Cursor.N_RESIZE; } else if (mouseEventY > sceneHeight - border) { cursorEvent = Cursor.S_RESIZE; } else { cursorEvent = Cursor.DEFAULT; } scene.setCursor(cursorEvent); } else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) { scene.setCursor(Cursor.DEFAULT); } else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) { startX = stage.getWidth() - mouseEventX; startY = stage.getHeight() - mouseEventY; } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) { if (!Cursor.DEFAULT.equals(cursorEvent)) { if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) { double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2); double maxHeight = stage.getMaxHeight(); if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent) || Cursor.NE_RESIZE.equals(cursorEvent)) { double newHeight = stage.getHeight() - (mouseEvent.getScreenY() - stage.getY()); if (newHeight >= minHeight && newHeight <= maxHeight) { stage.setHeight(newHeight); stage.setY(mouseEvent.getScreenY()); } else { newHeight = Math.min(Math.max(newHeight, minHeight), maxHeight); // y1 + h1 = y2 + h2 // y1 = y2 + h2 - h1 stage.setY(stage.getY() + stage.getHeight() - newHeight); stage.setHeight(newHeight); } } else { stage.setHeight(Math.min(Math.max(mouseEventY + startY, minHeight), maxHeight)); } } if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) { double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2); double maxWidth = stage.getMaxWidth(); if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent) || Cursor.SW_RESIZE.equals(cursorEvent)) { double newWidth = stage.getWidth() - (mouseEvent.getScreenX() - stage.getX()); if (newWidth >= minWidth && newWidth <= maxWidth) { stage.setWidth(newWidth); stage.setX(mouseEvent.getScreenX()); } else { newWidth = Math.min(Math.max(newWidth, minWidth), maxWidth); // x1 + w1 = x2 + w2 // x1 = x2 + w2 - w1 stage.setX(stage.getX() + stage.getWidth() - newWidth); stage.setWidth(newWidth); } } else { stage.setWidth(Math.min(Math.max(mouseEventX + startX, minWidth), maxWidth)); } } } } if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) { startScreenX = mouseEvent.getScreenX(); startScreenY = mouseEvent.getScreenY(); } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) { if (Cursor.DEFAULT.equals(cursorEvent)) { stage.setX(stage.getX() + mouseEvent.getScreenX() - startScreenX); startScreenX = mouseEvent.getScreenX(); stage.setY(stage.getY() + mouseEvent.getScreenY() - startScreenY); startScreenY = mouseEvent.getScreenY(); } } } } } 

Kotlin Version

Inspired by the many answers here, I wrote my own implementation that has all the necessary features that I could think of:

特征

  • Draggability

    • Window is dragable
    • Customizable interaction areas (top, right, bottom, left)
    • Custom cursor when hovering over a drag area
  • Fullscreen

    • Window can be set into fullscreen
    • Window restores back to its previous size and position
    • Customizable interaction areas (top, right, bottom, left)
  • Resizeable

    • Window can be resized by the window border in any direction (n, ne, e, se, s, se, w, nw)
    • Customizable interaction areas (n, e, w, s)
    • Double clicking on a window border will resize the window and take up all the available space in that direction
    • Custom cursor when hovering over a resize area

用法

Quick setup:

 StageInteractor(this) .makeDraggable() .makeFullscreenable() .makeResizable() 

Custom interaction areas:

 StageInteractor(this) // custom width / height for drag areas (top, right, bottom, left) .makeDraggable(50, 20, 50, 20) // custom width / height for double-clickable areas (top, right, bottom, left) .makeFullscreenable(50, 20, 50, 20) // custom width / height for resize areas (top, right, bottom, left) .makeResizable(20, 20, 20, 20) 

 import javafx.scene.Cursor import javafx.scene.Scene import javafx.scene.input.MouseButton import javafx.scene.input.MouseEvent import javafx.stage.Screen import javafx.stage.Stage class StageInteractor { private val stage: Stage private val scene: Scene private var isDraggable = false private var isDragging = false private var allowDragging = true private var dragMarginTop = 0.0 private var dragMarginRight = 0.0 private var dragMarginBottom = 0.0 private var dragMarginLeft = 0.0 private var isFullscreenable = false private var isFullscreen = false private var allowFullscreen = true private var fullscreenMarginTop = 0.0 private var fullscreenMarginRight = 0.0 private var fullscreenMarginBottom = 0.0 private var fullscreenMarginLeft = 0.0 private var stageWidthBeforeFullscreen = 0.0 private var stageHeightBeforeFullscreen = 0.0 private var stageXBeforeFullscreen = 0.0 private var stageYBeforeFullscreen = 0.0 private var isResizeable = false private var isResizing = false private var allowResizing = true private var resizeDirection: ResizeDirection? = null private var resizeMarginTop = 0.0 private var resizeMarginRight = 0.0 private var resizeMarginBottom = 0.0 private var resizeMarginLeft = 0.0 constructor(stage: Stage) { this.stage = stage this.scene = stage.scene } fun makeDraggable( marginTop: Double = 50.0, marginRight: Double = 0.0, marginBottom: Double = 0.0, marginLeft: Double = 0.0 ): StageInteractor { dragMarginTop = marginTop dragMarginRight = marginRight dragMarginBottom = marginBottom dragMarginLeft = marginLeft if (!isDraggable) { isDraggable = true var dragStartOffsetX = 0.0 var dragStartOffsetY = 0.0 scene.addEventHandler(MouseEvent.MOUSE_MOVED) { val isWithinBounds = detectDraggingBounds(it) if (isDraggable && allowDragging && isWithinBounds) { scene.cursor = Cursor.OPEN_HAND } else { if (scene.cursor == Cursor.OPEN_HAND) { scene.cursor = Cursor.DEFAULT } } } scene.addEventHandler(MouseEvent.MOUSE_PRESSED) { dragStartOffsetX = stage.x - it.screenX dragStartOffsetY = stage.y - it.screenY } scene.addEventHandler(MouseEvent.MOUSE_DRAGGED) { val isWithinBounds = detectDraggingBounds(it) if (isDraggable && allowDragging && isWithinBounds) { isDragging = true scene.cursor = Cursor.CLOSED_HAND } if (isDragging) { stage.x = it.screenX + dragStartOffsetX stage.y = it.screenY + dragStartOffsetY } } scene.addEventHandler(MouseEvent.MOUSE_RELEASED) { if (isDragging) { isDragging = false scene.cursor = Cursor.DEFAULT } } } return this } private fun detectDraggingBounds(event: MouseEvent): Boolean { return event.sceneY <= dragMarginTop || scene.height - event.sceneY <= dragMarginBottom || event.sceneX <= dragMarginLeft || scene.width - event.sceneX <= dragMarginRight } fun makeFullscreenable( marginTop: Double = 50.0, marginRight: Double = 0.0, marginBottom: Double = 0.0, marginLeft: Double = 0.0 ): StageInteractor { fullscreenMarginTop = marginTop fullscreenMarginRight = marginRight fullscreenMarginBottom = marginBottom fullscreenMarginLeft = marginLeft if (!isFullscreenable) { isFullscreenable = true scene.addEventHandler(MouseEvent.MOUSE_PRESSED) { val isDoubleClick = it.button == MouseButton.PRIMARY && it.clickCount >= 2 if (isFullscreenable && allowFullscreen && isDoubleClick && detectFullscreenBounds(it)) { if (isFullscreen) { isFullscreen = false allowDragging = true allowResizing = true stage.x = stageXBeforeFullscreen stage.y = stageYBeforeFullscreen stage.width = stageWidthBeforeFullscreen stage.height = stageHeightBeforeFullscreen } else { isFullscreen = true allowDragging = false allowResizing = false stageWidthBeforeFullscreen = stage.width stageHeightBeforeFullscreen = stage.height stageXBeforeFullscreen = stage.x stageYBeforeFullscreen = stage.y val screenBounds = Screen.getPrimary().visualBounds val newWidth = if (stage.maxWidth < screenBounds.width) { stage.maxWidth } else { screenBounds.width } val newHeight = if (stage.maxHeight < screenBounds.height) { stage.maxHeight } else { screenBounds.height } stage.width = newWidth stage.height = newHeight stage.x = screenBounds.minX stage.y = screenBounds.minY } } } } return this } private fun detectFullscreenBounds(event: MouseEvent): Boolean { val isWithinBounds = event.sceneY <= fullscreenMarginTop || scene.height - event.sceneY <= fullscreenMarginBottom || event.sceneX <= fullscreenMarginLeft || scene.width - event.sceneX <= fullscreenMarginRight val resizeDirection = detectResizeDirection(event) return isWithinBounds && resizeDirection == null } fun makeResizable( marginTop: Double = 10.0, marginRight: Double = 10.0, marginBottom: Double = 10.0, marginLeft: Double = 10.0 ): StageInteractor { resizeMarginTop = marginTop resizeMarginRight = marginRight resizeMarginBottom = marginBottom resizeMarginLeft = marginLeft if (!isResizeable) { isResizeable = true scene.addEventHandler(MouseEvent.MOUSE_MOVED) { if (isResizeable && allowResizing && !isResizing) { when (detectResizeDirection(it)) { ResizeDirection.NORTH_WEST -> scene.cursor = Cursor.NW_RESIZE ResizeDirection.NORTH_EAST -> scene.cursor = Cursor.NE_RESIZE ResizeDirection.SOUTH_WEST -> scene.cursor = Cursor.SW_RESIZE ResizeDirection.SOUTH_EAST -> scene.cursor = Cursor.SE_RESIZE ResizeDirection.NORTH -> scene.cursor = Cursor.N_RESIZE ResizeDirection.SOUTH -> scene.cursor = Cursor.S_RESIZE ResizeDirection.WEST -> scene.cursor = Cursor.W_RESIZE ResizeDirection.EAST -> scene.cursor = Cursor.E_RESIZE else -> { val cursors = listOf( Cursor.NW_RESIZE, Cursor.NE_RESIZE, Cursor.SW_RESIZE, Cursor.SE_RESIZE, Cursor.N_RESIZE, Cursor.S_RESIZE, Cursor.W_RESIZE, Cursor.E_RESIZE ) if (cursors.contains(scene.cursor)) { scene.cursor = Cursor.DEFAULT } } } } } var resizeStartFromSceneX = 0.0 var resizeStartFromSceneY = 0.0 var resizeStartFromScreenX = 0.0 var resizeStartFromScreenY = 0.0 var resizeStartStageWidth = 0.0 var resizeStartStageHeight = 0.0 scene.addEventHandler(MouseEvent.MOUSE_PRESSED) { if (isResizeable && allowResizing && !isResizing) { resizeDirection = detectResizeDirection(it) if (resizeDirection != null) { if (it.button == MouseButton.PRIMARY && it.clickCount >= 2) { val screenBounds = Screen.getPrimary().visualBounds if (resizeDirection == ResizeDirection.NORTH || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.NORTH_EAST) { stage.height = ensureStageHeightIsWithinLimits( stage.height + stage.y - screenBounds.minY ) stage.y = 0.0 } if (resizeDirection == ResizeDirection.SOUTH || resizeDirection == ResizeDirection.SOUTH_WEST || resizeDirection == ResizeDirection.SOUTH_EAST) { stage.height = ensureStageHeightIsWithinLimits( screenBounds.height - stage.y + screenBounds.minY ) if (stage.height == screenBounds.height) { stage.y = 0.0 } } if (resizeDirection == ResizeDirection.WEST || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.SOUTH_WEST) { stage.width = ensureStageWidthIsWithinLimits( stage.width + stage.x ) stage.x = 0.0 } if (resizeDirection == ResizeDirection.EAST || resizeDirection == ResizeDirection.NORTH_EAST || resizeDirection == ResizeDirection.SOUTH_EAST) { stage.width = ensureStageWidthIsWithinLimits( screenBounds.width - stage.x ) if (stage.width == screenBounds.width) { stage.x = 0.0 } } } else { isResizing = true isDraggable = false isFullscreenable = false resizeStartFromScreenX = it.screenX resizeStartFromScreenY = it.screenY resizeStartFromSceneX = it.sceneX resizeStartFromSceneY = it.sceneY resizeStartStageWidth = stage.width resizeStartStageHeight = stage.height } } } } scene.addEventHandler(MouseEvent.MOUSE_DRAGGED) { if (isResizing) { if (resizeDirection == ResizeDirection.NORTH || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.NORTH_EAST) { val newHeight = ensureStageHeightIsWithinLimits( resizeStartStageHeight + (resizeStartFromScreenY - it.screenY) ) val newY = when (newHeight) { stage.maxHeight, stage.minHeight -> stage.y else -> it.screenY - resizeStartFromSceneY } stage.height = newHeight stage.y = newY } if (resizeDirection == ResizeDirection.SOUTH || resizeDirection == ResizeDirection.SOUTH_WEST || resizeDirection == ResizeDirection.SOUTH_EAST) { val newHeight = ensureStageHeightIsWithinLimits( resizeStartStageHeight + (it.screenY - resizeStartFromScreenY) ) stage.height = newHeight } if (resizeDirection == ResizeDirection.WEST || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.SOUTH_WEST) { val newWidth = ensureStageWidthIsWithinLimits( resizeStartStageWidth + (resizeStartFromScreenX - it.screenX) ) val newX = when (newWidth) { stage.maxWidth, stage.minWidth -> stage.x else -> it.screenX - resizeStartFromSceneX } stage.width = newWidth stage.x = newX } if (resizeDirection == ResizeDirection.EAST || resizeDirection == ResizeDirection.NORTH_EAST || resizeDirection == ResizeDirection.SOUTH_EAST) { val newWidth = ensureStageWidthIsWithinLimits( resizeStartStageWidth + (it.screenX - resizeStartFromScreenX) ) stage.width = newWidth } } } scene.addEventHandler(MouseEvent.MOUSE_RELEASED) { if (isResizing) { isResizing = false isDraggable = true isFullscreenable = true } } } return this } private fun detectResizeDirection(event: MouseEvent): ResizeDirection? { val isNorthResize = event.sceneY <= resizeMarginTop val isSouthResize = scene.height - event.sceneY <= resizeMarginBottom val isWestResize = event.sceneX <= resizeMarginLeft val isEastResize = scene.width - event.sceneX <= resizeMarginRight val isNorthWestResize = isNorthResize && isWestResize val isNorthEastResize = isNorthResize && isEastResize val isSouthWestResize = isSouthResize && isWestResize val isSouthEastResize = isSouthResize && isEastResize return when { isNorthWestResize -> ResizeDirection.NORTH_WEST isNorthEastResize -> ResizeDirection.NORTH_EAST isSouthWestResize -> ResizeDirection.SOUTH_WEST isSouthEastResize -> ResizeDirection.SOUTH_EAST isNorthResize -> ResizeDirection.NORTH isSouthResize -> ResizeDirection.SOUTH isWestResize -> ResizeDirection.WEST isEastResize -> ResizeDirection.EAST else -> null } } private fun ensureStageWidthIsWithinLimits(width: Double): Double { val screenBounds = Screen.getPrimary().visualBounds return when { width > stage.maxWidth -> stage.maxWidth width < stage.minWidth -> stage.minWidth width > screenBounds.width -> screenBounds.width else -> width } } private fun ensureStageHeightIsWithinLimits(height: Double): Double { val screenBounds = Screen.getPrimary().visualBounds return when { height > stage.maxHeight -> stage.maxHeight height < stage.minHeight -> stage.minHeight height > screenBounds.height -> screenBounds.height else -> height } } enum class ResizeDirection { NORTH, NORTH_EAST, NORTH_WEST, SOUTH, SOUTH_EAST, SOUTH_WEST, EAST, WEST; } } 

Undecorater seems to be the only solution.