Creación de una GUI con Tk en Go
Python incorpora bibliotecas GUI como Tkinter de forma predeterminada. Recientemente, se ha desarrollado una biblioteca CGo-Free, Cross Platform Tk para permitir el uso de Tcl/Tk también en el lenguaje Go. Hoy, examinaremos su uso básico.
Creación de "Hello, Tk"
Comenzaremos con un sencillo ejemplo de "Hello, TK!".
1package main
2
3import tk "modernc.org/tk9.0"
4
5func main() {
6 tk.Pack(
7 tk.TButton(
8 tk.Txt("Hello, TK!"), // Texto del botón
9 tk.Command(func() { // Comando a ejecutar cuando se presiona el botón
10 tk.Destroy(tk.App) // Destruye la aplicación
11 })),
12 tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10), // Opciones de relleno
13 )
14 tk.App.Wait() // Espera a que la aplicación termine
15}

Examinaremos en detalle el código del ejemplo anterior y sus resultados de ejecución.
Aquellos familiarizados con el uso de Tk en Python comprenderán la estructura en la que los widgets se empaquetan dentro de una ventana o directamente debajo de ella. Dependiendo del tipo de widget, se incluyen elementos como etiquetas.
Ipadx e Ipady son abreviaturas de Internal padding, que ajustan el margen de los widgets internos. En este ejemplo, se ajusta el margen del botón.
Esta biblioteca contiene la estructura Window, y la variable App gestiona la ventana de nivel superior. Esta se encuentra predefinida dentro de la biblioteca. Por lo tanto, la función tk.App.Destroy(), que cierra tk.App.Wait(), se encarga de cerrar la ventana de nivel superior.
Ahora, examinaremos algunos ejemplos de la carpeta _examples de GitLab.
Procesamiento de archivos SVG
A continuación, se presenta un ejemplo que muestra un archivo SVG en un widget Label.
1package main
2
3import . "modernc.org/tk9.0"
4
5// https://en.wikipedia.org/wiki/SVG
6const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
7<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
8<svg width="391" height="391" viewBox="-70.5 -70.5 391 391" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9<rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
10<g opacity="0.8">
11 <rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
12 <circle cx="125" cy="125" r="75" fill="orange" />
13 <polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
14 <line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
15</g>
16</svg>`
17
18func main() {
19 Pack(Label(Image(NewPhoto(Data(svg)))), // Empaqueta una etiqueta con la imagen SVG
20 TExit(), // Widget de salida
21 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m")) // Opciones de relleno
22 App.Center().Wait() // Centra la aplicación y espera a que termine
23}

El método para procesar SVG en esta biblioteca es el siguiente:
- Se lee el contenido del archivo SVG como una cadena (o se incluye directamente como en el ejemplo anterior).
- Este contenido se pasa a la función Data para convertirlo en una cadena con opciones (opción -data).
- El valor de byte convertido se pasa a la función NewPhoto, que devuelve un puntero a la estructura Img que representa la imagen Tcl/Tk.
- Al pasar por la función Image, el puntero a la estructura Img se convierte en una cadena con la opción -Image añadida.
- La razón por la que se convierte en una cadena que contiene el valor RAW de la estructura es para la creación del widget Label.
Los archivos ICO y PNG se procesan de manera similar.
Procesamiento de archivos PNG
1package main
2
3import _ "embed"
4import . "modernc.org/tk9.0"
5
6//go:embed gopher.png
7var gopher []byte
8
9func main() {
10 Pack(Label(Image(NewPhoto(Data(gopher)))), // Empaqueta una etiqueta con la imagen PNG
11 TExit(), // Widget de salida
12 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m")) // Opciones de relleno
13 App.Center().Wait() // Centra la aplicación y espera a que termine
14}

El proceso de procesamiento de archivos PNG es el siguiente:
- El
gopher.pngincrustado se convierte en un tipo de cadena que contiene opciones. - Se convierte al tipo
*Imga través de la funciónNewPhoto. - Después de pasar por la función
Imagey convertirse en una cadena RAW, se crea como un widget Label.
Los archivos ICO se procesan de la misma manera, y la única diferencia con el formato SVG es el método de procesamiento interno de la función Data.
Examinemos ahora la naturaleza de la "cadena con opciones":
1type rawOption string
La cadena con opciones mencionada anteriormente es simplemente una cadena formateada.
1func (w *Window) optionString(_ *Window) string {
2 return w.String()
3}
El método optionString es un método para un puntero de Window, que devuelve una cadena.
Finalmente, examinaremos brevemente la estructura interna de la función Data:
1func Data(val any) Opt {
2 switch x := val.(type) {
3 case []byte:
4 switch {
5 case bytes.HasPrefix(x, pngSig):
6 // ok
7 case bytes.HasPrefix(x, icoSig):
8 b := bytes.NewBuffer(x)
9 img, err := ico.Decode(bytes.NewReader(x))
10 if err != nil {
11 fail(err)
12 return rawOption("")
13 }
14
15 b.Reset()
16 if err := png.Encode(b, img); err != nil {
17 fail(err)
18 return rawOption("")
19 }
20
21 val = b.Bytes()
22 }
23 }
24 return rawOption(fmt.Sprintf(`-data %s`, optionString(val)))
25}
El código muestra que los archivos ICO o PNG requieren un proceso de codificación/decodificación. En otros casos, sin una conversión especial, solo se añade la opción -data a la cadena convertida a tipo byte para indicar que es el resultado de la función Data.
Carga de imágenes con el widget de menú
Si añadimos un widget de menú a los ejemplos anteriores de carga de PNG e ICO, podemos crear una aplicación que muestre las imágenes necesarias.
Primero, examinaremos un ejemplo sencillo de widget de menú.
1package main
2
3import (
4 "fmt"
5 . "modernc.org/tk9.0"
6 "runtime"
7)
8
9func main() {
10 menubar := Menu() // Crea una barra de menú
11
12 fileMenu := menubar.Menu() // Crea un menú de archivo
13 fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N")) // Añade un comando "New"
14 fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() })) // Añade un comando "Open..."
15 Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) })) // Vincula Ctrl+O para invocar el segundo comando del menú de archivo
16 fileMenu.AddCommand(Lbl("Save As..."), Underline(5)) // Añade un comando "Save As..."
17 fileMenu.AddSeparator() // Añade un separador
18 fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler()) // Añade un comando "Exit"
19 Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) })) // Vincula Ctrl+Q para invocar el quinto comando del menú de archivo
20 menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu)) // Añade el menú de archivo a la barra de menú
21
22 App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS)) // Establece el título de la ventana
23 App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait() // Configura la aplicación y espera
24}

En este ejemplo, hemos creado una barra de menú y un menú, resaltado texto, utilizado la opción Command, vinculado atajos de teclado y establecido el tamaño inicial de la ventana de la aplicación.
Ahora modificaremos la función Command designada como GetOpenFile para crear un programa que cargue y muestre imágenes.
El plan para escribir el programa es el siguiente:
- Restringir la apertura solo a archivos PNG e ICO.
- Procesar el archivo seleccionado en el diálogo de archivo.
- Implementar un widget para mostrar la imagen.
El siguiente código refleja este plan:
1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "runtime"
8 "strings"
9
10 . "modernc.org/tk9.0"
11)
12
13func handleFileOpen() {
14 s := GetOpenFile() // Obtiene el archivo abierto
15 for _, photo := range s {
16 formatCheck := strings.Split(photo, ".") // Divide el nombre del archivo para obtener la extensión
17 format := formatCheck[len(formatCheck)-1] // Obtiene la extensión del archivo
18
19 if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) { // Comprueba si el formato es PNG o ICO
20 picFile, err := os.Open(photo) // Abre el archivo
21 if err != nil {
22 log.Println("Error while opening photo, error is: ", err) // Registra un error si no se puede abrir el archivo
23 }
24
25 pictureRawData := make([]byte, 10000*10000) // Crea un slice de bytes para los datos de la imagen
26 picFile.Read(pictureRawData) // Lee los datos del archivo en el slice
27
28 imageLabel := Label(Image(NewPhoto(Data(pictureRawData)))) // Crea una etiqueta de imagen
29 Pack(imageLabel, // Empaqueta la etiqueta de imagen
30 TExit(), // Widget de salida
31 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m")) // Opciones de relleno
32 }
33 picFile.Close() // Cierra el archivo
34 }
35}
36
37func main() {
38 menubar := Menu() // Crea una barra de menú
39
40 fileMenu := menubar.Menu() // Crea un menú de archivo
41 fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen)) // Añade un comando "Open..." con un manejador
42 Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) })) // Vincula Ctrl+O para invocar el primer comando del menú de archivo
43 fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler()) // Añade un comando "Exit"
44 Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) })) // Vincula Ctrl+Q para invocar el segundo comando del menú de archivo
45 menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu)) // Añade el menú de archivo a la barra de menú
46
47 App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS)) // Establece el título de la ventana
48 App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait() // Configura la aplicación y espera
49}

El código anterior funciona de la siguiente manera:
- La función de comparación de cadenas del paquete strings se utiliza para verificar la extensión del archivo.
- El archivo se abre, se lee y se cierra utilizando el paquete os.
- La imagen leída se muestra como un widget Label.
Resumen
En este artículo, hemos utilizado la biblioteca Tcl/Tk del lenguaje Go para cubrir los siguientes temas:
- Estructura básica de una aplicación GUI.
- Procesamiento de varios formatos de imagen como SVG, PNG e ICO.
- Empaquetado de widgets y gestión de diseño.
- Estructura de procesamiento de datos de imagen.
- Vinculación de atajos de teclado y comandos de widgets.
La combinación de Go y Tcl/Tk permite crear programas GUI sencillos pero prácticos. Con esto como base, le animamos a intentar desarrollar aplicaciones GUI más complejas.