【问题标题】:Make ScalaFX scene draggable使 ScalaFX 场景可拖动
【发布时间】:2014-12-28 18:33:08
【问题描述】:

我实际上正在开发一个没有窗口装饰的 ScalaFX 应用程序。 我想做的是让它可拖动,以便用户可以随意移动它。

我只需将拖动鼠标的坐标写入舞台 X/Y 坐标即可移动舞台。然而,这导致了一个滞后和闪烁的窗口。

如何在 ScalaFX 中顺利实现舞台的拖动?

【问题讨论】:

  • 我回答了一个对我有用的例子,但如果你发布一个你已经尝试过的代码示例会有所帮助。

标签: scalafx


【解决方案1】:

我知道已经回答了这个问题,但为了完整起见,我想包括我过去的做法。

学分:
可拖动窗口:Pro JavaFX 8 示例
可调整大小的窗口:小孩子 - Allow user to resize an undecorated Stage
上/下恢复:Undecorator - UndecoratorController.java

为(混乱的)代码墙做好准备

class UndecoratedWindowHelper(val target: Stage) {

   var anchorPt: Point2D = null
   var prevLoc: Point2D = null
   private var savedBounds: BoundingBox = _
   private val _maximized: ReadOnlyBooleanWrapper = new ReadOnlyBooleanWrapper {value = false}

   // begin init
   val resizeListener = new ResizeListener(target, this)
   target.scene.get.addEventHandler(jfxme.MOUSE_MOVED, resizeListener)
   target.scene.get.addEventHandler(jfxme.MOUSE_PRESSED, resizeListener)
   target.scene.get.addEventHandler(jfxme.MOUSE_DRAGGED, resizeListener)
   // end init

   def addWindowDragPoint(dragNode: Node): Unit = {
      dragNode.onMousePressed = (event: MouseEvent) => {
         anchorPt = new Point2D(event.screenX, event.screenY)
         prevLoc = new Point2D(target.getX, target.getY)
      }

      dragNode.onMouseClicked = (event: MouseEvent) =>
         if (event.clickCount == 2)
            maximizeOrRestore()

      dragNode.onMouseDragged = (event: MouseEvent) =>
         if (resizeListener.cursorEvent == Cursor.DEFAULT && !maximized) {
            target.x = prevLoc.x + event.screenX - anchorPt.x
            target.y = prevLoc.y + event.screenY - anchorPt.y
         }

      target.onShown = (event: WindowEvent) => prevLoc = new Point2D(target.getX, target.getY)
   }


   def maximizeOrRestore() {
      if (maximized) {
         restoreSavedBounds()
         savedBounds = null
         _maximized set false
      } else {
         val screensForRectangle = Screen.screensForRectangle(target.getX, target.getY, target.getWidth, target.getHeight)
         val screen = screensForRectangle.get(0)
         val visualBounds = screen.visualBounds
         savedBounds = new BoundingBox(target.getX, target.getY, target.getWidth, target.getHeight)

         target.setX(visualBounds.getMinX)
         target.setY(visualBounds.getMinY)
         target.setWidth(visualBounds.getWidth)
         target.setHeight(visualBounds.getHeight)
         _maximized set true
      }
   }
   def saveBounds() {
      savedBounds = new BoundingBox(target.getX, target.getY, target.getWidth, target.getHeight)
   }

   def restoreSavedBounds() {
      target.setX(savedBounds.getMinX)
      target.setY(savedBounds.getMinY)
      target.setWidth(savedBounds.getWidth)
      target.setHeight(savedBounds.getHeight)
      savedBounds = null
   }

   def maximized = _maximized.getValue
   def maximizedProp = _maximized.getReadOnlyProperty
}

//formatter:off
protected class ResizeListener(
                                  val stage: Stage, owner: UndecoratedWindowHelper) extends EventHandler[jfxme] {

   var cursorEvent = Cursor.DEFAULT
   val border = 4
   var startX = 0d
   var startY = 0d
   //formatter:on

   def handle(mouseEvent: jfxme) {
      val mouseEventType = mouseEvent.getEventType
      val scene = stage.getScene

      val mouseEventX = mouseEvent.getSceneX
      val mouseEventY = mouseEvent.getSceneY
      val sceneWidth = scene.getWidth
      val sceneHeight = scene.getHeight

      if (jfxme.MOUSE_MOVED == mouseEventType) {

         //@formatter:off
         cursorEvent =
               if (mouseEventX < border && mouseEventY < border) Cursor.NW_RESIZE
               else if (mouseEventX < border && mouseEventY > sceneHeight - border) Cursor.SW_RESIZE
               else if (mouseEventX > sceneWidth - border && mouseEventY < border) Cursor.NE_RESIZE
               else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) Cursor.SE_RESIZE
               else if (mouseEventX < border) Cursor.W_RESIZE
               else if (mouseEventX > sceneWidth - border) Cursor.E_RESIZE
               else if (mouseEventY < border) Cursor.N_RESIZE
               else if (mouseEventY > sceneHeight - border) Cursor.S_RESIZE
               else Cursor.DEFAULT
         //@formatter:on
         if (owner.maximized)
            cursorEvent = Cursor.DEFAULT


         scene.setCursor(cursorEvent)
      } else if (mouseEventType == jfxme.MOUSE_PRESSED) {

         startX = stage.getWidth - mouseEventX
         startY = stage.getHeight - mouseEventY

      } else if (mouseEventType == jfxme.MOUSE_DRAGGED) {
         if (Cursor.DEFAULT != cursorEvent) {


            if (Cursor.W_RESIZE != cursorEvent && Cursor.E_RESIZE != cursorEvent) {// not west or east

               val minHeight = Math.max(stage.getMinHeight, border * 2)


               if (Cursor.NW_RESIZE == cursorEvent || Cursor.N_RESIZE == cursorEvent || Cursor.NE_RESIZE == cursorEvent) {// NW or N or NE

                  val attemptedSize = stage.getY - mouseEvent.getScreenY + stage.getHeight
                  val actSize = Math.max(minHeight, attemptedSize)
                  val diff = actSize - attemptedSize

                  stage.setHeight(actSize)
                  stage.setY(mouseEvent.getScreenY - diff)

               } else {// SW or S or SE

                  val attemptedSize = mouseEventY + startY
                  val actSize = Math.max(minHeight, attemptedSize)

                  stage.setHeight(actSize)
               }
            }

            if (Cursor.N_RESIZE != cursorEvent && Cursor.S_RESIZE != cursorEvent) {

               val minWidth = Math.max(stage.getMinWidth, border * 2)

               if (Cursor.NW_RESIZE == cursorEvent || Cursor.W_RESIZE == cursorEvent || Cursor.SW_RESIZE == cursorEvent) {
                  val attemptedSize = stage.getX - mouseEvent.getScreenX + stage.getWidth
                  val actSize = Math.max(minWidth, attemptedSize)
                  val diff = actSize - attemptedSize

                  stage.setWidth(actSize)
                  stage.setX(mouseEvent.getScreenX - diff)

               } else {
                  val attemptedSize = mouseEventX + startX
                  val actSize = Math.max(minWidth, attemptedSize)

                  stage.setWidth(actSize)

               }
            }
            owner.saveBounds()
         }

      }
   }
}

【讨论】:

    【解决方案2】:

    这是一个适合我的示例。它改编自“JavaFX 8 Introduction by Example”一书中的一个示例。与您的尝试相比如何?

    import scalafx.Includes._
    import scalafx.application.JFXApp
    import scalafx.application.JFXApp.PrimaryStage
    import scalafx.geometry.Point2D
    import scalafx.scene.Scene
    import scalafx.scene.input.MouseEvent
    import scalafx.scene.paint.Color
    import scalafx.stage.{WindowEvent, StageStyle}
    
    object DraggableApp extends JFXApp {
    
      private var anchorPt: Point2D = null
      private var previousLocation: Point2D = null
    
      stage = new PrimaryStage {
        initStyle(StageStyle.TRANSPARENT)
        scene = new Scene {
          fill = Color.rgb(0, 0, 0, 1.0)
        }
      }
    
      // Initialize stage to be movable via mouse
      initMovablePlayer()
    
      /**
       * Initialize the stage to allow the mouse cursor to move the application
       * using dragging.
       */
      private def initMovablePlayer(): Unit = {
        val scene = stage.getScene
    
        scene.onMousePressed = (event: MouseEvent) => anchorPt = new Point2D(event.screenX, event.screenY)
    
        scene.onMouseDragged = (event: MouseEvent) =>
          if (anchorPt != null && previousLocation != null) {
            stage.x = previousLocation.x + event.screenX - anchorPt.x
            stage.y = previousLocation.y + event.screenY - anchorPt.y
          }
    
        scene.onMouseReleased = (event: MouseEvent) => previousLocation = new Point2D(stage.getX, stage.getY)
    
        stage.onShown = (event: WindowEvent) => previousLocation = new Point2D(stage.getX, stage.getY)
      }
    }
    

    编辑:关于你关于调整舞台大小的问题,我尝试了以下变体,它在右键单击和拖动时调整舞台大小;我没有看到任何闪烁(在 OS X 上)。

    import javafx.scene.{input => jfxsi}
    // (...)
    
    object DraggableApp extends JFXApp {
    
      private var previousHeight = 0.0
      private var previousWidth = 0.0
      // (...)
    
      private def initMovablePlayer(): Unit = {
        val scene = stage.getScene
    
        scene.onMousePressed = (event: MouseEvent) => anchorPt = new Point2D(event.screenX, event.screenY)
    
        scene.onMouseDragged = (event: MouseEvent) =>
          if (anchorPt != null && previousLocation != null) {
            if (event.getButton == jfxsi.MouseButton.PRIMARY) {
              stage.x = previousLocation.x + event.screenX - anchorPt.x
              stage.y = previousLocation.y + event.screenY - anchorPt.y
            } else if (event.getButton == jfxsi.MouseButton.SECONDARY) {
              stage.width = previousWidth + event.screenX - anchorPt.x
              stage.height = previousHeight + event.screenY - anchorPt.y
            }
          }
    
        scene.onMouseReleased = (_: MouseEvent) => reset()
    
        stage.onShown = (_: WindowEvent) => reset()
    
        def reset (): Unit = {
          previousLocation = new Point2D(stage.getX, stage.getY)
          previousHeight = stage.getHeight
          previousWidth = stage.getWidth
        }
      }
    }
    

    【讨论】:

    • 首先,谢谢您的回答。我不知道为什么它以前对我不起作用,也许我的笔记本电脑承受着压力并且像地狱一样滞后?您的解决方案有效。但是,您的解决方案在删除 previousLocation 和 anchorPt 时也有效,只需将 onMouseDragged 处理程序中的 event.screenX/Y 分配给 stage.x,y (基本上是我在问这个问题 Oo 之前所拥有的)。为什么需要previousLocation 和achorPt?
    • 只是一个题外话的问题,如果您以前使用过未装饰的窗户:您知道如何在调整未装饰的舞台大小时消除闪烁(尤其是在较大的舞台尺寸上,例如最大化窗户)?
    • 使用stage.x = event.screenX; stage.y = event.screenY,当您开始拖动时,舞台的左上角会跳转到鼠标位置。如果你有一个大舞台并且在开始拖动之前单击右下角附近,效果会更明显。这里的想法是将应用于鼠标光标的相同移动应用于舞台,但起点不同(舞台的左上角在其初始位置与您单击的点)。我会看看调整大小的问题,但我不能做出任何承诺,因为我对此没有很多经验。
    • 我添加了一个我所做的添加调整大小的示例(使用鼠标右键调整舞台大小,左键拖动舞台),我没有看到任何闪烁。您是否在计算机上闪烁相同的代码?使用不同的代码?具有相似的代码但不同/更复杂的阶段?
    • 感谢您的帮助。我无法重现该行为,所以我猜这是由我的程序的另一部分引起的。如果我有重现问题的小示例代码,我将重新发布我的另一个问题:)
    猜你喜欢
    • 2020-09-18
    • 1970-01-01
    • 1970-01-01
    • 2014-07-05
    • 1970-01-01
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多