Search code examples
ruser-interfaceglobal-variablesgwidgetsreference-class

Using R reference classes to pass values from one window to another in a GUI


I am making a GUI in R using gWidgets. Until now I have been passing values from one window to another via the global environment. Using the global environment is simple to implement but not ideal. One problem is that R CMD check complains about lacking visible bindings for global variables.

As a solution to this problem, reference classes have been mentioned by several R programmers. But to understand how reference classes would work in this context, it would really help to have a simple example.

Let me give a silly GUI to work with. When the user hits the button of the first window, it puts the model m in the global environment. The second button gets m from the global environment and gives an output. When you hit the first button again, it will make a new model m and change the output of the second button. If you close the first window, the button in the second window will still work, because m is in the global environment.

library(gWidgets)
options(guiToolkit = "tcltk")

h1 <- function(h, ...){
  d1 <- data.frame(x=runif(10), y=runif(10))
  .GlobalEnv$m <- lm(x ~ y, data=d1)
}

g1 <- gbutton("1. Make model", 
  container=gwindow(), handler=h1)

h2 <- function(h, ...){
  d2 <- data.frame(y=(1:10)/10)
  p <- predict(.GlobalEnv$m, newdata=d2)
  print(p)
}

g2 <- gbutton("2. Make prediction", 
  container=gwindow(), handler=h2)

How can I use reference classes in this example?


Solution

  • Call setRefClass, and include each widget and data value as a field. Widgets should have type ANY. Initialize those widgets in the initialize method, and outsource functionality to other methods. Create a function to wrap the creation of the class.

    silly_gui_generator <- setRefClass(
      "SillyGui",
      fields = list(
        #widgets
        win1           = "ANY",
        win2           = "ANY",
        button1        = "ANY",
        button2        = "ANY",
        #data
        modelData      = "data.frame",
        predictionData = "data.frame",
        model          = "lm"
      ),
      methods = list(
        initialize = function(modelData = NULL)
        {
          if(is.null(modelData))
          {
            modelData <<- data.frame(x = runif(10), y = runif(10))
          }
    
          win1 <<- gwindow(visible = FALSE)
          win2 <<- gwindow(visible = FALSE)
          button1 <<- gbutton(
            "1. Make model", 
            container = win1, 
            handler   = function(h, ...)
            {          
              makeModel()
            }
          )
          button2 <<- gbutton(
            "2. Make prediction", 
            container = win2, 
            handler   = function(h, ...)
            {          
              print(predictModel())
            }
          )
          visible(win1) <- TRUE
          visible(win2) <- TRUE
        },
        makeModel = function()
        {
          model <<- lm(x ~ y, data = modelData)
        },
        predictModel = function()
        {
          predictionData <<- data.frame(y = (1:10) / 10)
          predict(model, newdata = predictionData)
        }
      )
    )
    
    generate_silly_gui <- function(modelData = NULL)
    {
      invisible(silly_gui_generator$new(modelData = modelData))
    }