سؤال

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?

هل كانت مفيدة؟

المحلول

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))
}

نصائح أخرى

Richie's answer is one way to do it. It gives you a single object (the instance returned by generate_silly_gui that you can use to manipulate the model and the widgets used for the GUI. A good approach. The following is simpler, it just does the model and is simply a slight deviation from the code in the question. The use of reference classes here is more structured way of using an environment:

OurModel <- setRefClass("OurModel",
                       fields="m")

## a global
model_instance = OurModel$new(m=NULL)

Then just replace .GlobalEnv$m with model_instance$m in your code and run.

Using a reference class allows you do things like add getter's and setter's that also do other things and pushes you towards the model-view-controller style. The objectSignals package goes in that direction.

If your GUI gets more complicated you might want to decouple the two approaches.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top