Search code examples
scalascalafxscalafxml

Unable to access ScalaFXML controller class members in another class


I have following code

@sfxml
class Charts(private val predicate_chart:ComboBox[String],
             private val measure_chart : ComboBox[String],
             private val chart_chart : ComboBox[String],
             private val chart_graph : ComboBox[String],
             private val chart_container:Pane) {

       val stackPane = new StackPane()

       def createCharts(): Unit ={    
            new KPie(this).createPie()
       }
}

class KPie(charts:Charts) {
     def createPieUI(result : ResultSet): Unit ={
           val qs: List[QuerySolution] = new IteratorResultSetQuerySolution(result).toList
           val pieChartData = ObservableBuffer(qs.map { v: QuerySolution => PieChart.Data(v.get("s").toString, v.getLiteral("measure").getDouble) })
           val chart = new PieChart(pieChartData)         
           charts.stackPane.getChildren.addAll(chart)
     }
}

while I am trying this I am getting following error

Error: value stackPane is not a member of Charts
    charts.stackPane.getChildren.addAll(chart)

I just want to use the controller class variables in another class. Please help me to access those member variables thanks in advance.


Solution

  • I'm not able to test that this resolves your issue, but the ScalaFXML documentation indicates that the @sfxml macro replaces the controller class with a generated version, which effectively removes user-supplied class members. It looks like this is the explanation for your problem.

    The documentation describes an approach to providing additional class members by placing them in a trait (or, possibly, an abstract class) from which your controller class is extended. To access these members, you first need to get a reference to the controller by calling getController[Trait] (where Trait is the name of the trait) on your FXMLLoader instance. Following this approach, your code would look something like this:

    trait ChartsMembers {
    
      // Note: This stack pane is not apparently used anywhere.
      val stackPane = new StackPane()
    
      // Note: I added the ResultSet argument which is needed for your example.
      def createCharts(result: ResultSet): Unit = {
    
        // Note: There was no createPie() member of KPie in your example. I'm assuming that the
        // createPieUI(ResultSet) function was the original intended call. Here's what I did:
        // 1. Renamed createPie to createPieUI.
        // 2. Added the ChartsMembers reference, so that this trait's members are available.
        //
        // Note: The reference to the KPie instance was neither stored nor returned, and so would
        // be garbage collected. Since its charts argument is now redundant, I changed KPie to
        // be an object.
        KPie.createPieUI(this, result)
      }
    }
    
    // Note: Charts now extends ChartsMembers.
    //
    // Note: I have removed the "private val" declarations for the arguments, since they are
    // redundant.
    @sfxml
    class Charts(
      predicate_chart: ComboBox[String],
      measure_chart: ComboBox[String],
      chart_chart: ComboBox[String],
      chart_graph: ComboBox[String],
      chart_container: Pane
    ) extends ChartsMembers
    
    // Note: This is now an object - see note above.
    object KPie {
    
      // Note: Changed signature - see note above.
      def createPieUI(controller: ChartsMembers, result: ResultSet): Unit = {
        val qs: List[QuerySolution] = new IteratorResultSetQuerySolution(result).toList
        val pieChartData = ObservableBuffer(qs.map { v: QuerySolution => 
          PieChart.Data(v.get("s").toString, v.getLiteral("measure").getDouble)
        })
        val chart = new PieChart(pieChartData)
    
        // Access the stackPane member on the controller.
        controller.stackPane.getChildren.addAll(chart)
      }
    }
    

    In order to use the above, you would first need to get a loader reference (which you will need in any case), and you would then use that to call the createCharts function:

    val result = // As required...
    val loader = new FXMLLoader(/*Args as required...*/)
    val controller = loader.getReference[ChartsMembers]
    
    // Create the charts on the controller.
    controller.createCharts(result)
    

    To be honest, this all seems rather convoluted, and the resulting populated stack pane isn't used by anything or added to the scene graph, so the above is all fairly pointless. However, I hope you can see how this approach works, so that you can implement it in your application.

    A minimal, complete and verifiable example (including all source files and build files) would have helped immensely.