Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Of all the things I dislike about R, one of the biggest is the fact that you can declare a function within the list of arguments to another function. I’ve gotten over it for very minor operations needed by things like lapply, but it can drive me bonkers elsewhere. One such instance is writing an event handler using the gwidgets package. Here’s an example, inspired by the example in “Programming Graphical User Interfaces in R“:
# Create a basic window with an output section require(gWidgets) options(guiToolkit = RGtk2) window = gwindow("File search", visible = FALSE) paned = gpanedgroup(cont = window) frmOutput = gframe("Output: ", cont=paned, horizontal = FALSE) txtOutput = gtext("", cont = frmOutput, expand = TRUE) size(txtOutput) = c(350, 200) # Create a button to open a .csv file btnImportFile = gfilebrowse(text = "Select a file ", quote = FALSE, type = "open", cont = container, filter = "*.csv") addHandlerChanged(btnImportFile, handler = function(h, ...){ svalue(txtOutput) = svalue(h$obj) })
OK, that was a fairly tame handler. All that we’re doing is placing the name of the file into the output area. What I’d like to do is use the output to show the names of columns contained in the file. Here’s what that would look like:
btnDescribeColumns = gbutton(text = "Describe columns", cont = container) addHandlerChanged(btnDescribeColumns, handler = function(h, ...){ filename = svalue(txtOutput) if( filename == ""){ svalue(txtOutput) = "File not found" } else { df = read.csv(filename, header = TRUE) svalue(txtOutput) = names(df) } })
That’s not too dreadful in isolation, but the event handler sits in the same function where I’ve defined another event handler. I’m now defining two functions inside a third function. For any rich interface, this will quickly get complex. Making changes to a specific handler means trolling through one giant, monolithic function. (Yes, yes, I could just search, but the aesthetics of my code are still poor.)
How about this instead? I code a function to create the widget and the handler is defined there.
AddBtnDescribeColumns = function(container, txtOutput){ require(gWidgets) btnDescribeColumns = gbutton(text = "Describe columns", cont = container) addHandlerChanged(btnDescribeColumns, handler = function(h, ...){ filename = svalue(txtOutput) if( filename == ""){ svalue(txtOutput) = "File not found" } else { df = read.csv(filename, header = TRUE) svalue(txtOutput) = names(df) } }) return (btnDescribeColumns) }
With a similar approach to the file open button (not shown, but available on Github), the main function for my dialog box is beginning to look fairly readable. Note that I must declare the output box before the buttons, so that it may be passed to the button constructors.
Main = function(WhichToolkit = "RGtk2"){ require(gWidgets) options(guiToolkit = WhichToolkit) window = gwindow("File search", visible = FALSE) paned = gpanedgroup(cont = window) group = ggroup(cont = paned, horizontal = FALSE) frmOutput = gframe("Output: ", cont=paned, horizontal = FALSE) txtOutput = gtext("", cont = frmOutput, expand = TRUE) size(txtOutput) = c(350, 200) AddBtnImportFile(group, txtOutput) AddBtnDescribeColumns(group, txtOutput) visible(window) = TRUE }
Another nice aspect of this construct is that I can declare multiple copies of the same button and place them wherever I like. They’ll all have the same event handler, which I only need to code once.
I’ve been working with gWidgets for a sum total of about three hours, so there may be something obvious I’ve missed. If so, please feel free to comment.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.