---
id: 974f36e12dfcece861bfda1af9303980
author: Yunjin Lee
title: 'Implementarea unei interfețe grafice Tcl/Tk în Go fără CGo (modernc.org/tk9.0): împachetarea widget-urilor și randarea imaginilor'
description: Implementați crearea de ferestre în Go, împachetarea widget-urilor de tip buton/bară de meniu și randarea imaginilor SVG/PNG/ICO folosind biblioteca multiplatformă modernc.org/tk9.0, fără dependențe CGo.
language: ro
date: 2024-10-22T11:51:27.614782351Z
path: /blog/posts/creating-a-gui-with-tk-in-go-z004dd008
category: gui
lang_canonical:
    ko: https://blog.naver.com/bugaku/223629101405
---

Python include în mod implicit biblioteci GUI precum Tkinter.
Recent, a fost dezvoltată și [CGo-Free, Cross Platform Tk library](https://pkg.go.dev/modernc.org/tk9.0), care permite utilizarea Tcl/Tk și în limbajul Go. Astăzi vom analiza utilizarea sa de bază.

## Crearea unui Hello, Tk

Vom începe cu un exemplu simplu 'Hello, TK!'.

```go
package main

import tk "modernc.org/tk9.0"

func main() {
    tk.Pack(
        tk.TButton(
            tk.Txt("Hello, TK!"),
            tk.Command(func() {
                tk.Destroy(tk.App)
            })),
        tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10),
    )
    tk.App.Wait()
}
```

![Rezultatul executării hello-tk](/assets/images/go-tk-helloworld-svg-etc/go-tk-hello.png)

---

Vom analiza mai detaliat exemplul de cod de mai sus și rezultatul său de execuție.

Dacă ați utilizat anterior Tk în Python, veți înțelege structura în care widgeturile sunt împachetate într-o fereastră sau direct în fereastra de sus. În funcție de tipul widgetului, etichetele și alte elemente sunt incluse în acesta.

_Ipadx și Ipady sunt abrevieri ale Internal padding (padding intern), care ajustează spațiile interioare ale widgeturilor. În acest exemplu, se ajustează spațiul interior al butonului._

Această bibliotecă dispune de structura Window, iar variabila App gestionează fereastra de nivel superior. Aceasta este definită în prealabil în interiorul bibliotecii. Astfel, funcția tk.App.Destroy(), care încheie tk.App.Wait(), are rolul de a închide fereastra de nivel superior.

Acum vom analiza câteva exemple din folderul \_examples de pe GitLab.

## Procesarea fișierelor SVG

Următorul exemplu afișează un fișier SVG într-un widget Label.

```go
package main

import . "modernc.org/tk9.0"

// https://en.wikipedia.org/wiki/SVG
const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<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">
<rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
<g opacity="0.8">
    <rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
    <circle cx="125" cy="125" r="75" fill="orange" />
    <polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
    <line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
</g>
</svg>`

func main() {
    Pack(Label(Image(NewPhoto(Data(svg)))),
        TExit(),
        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
    App.Center().Wait()
}
```

![Rezultatul executării go-tk-svg](/assets/images/go-tk-helloworld-svg-etc/go-tk-svg.png)

Modul în care această bibliotecă procesează SVG este următorul:

1. Citiți conținutul fișierul SVG ca șir de caractere (sau includeți-l direct, ca în exemplul de mai sus).
2. Transmiteți acest conținut funcției Data, pentru a-l converti într-un șir de caractere care include opțiuni (opțiunea -data).
3. Octeții convertiți sunt transmiși funcției NewPhoto, care returnează un pointer către structura Img, reprezentând o imagine Tcl/Tk.
4. Prin funcția Image, pointerul către structura Img este convertit într-un șir de caractere la care se adaugă opțiunea -Image.
5. Valoarea RAW a structurii este convertită într-un șir de caractere, motivul fiind crearea widgetului Label.

Fișierele ICO și PNG sunt procesate într-un mod similar.

## Procesarea fișierelor PNG

```go
package main

import _ "embed"
import . "modernc.org/tk9.0"

//go:embed gopher.png
var gopher []byte

func main() {
    Pack(Label(Image(NewPhoto(Data(gopher)))),
        TExit(),
        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
    App.Center().Wait()
}
```

![Rezultatul executării go-tk-png](/assets/images/go-tk-helloworld-svg-etc/go-tk-png.png)

Procesul de tratare a fișierelor PNG este următorul:

1. Convertiți fișierul gopher.png inclus (embedded) într-un tip de șir de caractere cu opțiuni.
2. Convertiți-l în tipul *Img prin funcția NewPhoto.
3. Prin funcția Image, se obține un șir de caractere RAW, după care se creează widgetul Label.

Fișierele ICO sunt procesate în aceeași manieră, iar diferența față de formatul SVG constă doar în modul de procesare din interiorul funcției Data.

Să analizăm acum natura „șirului de caractere cu opțiuni”:

```go
type rawOption string
```

Șirul de caractere cu opțiuni menționat anterior este, pur și simplu, un șir formatat.

```go
func (w *Window) optionString(_ *Window) string {
    return w.String()
}
```

Metoda optionString este o metodă a pointerului Window și returnează un șir de caractere.

În final, să analizăm pe scurt structura internă a funcției Data:

```go
func Data(val any) Opt {
    switch x := val.(type) {
    case []byte:
        switch {
        case bytes.HasPrefix(x, pngSig):
            // ok
        case bytes.HasPrefix(x, icoSig):
            b := bytes.NewBuffer(x)
            img, err := ico.Decode(bytes.NewReader(x))
            if err != nil {
                fail(err)
                return rawOption("")
            }

            b.Reset()
            if err := png.Encode(b, img); err != nil {
                fail(err)
                return rawOption("")
            }

            val = b.Bytes()
        }
    }
    return rawOption(fmt.Sprintf(`-data %s`, optionString(val)))
}
```

Din cod reiese că fișierele ICO sau PNG necesită un proces de codificare/decodificare.
În celelalte cazuri, nu se efectuează nicio transformare specială; se adaugă doar opțiunea -data la șirul convertit în octeți, pentru a indica faptul că acesta este rezultatul funcției Data.

### Încărcarea imaginilor cu ajutorul unui widget de meniu

Adăugând un widget de meniu la exemplul de încărcare a fișierelor PNG și ICO exersat anterior, se poate crea o aplicație care afișează imaginea necesară.

Mai întâi, să analizăm un exemplu simplu de widget de meniu.

```go
package main

import (
    "fmt"
    . "modernc.org/tk9.0"
    "runtime"
)

func main() {
    menubar := Menu()

    fileMenu := menubar.Menu()
    fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N"))
    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() }))
    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) }))
    fileMenu.AddCommand(Lbl("Save As..."), Underline(5))
    fileMenu.AddSeparator()
    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) }))
    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))

    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
    App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait()
}
```

![Rezultatul executării go-tk-meniu-bară](/assets/images/go-tk-helloworld-svg-etc/go-tk-menu-bar.png)

În acest exemplu, am exersat bara de meniu și crearea meniurilor, evidențierea literelor, opțiunea Command, legarea tastelor de scurtătură și stabilirea dimensiunii inițiale a ferestrei aplicației.

Acum vom modifica funcția Command atribuită lui GetOpenFile, pentru a crea un program care încarcă și afișează imagini.

Planul de realizare a programului este următorul:

1. Restricționarea deschiderii doar a fișierelor PNG și ICO
2. Procesarea fișierului selectat din dialogul de fișiere
3. Implementarea unui widget pentru afișarea imaginii

Următorul cod reflectă acest plan:

```go
package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "strings"

    . "modernc.org/tk9.0"
)

func handleFileOpen() {
    s := GetOpenFile()
    for _, photo := range s {
        formatCheck := strings.Split(photo, ".")
        format := formatCheck[len(formatCheck)-1]

        if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
            picFile, err := os.Open(photo)
            if err != nil {
                log.Println("Error while opening photo, error is: ", err)
            }

            pictureRawData := make([]byte, 10000*10000)
            picFile.Read(pictureRawData)

            imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
            Pack(imageLabel,
                TExit(),
                Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
        }
        picFile.Close()
    }
}

func main() {
    menubar := Menu()

    fileMenu := menubar.Menu()
    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen))
    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))

    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
    App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait()
}
```

![Rezultatul executării go-tk-încărcare-imagine](/assets/images/go-tk-helloworld-svg-etc/go-tk-image-open.png)

Codul de mai sus funcționează în felul următor:

1. Se verifică extensia fișierului prin funcția de comparare a șirurilor din pachetul strings
2. Se deschide, se citește și se închide fișierul folosind pachetul os
3. Imaginea citită este afișată într-un widget Label

## Concluzie

În acest articol, am tratat următoarele subiecte folosind biblioteca Tcl/Tk din limbajul Go:

1. Structura de bază a unei aplicații GUI
2. Procesarea diverselor formate de imagini, precum SVG, PNG și ICO
3. Împachetarea widgeturilor și gestionarea layout-ului
4. Structura de procesare a datelor imaginilor
5. Legarea tastelor de scurtătură și comenzile widgeturilor

Utilizând împreună limbajul Go și Tcl/Tk, se pot crea programe GUI simple, dar practice. Vă recomand să încercați dezvoltarea unor aplicații GUI mai complexe pornind de la aceste baze.
