ScrollPane vvalue automatically resets if VBox node is scaled scaleX, scaleY

37 views
Skip to first unread message

Bene Volent

unread,
Sep 29, 2019, 7:04:06 PM9/29/19
to ScalaFX Users
I discovered in relation to nodes which are scaled (VBox nodes as containers-inside FlowPane-inside a ScrollPane) there is an interesting effect unexpectedly causing the ScrollPane's vvalue to go to its lowest value / reset. 

Objective is to zoom the selected Node by context menu call (node.scaleX, node.scaleY), but this behavior causes the loss of focus in ScrollPane (if it was a node at the bottom of the ScrollPane, this scaling selection makes it jump to the top of the ScrollPane). I even tried to store the ScrollPane.vvalue and force it to what it was when contextMenu was triggered, but it's not working / listened)...

I'm wandering if there is something that can be done (exclude an event or similar) to exclude this effect (reset of the ScrollPane.vvalue)?

BV

Bene Volent

unread,
Oct 1, 2019, 5:29:39 AM10/1/19
to ScalaFX Users
Seems this is connected to event / alert handling. No clue how.

Jarek

unread,
Oct 1, 2019, 2:44:45 PM10/1/19
to ScalaFX Users
Can you provide some sample code to reproduce the issue?

Bene Volent

unread,
Oct 4, 2019, 5:55:51 PM10/4/19
to ScalaFX Users


On Tuesday, October 1, 2019 at 9:44:45 PM UTC+3, Jarek wrote:
Can you provide some sample code to reproduce the issue?

Took some time, but here's the pruned section of the code. 

Seems this is about ScrollPane and vmin and vmax, since after including them the UI changes behavior with unexpected reset after PopUp window return. See comments in code. 

BTW this is on Eclipse with Windows 10 and Scala 2.12-8.  

--- THE CODE ---

import javafx.application.Application.launch
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.{Scene, Node}
import scalafx.scene.layout.{BorderPane, FlowPane}
import scalafx.scene.{Scene, Node, Group}
import scalafx.scene.layout.{HBox, VBox}
import scalafx.geometry.{HPos, Insets, Orientation, VPos, Pos}
import scalafx.event.ActionEvent
import scalafx.scene.control.ScrollPane.ScrollBarPolicy
import scalafx.scene.control.ScrollPane
import scalafx.scene.input.ContextMenuEvent
import scalafx.scene.control.Alert.AlertType
import scalafx.scene.control._
import scalafx.scene.control.MenuItem._
import scalafx.scene.effect.DropShadow
import scalafx.scene.paint.Color._
import scalafx.scene.paint.Color
import scalafx.scene.shape.Rectangle
import scalafx.beans.property.DoubleProperty


object ScrollFlow extends JFXApp {
  val spane = new ScrollPane
  
  val mainPane = new BorderPane {
      center = centerContent   
  }
    
  stage = new PrimaryStage {
    scene = new Scene(600, 200) {
      root = mainPane
}
}

  // -------------------------------------------------------------------------------------------100
  
  def imagePart() : VBox = new VBox {
    padding = Insets(2, 5, 8, 5)
    children = ( 
            new Rectangle {
              width = 250
              height = 125
              fill = Color.Grey
            }        
    )
  } // END imgPart
  
  // -------------------------------------------------------------------------------------------100
      
  private def centerContent : Node = {
    val flowStuff = new FlowPane {  
      for(ix <- 10 until 96) {
        children += nodeContextMenu((imagePart))   
      }
    }
    
    spane.content = flowStuff
    spane.vbarPolicy = ScrollBarPolicy.Always
    spane.hbarPolicy = ScrollBarPolicy.Never
    spane.pannable = true
    spane.fitToWidth = true
    spane.fitToHeight = true
    // After including these parameters PopUp resets the vvalue position (see below)
    spane.vmin = 0          // vvalue minimum
    spane.vmax = 100        // vvalue maximum
    
    spane
  } // END centerContent
      
  // -------------------------------------------------------------------------------------------100
  
  private def nodeContextMenu(vbnode: VBox) : Node = {
    new Group (vbnode) {
      val contextMenu = new ContextMenu {
        items += (
          new MenuItem("Mark This Node") {
            onAction = (event:ActionEvent) => {
              println("ContextMenu spane.vvalue:"+spane.vvalue)
              alertVideoRemovePopup(vbnode)
            } 
          } 
        ) 
      } 
      // event handler for this contextMenu
      onContextMenuRequested = (event: ContextMenuEvent) => {
        contextMenu.show(vbnode, event.getScreenX, event.getScreenY)
      }  
    }
  }  // END nodeContextMenu

  // -------------------------------------------------------------------------------------------100
  
  /** Create and show confirmation Popup */
  def alertVideoRemovePopup(vbnode:VBox) {
    val dropShadow = new DropShadow() {
      color = Color.Blue
      spread = 0.4
    }
    vbnode.effect = dropShadow
    
    val alert = new Alert(AlertType.Confirmation) {
      println("vvalue in alert:"+spane.vvalue)
      initOwner(stage)
      title = "Confirm"
      headerText = "Header"
      contentText = "Are you ok with this?"
    }
      
    val result = alert.showAndWait()
    // HERE: at this point the vvalue is reset by something, what is missing to tackle this?
    // Can this be related to DoubleValue type or to a missing binding?
    println("vvalue after alert:"+spane.vvalue)  
    
    result match {
      case Some(ButtonType.OK) => println("OK CLick; spane.vvalue:"+spane.vvalue)
      case _                   => println("Cancel or closed")
    }
  } // END alertVideoRemovePopup
  
  // -------------------------------------------------------------------------------------------100

Bene Volent

unread,
Oct 4, 2019, 6:04:13 PM10/4/19
to ScalaFX Users
CopyPaste issue, apologies for missingthe bottom } to close the object.


On Monday, September 30, 2019 at 2:04:06 AM UTC+3, Bene Volent wrote:

Bene Volent

unread,
Oct 5, 2019, 1:30:23 PM10/5/19
to scalaf...@googlegroups.com
To avoid further confusion, Copy/Paste issue is related to posting the code sample, not in relation to actual issue, which is still an open question.

--
You received this message because you are subscribed to the Google Groups "ScalaFX Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scalafx-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/scalafx-users/1336833d-a998-4e6a-91d5-b9a6a78351b4%40googlegroups.com.

Jarek Sacha

unread,
Oct 7, 2019, 9:59:44 PM10/7/19
to scalaf...@googlegroups.com

The way I understand the issue. You put several rectangles into FlowPane and put FlowPane into a ScrollPane. You scroll down and change size of one of the rectangles (add drop shadow). This causes the FlowPane to change layout to accommodate new size of that rectangle. ScrollPane sees that its content changed and resets scroll. I do not know if there is a way to just stop ScrollPane. The options I see:

  1. Do not change the size of your rectangle, render them in a way that you can add highlight (drop shadow) without changing the size of the container.

  2. Wait for the layout to happen and reset ScrollPane back where it was. It is a bit tricky because you need the layout to happen and ScrollPane needs to finish reset. If you do it too early nothing will happen. Here is how the code can be modified to restore scroll position before dialog is displayed:

  /** Create and show confirmation Popup */
  def alertVideoRemovePopup(vbnode: VBox
) {
    // Remember scroll position before making changes to layout
    val vvalue = spane.vvalue.value

    
val dropShadow = new DropShadow() {
      color = Color.Blue
      spread = 0.4

    }
    vbnode.effect = dropShadow

    // A hack to leave some time for FX to update layout after shadow was added,
    // FX will also reset vscroll to 1 after layout
    Thread.sleep(100)

    // Additional step to help FX do layout before we reset scroll pane to initial vvalue
    Platform.runLater { () =>
      // Restore scroll position
      spane.vvalue.value = vvalue

      
val alert = new Alert(AlertType.Confirmation) {
        println("vvalue in alert:" + spane.vvalue)
        initOwner(stage)
        title = "Confirm"
        headerText = "Header"
        contentText = "Are you ok with this?"
      }

      val result =
 alert.showAndWait()

      result match {
        
case Some(ButtonType.OK) => println("OK CLick; spane.vvalue:" + spane.vvalue)
        case _ => println("Cancel or closed")
      }
    }
  } // END alertVideoRemovePopup

Below is a GIF with how it works for me

08650E1F-3E66-4C71-91A8-5A4247731C5F.GIF

There are probably other ways to to do it too.

Jarek

Bene Volent

unread,
Oct 8, 2019, 1:38:40 PM10/8/19
to ScalaFX Users
Jarek, This workaround does seem to work fine for my purposes as I tested it straight away. I am grateful to you for that. Just kind of hard to figure out the "forces behind" and how they need to be considered in the design strategy to avoid these shortcomings...
Reply all
Reply to author
Forward
0 new messages