Convenience methods Event handlers vs Mouse Events

45 views
Skip to first unread message

invic...@gmail.com

unread,
May 9, 2021, 4:44:01 AM5/9/21
to ScalaFX Users
Hi,
(Reposted with enhanced code formatting) Trying to adopt event handlers from convenience methods, but I'm facing undesired behavior. Structure is simple: I have a FlowPane with bunch of VBox nodes. 

I'm applying a Select / De-Select mechanism. As I click on a VBox node it gets selected, similarly as I click on empty space in FlowPane area, all nodes gets de-selected.

I'd like to use convenience methods for selecting VBox nodes , which reduces amount of code at the handler (node selector) side ie:
onMouseClicked = (_: MouseEvent) => vBoxCyanHandler(self)
But this is not exclusive event since the FlowPane convenience method gets triggered as well, which would mean a de-selection event right after the selection event. As a workaround I've implemented Mouse Events for the caller VBox Node and this will not trigger the FlowPane convenience method's event. 

Is this because of some event hierarchy rule base? Code is below (Grey VBoxes behave as desired (with Mouse Event) whereas Cyan VBox nodes clicked trigger also FlowPane convenience method event handler (not desired). 

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.layout.FlowPane
import scalafx.scene.Scene
import scalafx.scene.layout.VBox
import scalafx.geometry.Insets
import scalafx.scene.input.{MouseButton, MouseEvent}
import scalafx.scene.paint.Color
import scalafx.scene.shape.Rectangle

object EventPriority extends JFXApp {

  // -------------------------------------------------------------------------------------------100

  private def vBoxGreyMouseEvents(videoNode: VBox): Unit = {
    videoNode.onMouseClicked = (me: MouseEvent) => {
      me.button match {
        case MouseButton.Primary => println("VBox: Primary mouse button")
        case _ =>
      }
      me.consume()
    }
  }

  // -------------------------------------------------------------------------------------------100

  private def vBoxCyanHandler(flowPNode: VBox): Unit = {
    println("from VBox")
    //
  }

  // -------------------------------------------------------------------------------------------100

  private def vboxCYANWithConvenience: VBox = new VBox {
    padding = Insets(2, 5, 8, 5)
    children = new Rectangle {
      width = 250
      height = 125
      fill = Color.Cyan
    }
    val self: VBox = this
    onMouseClicked = (_: MouseEvent) => vBoxCyanHandler(self)
  }

  // -------------------------------------------------------------------------------------------100

  private def vboxGREYWithMouseEV: VBox = new VBox {
    padding = Insets(2, 5, 8, 5)
    children = new Rectangle {
      width = 250
      height = 125
      fill = Color.Grey
    }
    val self: VBox = this
    vBoxGreyMouseEvents(self)
  }

  // -------------------------------------------------------------------------------------------100

  private def flowPaneHandler(flowPNode: FlowPane): Unit = {
    println("from FlowPane")
  }

  // -------------------------------------------------------------------------------------------100

  private def fpaneContent: FlowPane = {
    val flowpane = new FlowPane {
      for (ix <- 1 until 4) {
        children += vboxCYANWithConvenience
        children += vboxGREYWithMouseEV
      }
    }
    flowpane.onMouseClicked = (_: MouseEvent) => flowPaneHandler(flowpane)// flowPNodeEventHandler(videoFlowPane)

    flowpane
  }

  // -------------------------------------------------------------------------------------------100

  stage = new PrimaryStage {
    scene = new Scene(600, 200) {
      root = fpaneContent
    }
  }
} // END

Jarek Sacha

unread,
May 9, 2021, 10:26:08 AM5/9/21
to scalaf...@googlegroups.com
There is an example of event filtering in this simple project: https://github.com/scalafx/ScalaFX-Tutorials/tree/master/event-filters
Will that work approach for you?

--
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/b402b920-6e6a-4b50-929f-d9c6802f5997n%40googlegroups.com.

Bene Volent

unread,
May 9, 2021, 11:02:09 AM5/9/21
to scalaf...@googlegroups.com
I'm familiar with those. But I probably need to rephrase my question: Why with a single click do I get events from both FlowPane AND from Cyan VBox (with convenience methods), whereas clicking Grey VBox nodes I get an event only from Grey VBox nodes (Mouse Event handler)?


Jarek Sacha

unread,
May 9, 2021, 5:17:36 PM5/9/21
to scalaf...@googlegroups.com
"vBoxGreyMouseEvents" consumes the event, so it is not propagated: "me.consume()". If you remove that, the event is propagated to FlowPane. Are you accounting for that?

Bene Volent

unread,
May 9, 2021, 6:00:28 PM5/9/21
to scalaf...@googlegroups.com
Actually I'm looking to get the event only from a VBox node. Ideal model is how VBoxCYAN is defined with its convenience method, but it also triggeres onMousClicked FlowPane -event.

It's about style and clarity in the code. 

On my opinion it'd be clearer to have the onMouseClicked = (_: MouseEvent) => vBoxCyanHandler(self) triggering exclusivly and only the Event for VBox node. 

This would be stylish, because I'd only need to take care what happens after the Event. This means that vBoxCyanHandler(self) method does not need to concern about Event handling, instead just of the consequence of an event (and involvement of FlowPane Event is completely unnecessary when I click the VBox node).

Of course I could go on with this workaround like in case of vboxGREYWithMouseEV method, but I don't like spread the VBox -class parts all around like the onMouseClicked convenience method.


Jarek Sacha

unread,
May 9, 2021, 7:25:56 PM5/9/21
to scalaf...@googlegroups.com
I am not certain that I really understand your reservations about structuring the code. JavaFX has a specific way of processing events to enable flexibility in handling/filtering them. To make custom use of them (interpret click on FlowPane and VBox) you have to account for the way JavaFX processes them, for instance you need to "consume" to prevent passing/bubbling to the parent. In case you have not seen it, there is a really nice description of event processing here: https://docs.oracle.com/javafx/2/events/processing.htm It says JavaFX 2 but those principles have not changed since then. 

If you are looking for more clarity, maybe what is needed is to have model that represents the selection state, something like:
 class ColorSelectionModel {
  val graySelected = BooleanProperty(false) // selected when gray rectangle clicked
  val cyanSelected = BooleanProperty(false) // selected when cyan rectangle clicked
}
so the rest of the application just sees that state rather than internals of the UI that changes the model.

invic...@gmail.com

unread,
May 9, 2021, 8:16:43 PM5/9/21
to ScalaFX Users
The way I intend to use is "dramatized" by the code below :) I hope it helps to see even better the considerations I am facing. 
This code model is actually closer to my real world case with database interaction and presenting it separately on VBox nodes and occasional need to clean the FlowPane.

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.input.{MouseButton, MouseEvent}
import scalafx.scene.layout.{FlowPane, VBox}
import scalafx.scene.paint.Color
import scalafx.scene.shape.Rectangle

object EventPriorityTight extends JFXApp {

  // -------------------------------------------------------------------------------------------100

  private def vBoxCyanHandler(rowId: Int, vbox:VBox): Unit = {
    // Database operations to fetch record for this rowId and process it further...
    // . . .
    println("Show fetch results report...")
  }

  // -------------------------------------------------------------------------------------------100

  private def flowPaneContentReset(flowPNode: FlowPane): Unit = {
    // This should only occur when clicking mouse on FlowPane area not intersecting with FlowPane
    // children, but this is triggered also for VBox node...
    println("Remove all elements from FlowPane...")
    flowPNode.children.removeAll(flowPNode.children)
  }

  // -------------------------------------------------------------------------------------------100

  private def vbox(rowId:Int): VBox = new VBox {
    padding = Insets(2, 5, 8, 5)
    children = new Rectangle {
      width = 250
      height = 125
      fill = Color.Cyan
    }
    val self: VBox = this
    onMouseClicked = (_: MouseEvent) => vBoxCyanHandler(rowId, self)
  }

  // -------------------------------------------------------------------------------------------100

  private def fpaneContent: FlowPane = {
    val databaseRowIdList= List[Int](1,2,3,4,5,6)
    val flowpane = new FlowPane {
      for (rowId <- databaseRowIdList) {
        children += vbox(rowId)
      }
    }
    flowpane.onMouseClicked = (_: MouseEvent) => flowPaneContentReset(flowpane)
    flowpane
  }

  // -------------------------------------------------------------------------------------------100

  stage = new PrimaryStage {
    scene = new Scene(600, 200) {
      root = fpaneContent
    }
  }
} // END



Bene Volent

unread,
May 10, 2021, 1:13:37 PM5/10/21
to scalaf...@googlegroups.com
Thanks Jarek, got it, finally there is clarity. Given that VBox node is a child node of FlowPane and we want to limit / are interested only in VBox node's event: 
1) Include the event to the convenience method of VBox child node
2) Consume the event at event handler method
Here they are:

  private def vbox(rowId:Int): VBox = new VBox {
    padding = Insets(2, 5, 8, 5)
    children = new Rectangle {
      width = 250
      height = 125
      fill = Color.Cyan
    }
    val self: VBox = this
    onMouseClicked = (event: MouseEvent) => vBoxCyanHandler(rowId, self, event)
  }

  private def vBoxCyanHandler(rowId: Int, vbox:VBox, event: MouseEvent): Unit = {
    // Database operations to fetch record for this rowId and process it further...
    // . . .
    println("In VBox,show fetch results report..."+rowId)
    event.consume()
  }


You received this message because you are subscribed to a topic in the Google Groups "ScalaFX Users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/scalafx-users/UHt8t6XesZs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to scalafx-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/scalafx-users/c211df87-7dbe-4d95-98e3-5ec6824f8a6fn%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages