bekwam courses

JavaFX Flat Custom Windows - Part 2

July 16, 2016

Moving

In the previous article, the standard windowing chrome and controls of a Stage were removed by calling the initStyle() method with StageStyle.UNDECORATED. An app-specific title bar including a window close button was added. However, there wasn't a mechanism to move the window. The window was displayed in the middle of the screen and could only be pushed to the background or closed.

This article will allow the user to move the window by selecting the title bar and dragging to another location on the screen.

Scene Builder

These steps wire several controller methods to the FXML file. There will be event handlers for the start of the move operation, the end of the move operation, and one for the during the move operation.

  1. Open resources/part2/start/FlatWin.fxml in Scene Builder.
  2. Select the topmost VBox. This is the root of the hierarchy.
  3. In the fx:id text field, enter "vbox".
  4. Expand the Code Tab.
  5. Select the topmost HBox. This is the title bar that contains two other HBoxes.
  6. In the On Drag Detected text field, enter "startMoveWindow".
  7. Scene Builder Code Tab
    Setting On Drag Detected Handler
  8. In the On Mouse Dragged text field, enter "moveWindow". Note that this is in the Mouse section rather than the DragDrop section.
  9. In the On Mouse Release text field, enter "endMoveWindow".
Scene Builder Code Tab
Setting Mouse Handlers

Code

Next, add the methods to the controller.

  1. In the IDE, open net.bekwam.bkcourse.flatwinapp.part2.start.FlatWinController.
  2. Add the following methods. They are implemented with a System.out.println which will be substituted later.

@FXML
public void startMoveWindow(MouseEvent evt) {
  System.out.println("start");
}
@FXML
public void moveWindow(MouseEvent evt) {
  System.out.println("moving");
}
@FXML
public void endMoveWindow(MouseEvent evt) {
  System.out.println("end");
}

Test to make sure that the FXML is wired correctly to the controller. Start the app and click-drag-release on the title bar. printlns for the start, moving, and end events should be displayed.

The controller class will need several variables to track the moving operation.

  1. Add the following member variables to the controller class.

@FXML
VBox vbox;

private double startMoveX = -1, startMoveY = -1;
private Boolean dragging = false;
private Rectangle moveTrackingRect;
private Popup moveTrackingPopup;

vbox is referenced from the fxml file. It is the container of all the components including the title bar.

startMoveX and startMoveY are recorded at the start of a move operation and they are used to compute an ending position. They are also used in a computation that moves around a Rectangle, moveTrackingRect, during the operation. The Rectangle is a visual cue that will go away when the operation is ended.

dragging is a state variable used to ignore MouseEvents that are not involved in the move operation.

moveTrackingRect is an outline showing what the move operation might look like. moveTrackingPopup is the windowing container for the Rectangle.

  1. Replace the startMoveWindow method (now just a println) with this.

@FXML
public void startMoveWindow(MouseEvent evt) {

  startMoveX = evt.getScreenX();
  startMoveY = evt.getScreenY();
  dragging = true;

  moveTrackingRect = new Rectangle();
  moveTrackingRect.setWidth(vbox.getWidth());
  moveTrackingRect.setHeight(vbox.getHeight());
  moveTrackingRect.getStyleClass().add( "tracking-rect" );

  moveTrackingPopup = new Popup();
  moveTrackingPopup.getContent().add(moveTrackingRect);
  moveTrackingPopup.show(vbox.getScene().getWindow());
  moveTrackingPopup.setOnHidden( (e) -> resetMoveOperation());
}

private void resetMoveOperation() {
  startMoveX = 0;
  startMoveY = 0;
  dragging = false;
  moveTrackingRect = null;
}

startMoveWindow is registered to the On Drag Detected event on the title bar. In startMoveWindow, the starting point is set using the Screen position. Next, the dragging flag is set. The tracking rectangle, moveTrackingRect, is created as a slightly-transparent white Rectangle with a border. moveTrackingRectangle is added to a Popup, moveTrackingPopup, which displays the Rectangle on the screen.

  1. Replace the moveWindow method with this.

@FXML
public void moveWindow(MouseEvent evt) {

  if (dragging) {

    double endMoveX = evt.getScreenX();
    double endMoveY = evt.getScreenY();

    Window w = vbox.getScene().getWindow();

    double stageX = w.getX();
    double stageY = w.getY();

    moveTrackingPopup.setX(stageX + (endMoveX - startMoveX));
    moveTrackingPopup.setY(stageY + (endMoveY - startMoveY));
  }
}

If the app is in a dragging state, calculate an offset and move the tracking Popup moveTrackingPopup accordingly.

  1. Replace the endMoveWindow method with this.

@FXML
public void endMoveWindow(MouseEvent evt) {

  if (dragging) {
    double endMoveX = evt.getScreenX();
    double endMoveY = evt.getScreenY();

    Window w = vbox.getScene().getWindow();

    double stageX = w.getX();
    double stageY = w.getY();

    w.setX(stageX + (endMoveX - startMoveX));
    w.setY(stageY + (endMoveY - startMoveY));

    if (moveTrackingPopup != null) {
      moveTrackingPopup.hide();
      moveTrackingPopup = null;
    }
  }

  resetMoveOperation();
}

Finally, the same computation is applied at the end of the move to the window being dragged as was used for the tracking Popup. The state variables (startMoveX, startMoveY, etc) are reset and the tracking Popup is closed. This is skipped if the tracking Popup was auto hidden via an ESC key press.

Save and run the program. Click-drag the title bar to reposition the window.

This example allowed the user to move the custom window. The next article will add maximize and minimize buttons to the program.


Headshot of Carl Walker

By Carl Walker

President and Principal Consultant of Bekwam, Inc