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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With