Creare interfacce grafiche per R con TCL/Tk

Il TCL è un linguaggio interpretato multipiattaforma molto utilizzato per creare applicazioni dotate di GUI (Graphical User Interfaces). Il Tk è invece un’estensione del TCL, che fornisce comandi per la creazione di interfacce grafiche. All’interno di diversi linguaggi interpretati, come R e Python, sono implementate delle librerie che richiamano direttamente le funzioni del TCL/Tk. In R, questi comandi sono resi disponibili nella libreria tcltk, già presente nella distribuzione base del software: è quindi possibile interfacciarsi con il TCL/Tk utilizzando delle apposite funzioni R.

Rispetto ad altri linguaggi, il TCL/Tk è molto semplice da utilizzare e si impara rapidamente, ma il risultato grafico ha uno stile un po’ retrò e non è particolarmente accattivante. Si tratta probabilmente del principale motivo per cui queste librerie vengono via via abbandonate in favore di altre soluzioni. Ritengo però che possano ancora tornare molto utili per agevolare gli utenti nell’uso di procedure troppo macchinose, dando la possibilità di manipolare tramite una interfaccia grafica gli argomenti di funzioni un po’ complesse.

Purtroppo, però, la libreria tcltk è mal documentata. In rete si trovano alcuni articoli, ma in generale questi non forniscono informazioni dettagliate. Io ho superato quest’ostacolo rivolgendomi direttamente alla documentazione ufficiale del Tcl/Tk, cercando poi a intuito le corrispettive implementazioni in R. Trovo molto utile anche un dettagliatissimo manuale della libreria Tkinter di Python.

In questo post vedremo come costruire una semplice interfaccia utente con tcltk. Si tratta di nozioni che ho appreso in completa autonomia, per cui non escludo che diversi passaggi potrebbero essere ottimizzati se non svolti con una logica completamente diversa: segnalazioni in merito saranno ben accette!

Prendiamo come esempio un caso molto semplice: vogliamo costruire una GUI che dia all’utente la possibilità di scegliere fra due alternative. L’interfaccia dovrà quindi contenere un radiobutton a due opzioni, e un pulsante che, una volta effettuata la scelta, invii la risposta.

Immaginiamo di voler chiedere all’utente se secondo lui il bicchiere sia mezzo vuoto oppure mezzo pieno. Questa è l’interfaccia che dovremo costruire:

R-GUI example

Come detto, non dobbiamo scaricare nulla perché la libreria è già pesente nella distribuzione base di R. È però necessario attivarla:

library(tcltk)

In linea generale, quando costruisco un’interfaccia grafica con tcltk, sono solito suddividere il lavoro in quattro fasi:

  1. Costruzione della finestra principale
  2. Definizione delle variabili Tk
  3. Definizione della funzione per l’invio dei dati
  4. Costruzione della GUI

Creare la finestra principale è la cosa più semplice:

tt <- tktoplevel()
tktitle(tt) <- "Il bicchiere è..."
funEnv <- new.env()
&#91;/code&#93;

<p style="text-align: justify;">Il comando che genera la finestra nella quale verranno incastonati tutti i <a href="http://it.wikipedia.org/wiki/Widget" target="_blank" title="Widget su Wikipedia">widget</a> &egrave; <b>tktoplevel</b>. &Egrave; sufficiente lanciare questo comando, senza alcun argomento specificato, per generare la finestra. Il comando <b>tktitle</b> assegna invece un titolo alla finestra, che comparir&agrave; nella barra superiore.</p>

<p style="text-align: justify;">
Il comando <b>new.env</b> non ha nulla a che vedere con tcltk. Esso crea un nuovo environment, nel quale prossimamente verranno definite tutte le variabili che conterranno le scelte operate dall'utente. Per la verit&agrave;, in questo esempio avremmo anche potuto risparmiarci la creazione di un nuovo environment. Stiamo infatti lavorando in un environment ben noto, che &egrave; quello globale (<i>.GlobalEnv</i>), per cui potremmo utilizzare questo stesso ambiente come contenitore.<br/>Questa per&ograve; non &egrave; la situazione pi&ugrave; comune. Difatti, spesso si preferisce scrivere delle vere e proprie funzioni per la costruzione dell'interfaccia; in questi casi, l'environment nel quale si andr&agrave; a lavorare non sar&agrave; pi&ugrave; quello globale, bens&igrave; quello interno alla funzione. Creare un environment dedicato garantir&agrave; quindi la &quot;portabilit&agrave;&quot; del codice.</p>

<p style="text-align: justify;">A questo punto, veniamo a quello che &egrave; il cuore dell'interfaccia: la definizione delle variabili Tk.</p>

[code language="R"]
titleLab <- tklabel(tt, text="Scegli un'opzione:")
glassVar <- tclVar(1)
glassTk1 <- tkradiobutton(tt, text="Mezzo vuoto", variable=glassVar, value=1, foreground="#311598")
glassTk2 <- tkradiobutton(tt, text="Mezzo pieno", variable=glassVar, value=2, foreground="#311598")
&#91;/code&#93;

<p style="text-align: justify;">I comandi <b>tklabel</b> e <b>tkradiobutton</b> creano rispettivamente un widget di tipo &quot;etichetta&quot; e un widget di tipo &quot;radiobutton&quot;. Questi widget andranno successivamente incastonati nell'interfaccia. Le variabili risultanti (titleLab, glassTk1 e glassTk2) contengono delle specifiche che R passer&agrave; al TCL per la costruzione dei widget.</p>

<p style="text-align: justify;">Per quanto riguarda i radiobutton, si noti che abbiamo costruito due variabili Tk: una per la prima opzione di risposta e una per la seconda opzione. Gli argomenti della funzione tkradiobutton specificano il testo da associare all'opzione (<i>text</i>), la variabile che conterr&agrave; la scelta dell'utente (<i>variable</i>), il valore che questa variabile dovr&agrave; assumere nel caso tale opzione venisse scelta (<i>value</i>), e il colore del testo associato dell'opzione (<i>foreground</i>). &Egrave; possibile specificare anche altri argomenti: per ulteriori dettagli si rimanda alla documentazione.</p>

<p style="text-align: justify;">Fondamentale &egrave; il comando <b>tclVar</b>. Questo crea una variabile R che funge da contenitore per quella che in realt&agrave; &egrave; una variabile TCL (non dimentichiamo che ci stiamo interfacciando con TCL attraverso R). Nell'esempio sopra, per default questa variabile avr&agrave; valore 1.<br/>Quando l'utente selezioner&agrave; la prima opzione, &quot;Mezzo vuoto&quot;, la variabile glassVar assumer&agrave; il valore 1 (come specificato nella definizione del widget glassTk1). Allo stesso modo, quando l'utente selezioner&agrave; la seconda opzione, &quot;Mezzo pieno&quot;, la variabile glassVar assumer&agrave; il valore 2 (come specificato nella definizione del widget glassTk2).</p>

<p style="text-align: justify;">L'opposto della funzione tclVar &egrave; <b>tclvalue</b>: data una variabile definita attraverso il comando tclVar, tclvalue consente di recuperare il valore della variabile. Infatti, nel nostro esempio, il comando <i>tclvalue(glassVar)</i> restituir&agrave; &quot;1&quot;, che &egrave; il valore della variabile.</p>

<p style="text-align: justify;">Ora abbiamo bisogno di creare un pulsante che, una volta premuto, assegni la scelta dell'utente a una variabile R. Sono due le cose da fare: 1) costruire il pulsante utilizzano la funzione <b>tkbutton</b>, 2) costruire una funzione da associare all'evento &quot;pressione del pulsante&quot;, che si occupa dell'assegnazione della scelta dell'utente a una nuova variabile R.</p>

<p style="text-align: justify;">Questo &egrave; il codice che ho utilizzato io:</p>

[code language="R"]
select <- function() {
glassVal <- tclvalue(glassVar)
assign("glassVal", value=glassVal, pos=funEnv)
tkdestroy(tt)
}
selectBut <- tkbutton(tt, text="Continua", command=select) 
&#91;/code&#93;

<p style="text-align: justify;">
Alla funzione <b>select</b> - cos&igrave; l'ho chiamata - non deve essere passato alcun argomento, ma questa deve poter recuperare tutto ci&ograve; che le serve tramite <u>variabili globali</u>. La funzione, infatti, necessita dei valori contenuti negli oggetti glassVar e funEnv per lavorare: questi non le vengono passati, ma lei &egrave; comunque in grado di recuperarseli da sola perch&egrave; &egrave; stata definita nello stesso environment in cui si sta processando il codice per la creazione dell'interfaccia. In questo environment, che nel nostro caso &egrave; <i>.GlobalEnv</i>, sono disponibili tutti gli oggetti R di cui la select ha bisogno.</p>

<p style="text-align: justify;">Il codice interno alla funzione pu&ograve; apparire un po' complesso. Tra poco lo vedremo nel dettaglio; in ogni caso, a questo punto siamo pronti per comporre la GUI incastonando i widget nella finestra toplevel:</p>

[code language="R"]
tkgrid(titleLab)
tkgrid(glassTk1, glassTk2)
tkgrid(tklabel(tt, text=""))
tkgrid(selectBut)
tkgrid(tklabel(tt, text=""))
tkfocus(tt)
tkwait.window(tt) 

Il comando deputato alla composizione della GUI è tkgrid. In realtà ce n'è anche un altro, tkpack, ma tkgrid è più flessibile e consente di controllare meglio la disposizione dei widget. Si noti la creazione di alcuni widget di tipo "Etichetta vuota", che servono a lasciare delle righe vuote.

La funzione tkfocus assicura che alla finestra toplevel venga assegnato il focus, mentre tkwait.window blocca completamente l'esecuzione dello script R in attesa di un contrordine. Quando il pulsante "Continua" viene premuto:

  1. il valore TCL viene estratto dalla variabile glassVar e salvato nella variabile glassVal;
  2. il valore ora contenuto nella variabile glassVal viene assegnato a un'omonima variabile nell'environment funEnv;
  3. l'interfaccia viene distrutta da tkdestroy.

A questo punto, nell'environment funEnv è stata registrata la variabile che codifica la scelta dell'utente. Per visualizzare il risultato dobbiamo richiamare il valore selezionato in questo modo:

funEnv$glassVal

Al valore "1" corrisponderà la scelta "Mezzo vuoto", mentre al valore "2" la scelta "Mezzo pieno".

Utilizzando la stessa logica si possono costruire interfacce ben più complesse, che fanno uso non solo di radiobutton ma anche di caselle di testo, checkbutton, boxlist, e così via: l'help ?TkWidgets fornisce una buona panoramica.


Riferimenti sul web

Dalgaad P. (2001a). The R-Tcl/Tk interface. Proceedings of the 2nd International Workshop on Distributed Statistical Computing. March 15-17, Vienna, Austria.

Dalgaad P. (2001b). A Primer on the R-Tcl/Tk Package. R News, 1/3, pp. 27-31.

Dalgaad P. (2002). Changes to the R-Tcl/Tk package. R News, 2/3, pp. 25-37.

Print Friendly

Lascia un Commento