GoSuda

Creazione di GUI con Tk utilizzando Go

By Yunjin Lee
views ...

In Python, le librerie GUI come Tkinter sono integrate di base. Recentemente, per consentire l'utilizzo di Tcl/Tk anche nel linguaggio Go, è stata sviluppata la CGo-Free, Cross Platform Tk library. Oggi esamineremo il suo utilizzo fondamentale.

Creazione di 'Hello, Tk'

Inizieremo con un semplice esempio di '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!"),
 9            tk.Command(func() {
10                tk.Destroy(tk.App)
11            })),
12        tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10),
13    )
14    tk.App.Wait()
15}

hello-tk 실행 결과


Esamineremo dettagliatamente il codice d'esempio sopracitato e il relativo risultato di esecuzione.

Coloro che hanno esperienza nell'utilizzo di Tk in Python comprenderanno la struttura per cui i widget vengono "packed" all'interno di una finestra o direttamente sotto di essa. A seconda della tipologia di widget, elementi quali le Label vengono inclusi al suo interno.

Ipadx e Ipady sono abbreviazioni di Internal padding e regolano il margine (padding) dei widget interni. In questo esempio, viene regolato il margine del bottone.

Questa libreria dispone di una struttura Window, e una variabile denominata App gestisce la finestra di livello superiore. Ciò è predefinito all'interno della libreria stessa. Di conseguenza, la funzione tk.App.Destroy(), che termina tk.App.Wait(), svolge la funzione di chiusura della finestra di livello superiore.

Adesso, esamineremo alcuni esempi presenti nella cartella _examples di GitLab.

Gestione dei file SVG

Segue un esempio che visualizza un file SVG in 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)))),
20        TExit(),
21        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
22    App.Center().Wait()
23}

go-tk-svg 실행 결과

Le modalità di gestione di SVG in questa libreria sono le seguenti:

  1. Il contenuto del file SVG viene letto come stringa (oppure incluso direttamente, come nell'esempio precedente).
  2. Questo contenuto viene passato alla funzione Data per essere convertito in una stringa contenente opzioni (opzione -data).
  3. I valori in byte convertiti vengono trasmessi alla funzione NewPhoto, che restituisce un puntatore alla struttura Img che rappresenta l'immagine Tcl/Tk.
  4. Passando attraverso la funzione Image, il puntatore alla struttura Img viene convertito in una stringa a cui è stata aggiunta l'opzione -Image.
  5. La conversione in una stringa contenente il valore RAW della struttura è necessaria ai fini della creazione del widget Label.

Anche i file ICO e PNG vengono gestiti con modalità analoghe.

Gestione dei file 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)))),
11        TExit(),
12        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
13    App.Center().Wait()
14}

go-tk-png 실행결과

Il processo di gestione dei file PNG è il seguente:

  1. Il file gopher.png incorporato viene convertito in un tipo stringa contenente opzioni.
  2. Viene convertito nel tipo *Img tramite la funzione NewPhoto.
  3. Dopo essere passato attraverso la funzione Image e convertito in una stringa RAW, viene generato come widget Label.

Anche i file ICO vengono gestiti nello stesso modo; l'unica discrepanza rispetto al formato SVG risiede nella modalità di elaborazione interna della funzione Data.

Analizziamo ora la natura di tale "stringa contenente opzioni":

1type rawOption string

La stringa contenente opzioni menzionata in precedenza è nient'altro che una stringa formattata.

1func (w *Window) optionString(_ *Window) string {
2    return w.String()
3}

Il metodo optionString è un metodo relativo a un puntatore Window e restituisce una stringa.

Infine, esamineremo brevemente la struttura interna della funzione 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}

Osservando il codice, si evince che nel caso dei file ICO o PNG è necessario un processo di encoding/decoding. Negli altri casi, senza alcuna conversione speciale, viene semplicemente aggiunta l'opzione -data alla stringa convertita in formato byte per indicare che è il risultato della funzione Data.

Caricamento di immagini tramite il widget Menu

Aggiungendo un widget Menu agli esempi di caricamento PNG e ICO precedentemente esercitati, è possibile creare un'applicazione che visualizzi le immagini necessarie.

Iniziamo esaminando un semplice esempio di widget Menu.

 1package main
 2
 3import (
 4    "fmt"
 5    . "modernc.org/tk9.0"
 6    "runtime"
 7)
 8
 9func main() {
10    menubar := Menu()
11
12    fileMenu := menubar.Menu()
13    fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N"))
14    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() }))
15    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) }))
16    fileMenu.AddCommand(Lbl("Save As..."), Underline(5))
17    fileMenu.AddSeparator()
18    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
19    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) }))
20    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
21
22    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
23    App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait()
24}

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

In questo esempio, abbiamo configurato la menu bar e la creazione del menu, l'evidenziazione dei caratteri, l'opzione Command, il binding delle scorciatoie da tastiera e le dimensioni iniziali della finestra dell'applicazione.

Adesso, modificheremo la funzione Command specificata con GetOpenFile per realizzare un programma che carichi e visualizzi le immagini.

La pianificazione della stesura del programma è la seguente:

  1. Limitare l'apertura ai soli file PNG e ICO.
  2. Gestire i file selezionati nella finestra di dialogo dei file.
  3. Implementare il widget per la visualizzazione delle immagini.

Di seguito è riportato il codice che riflette tale pianificazione:

 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()
15    for _, photo := range s {
16        formatCheck := strings.Split(photo, ".")
17        format := formatCheck[len(formatCheck)-1]
18        
19        if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
20            picFile, err := os.Open(photo)
21            if err != nil {
22                log.Println("Errore durante l'apertura della foto, l'errore è: ", err)
23            }
24
25            pictureRawData := make([]byte, 10000*10000)
26            picFile.Read(pictureRawData)
27
28            imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
29            Pack(imageLabel,
30                TExit(),
31                Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
32        }
33        picFile.Close()
34    }
35}
36
37func main() {
38    menubar := Menu()
39
40    fileMenu := menubar.Menu()
41    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen))
42    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
43    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
44    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
45    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
46
47    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
48    App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait()
49}

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

Il codice soprastante opera nelle seguenti modalità:

  1. Verifica l'estensione del file mediante la funzione di comparazione di stringhe del package strings.
  2. Apre, legge e successivamente chiude il file utilizzando il package os.
  3. L'immagine letta viene visualizzata come widget Label.

Riepilogo

In questo articolo, abbiamo trattato i seguenti argomenti utilizzando la libreria Tcl/Tk del linguaggio Go:

  1. Struttura base delle applicazioni GUI.
  2. Gestione di vari formati immagine quali SVG, PNG, ICO.
  3. Gestione del packing dei widget e del layout.
  4. Struttura di elaborazione dei dati immagine.
  5. Binding delle scorciatoie da tastiera e comandi dei widget.

L'utilizzo congiunto del linguaggio Go e Tcl/Tk consente la creazione di programmi GUI semplici ma allo stesso tempo pratici. Su questa base, si incoraggia a cimentarsi nello sviluppo di applicazioni GUI più complesse.