GoSuda

Creación de una GUI con Tk en Go

By Yunjin Lee
views ...

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}

hello-tk 실행 결과


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}

go-tk-svg 실행 결과

El método para procesar SVG en esta biblioteca es el siguiente:

  1. Se lee el contenido del archivo SVG como una cadena (o se incluye directamente como en el ejemplo anterior).
  2. Este contenido se pasa a la función Data para convertirlo en una cadena con opciones (opción -data).
  3. 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.
  4. 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.
  5. 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}

go-tk-png 실행결과

El proceso de procesamiento de archivos PNG es el siguiente:

  1. El gopher.png incrustado se convierte en un tipo de cadena que contiene opciones.
  2. Se convierte al tipo *Img a través de la función NewPhoto.
  3. Después de pasar por la función Image y 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}

go-tk-메뉴바-실행결과

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:

  1. Restringir la apertura solo a archivos PNG e ICO.
  2. Procesar el archivo seleccionado en el diálogo de archivo.
  3. 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}

go-tk-이미지-불러오기-실행결과

El código anterior funciona de la siguiente manera:

  1. La función de comparación de cadenas del paquete strings se utiliza para verificar la extensión del archivo.
  2. El archivo se abre, se lee y se cierra utilizando el paquete os.
  3. 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:

  1. Estructura básica de una aplicación GUI.
  2. Procesamiento de varios formatos de imagen como SVG, PNG e ICO.
  3. Empaquetado de widgets y gestión de diseño.
  4. Estructura de procesamiento de datos de imagen.
  5. 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.