A simple Tk toplevel window with an OK button

A simple Tk toplevel window with an OK button

The primary goal of the tcltk R package is to use the Tk graphical user interface (GUI) toolkit with R. Here is a Tk window with an OK button that just destroys the window when it is clicked:

# Import the tcltk package
# Create a new Tk toplevel window assigned to win1
win1 <- tktoplevel()
# Create a Tk button whose function (command) is to destroy the window win1
butOK <- tkbutton(win1, text = "OK",
command = function() tkdestroy(win1))
# Place the button on the window, using the grid geometry manager

You should get the following window:

A Tk window

Click OK to close the window

Note that a Tk widget is not placed automatically inside its container1. You have to use one of the three Tk geometry managers (grid, pack or place, using respectively the tcltk functions tkgrid(), tkpack() and tkplace() in R)2. The grid manager is the most powerful and the most used one. It devides the container into a grid of rows and columns, arranges nicely the widgets in the grid, and then automatically resizes the container to best match its content (resulting here in a shrinked small window around the OK button).

Our Tk window and the way we manage it is indeed far from optimal. It can be ameliorated in four ways:

  1. It would be nice to give a title to our Tk window. This can be done using tktitle().

  2. We could use ttk instead of tk widgets by replacing tkbutton() by ttkbutton(). The ttk widgets are styled according to a theme that makes your GUI look better, more modern, and sometimes more native (on Windows, for instance).

  3. We should think about the size of the widgets and margins around them for a better layout. Our tiny OK button in the middle of a small window is not that nice. So, let’s improve this.

  4. It is nice to keep track of our Tk windows and widgets by assigning variables, like win1 or butOK here. However, these variables clutter our workspace. They also do not reflect the hierarchy. butOK is embedded in win1 at the Tk level. It makes clean up more difficult once the window is destroyed: you must get rid of both win1 and butOK to free memory from items that are not needed any more. Finally, if you have two windows, each with an OK button, you should of course not call them both butOK. Also, reassigning win1 before the first window is destroyed leads to problems3. With a more complex GUI, you easily end up with dozens of variables to keep track of your Tk widgets, and you may be at risk for clashes and hard-to-debug behaviour!

A better approach

Here is an improved version that implements all four points raised here above:

win2 <- tktoplevel()
# Give a title to the window
tktitle(win2) <- "Tk window"
# Create a Ttk button with a minimum size (note negative value) of six characters
# The command is a lot more complicated to make it survive a reassignation to win2
# (explanation is beyond the scope of this introductory tutorial)
# Assign inside win2 to avoid the inflation of variables in the global environment
# We assign to win2$env, instead of win2$, so that butOK is available to all
# shared versions of win1 (need further explanation!)
win2$env$butOK <- ttkbutton(win2, text = "OK", width = -6,
command = (function(win) { force(win); function() tkdestroy(win)})(win2))
# Place the button on the window, with large margins around it
tkgrid(win2$env$butOK, padx = 70, pady = 30)

The button has now much more space around it. On Windows, it looks native, but on Linux it is still looking old-fashioned…

A Tk window

Our code is now becoming quite complicated. However, the tcltk2 package would be helpful here.

The tcltk2 version

The tcltk2 R package4 provides more advanced Tk widgets, additional R-Tcl commands, more modern themes for Linux and Mac OS X and it simplifies the creation of GUI items. Here is how you could get the same window using tcltk2:

# You can configure the window at creation. If you specify a manager, it will be
# automatically used for each child widget created, unless specified otherwise
win3 <- tk2toplevel(title = "Tk2 window", manage = "grid", padx = 70, pady = 30)
# Create and place the same button (note the simpler syntax)
win3$butOK <- tk2button(text = "OK", width = -6, command = TkCmd_destroy(parent))

The default theme on Linux is clearlooks, which gives the next visual:

A Tk window

  1. A container is a widget that can contain other widgets. A toplevel window like win1 is a container, while butOK is not. 

  2. Never mix Tk managers inside the same container! 

  3. Rerun the previous code to recreate the window and the button. Do not close that window, but rerun win1 <- tktoplevel(). This will create a new window, as win1. Now, when you click the OK button on the first window, it is the second window that is destroyed! 

  4. Install the package from CRAN with the instruction install.packages("tcltk2")