Crearea unei interfețe grafice de utilizator (GUI) în Go cu Tk
Python include în mod implicit biblioteci GUI precum Tkinter. Recent, a fost dezvoltată o bibliotecă Tk multiplatformă, CGo-Free pentru a permite utilizarea Tcl/Tk și în limbajul Go. Astăzi vom examina utilizarea sa fundamentală.
Crearea "Hello, Tk"
Să începem cu un exemplu simplu '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}

Să examinăm în detaliu codul exemplu de mai sus și rezultatul execuției.
Cei familiarizați cu utilizarea Tk în Python vor înțelege structura în care widget-urile sunt împachetate într-o fereastră sau direct sub o fereastră. În funcție de tipul widget-ului, etichete și alte elemente sunt incluse în acesta.
Ipadx și Ipady sunt abrevieri pentru Internal padding și ajustează marginile interne ale widget-urilor. În acest exemplu, marginile butonului sunt ajustate.
Această bibliotecă conține o structură Window, iar variabila App gestionează fereastra de nivel superior. Aceasta este predefinită în interiorul bibliotecii. Prin urmare, funcția tk.App.Destroy(), care încheie tk.App.Wait(), este responsabilă pentru închiderea ferestrei de nivel superior.
Acum, să examinăm câteva exemple din folderul _examples al GitLab.
Procesarea fișierelor SVG
Următorul exemplu arată afișarea unui fișier SVG într-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}

Iată cum această bibliotecă procesează SVG-urile:
- Conținutul fișierului SVG este citit ca un șir (sau inclus direct, ca în exemplul de mai sus).
- Acest conținut este transmis funcției Data pentru a fi convertit într-un șir cu opțiuni (opțiunea -data).
- Valoarea în octeți convertită este transmisă funcției NewPhoto, care returnează un pointer la o structură Img ce reprezintă o imagine Tcl/Tk.
- Trecând prin funcția Image, pointerul la structura Img este convertit într-un șir cu opțiunea -Image adăugată.
- Motivul pentru conversia într-un șir care conține valoarea RAW a structurii este crearea widget-ului Label.
Fișierele ICO și PNG sunt procesate într-un mod similar.
Procesarea fișierelor 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}

Procesul de tratare a fișierelor PNG este următorul:
- Fișierul
gopher.pngîncorporat este convertit într-un șir de tip opțional. - Este convertit la tipul
*Imgprin funcțiaNewPhoto. - După ce este convertit într-un șir RAW prin funcția
Image, este creat ca un widget de etichetă.
Fișierele ICO sunt procesate în același mod, iar diferența față de formatul SVG constă doar în modul în care funcția Data gestionează intern.
Să examinăm acum natura "șirului cu opțiuni":
1type rawOption string
Șirul cu opțiuni menționat anterior este pur și simplu un șir formatat.
1func (w *Window) optionString(_ *Window) string {
2 return w.String()
3}
Metoda optionString este o metodă pentru pointerul Window, care returnează un șir.
În cele din urmă, să aruncăm o privire rapidă la structura internă a funcției 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}
Din cod, se observă că fișierele ICO sau PNG necesită un proces de codare/decodare. În alte cazuri, se adaugă doar opțiunea -data la șirul convertit în format byte, fără nicio conversie specială, pentru a indica faptul că este rezultatul funcției Data.
Încărcarea imaginilor cu widget-ul Menu
Adăugând un widget Menu la exemplele anterioare de încărcare PNG și ICO, putem crea o aplicație care afișează imaginile necesare.
Să examinăm mai întâi un exemplu simplu de 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}

În acest exemplu, am configurat o bară de meniu și un meniu, am evidențiat textul, am utilizat opțiunea Command, am legat taste rapide și am setat dimensiunea inițială a ferestrei aplicației.
Acum, vom modifica funcția Command desemnată cu GetOpenFile pentru a crea un program care încarcă și afișează imagini.
Planul de scriere a programului este următorul:
- Restricționarea deschiderii doar a fișierelor PNG și ICO
- Procesarea fișierului selectat din dialogul de fișiere
- Implementarea unui widget pentru afișarea imaginii
Iată codul care reflectă aceste planuri:
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("Error while opening photo, error is: ", 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}

Codul de mai sus funcționează după cum urmează:
- Verifică extensia fișierului folosind funcția de comparare a șirurilor din pachetul strings.
- Deschide, citește și închide fișierul folosind pachetul os.
- Imaginea citită este afișată ca un widget de etichetă.
Rezumat
În acest articol, am utilizat biblioteca Tcl/Tk a limbajului Go pentru a aborda următoarele aspecte:
- Structura de bază a aplicațiilor GUI
- Procesarea diferitelor formate de imagine, cum ar fi SVG, PNG și ICO
- Împachetarea widget-urilor și gestionarea aspectului
- Structura de procesare a datelor imaginii
- Legarea tastelor rapide și a comenzilor widget-urilor
Utilizarea limbajului Go împreună cu Tcl/Tk permite crearea de programe GUI simple, dar practice. Pe baza acestora, vă încurajăm să abordați dezvoltarea de aplicații GUI mai complexe.