---
id: 974f36e12dfcece861bfda1af9303980
author: Yunjin Lee
title: 'CGo nélküli Go Tcl/Tk (modernc.org/tk9.0) GUI megvalósítása: widgetek elrendezése és képmegjelenítés'
description: A modernc.org/tk9.0 könyvtár segítségével, CGo-függőség nélkül, platformfüggetlen módon valósítjuk meg Go nyelven az ablakok létrehozását, a gomb- és menüsáv-vezérlők elrendezését (packing), valamint az SVG, PNG és ICO képek megjelenítését.
language: hu
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
---

A Pythonban a Tkinterhez hasonló GUI-könyvtárak alapértelmezés szerint beépítve megtalálhatók.
A közelmúltban a Go nyelvhez is elkészült a [CGo-Free, Cross Platform Tk library](https://pkg.go.dev/modernc.org/tk9.0), amely lehetővé teszi a Tcl/Tk használatát. Ma ennek alapvető használatát tekintjük át.

## Hello, Tk létrehozása

Először egy egyszerű „Hello, TK!” példával kezdünk.

```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()
}
```

![hello-tk futtatási eredménye](/assets/images/go-tk-helloworld-svg-etc/go-tk-hello.png)

---

Nézzük meg részletesebben a fenti példakódot és annak futtatási eredményét.

Aki használt már Tk-t Pythonban, az ismeri azt a szerkezetet, amelyben a widgetek egy ablakba vagy közvetlenül a főablak alá kerülnek pack módszerrel. A widget típusától függően például a címkék is ezen belül helyezkednek el.

_Az Ipadx és az Ipady az Internal padding rövidítése, és a belső widgetek margóit szabályozzák. Ebben a példában a gomb belső margója módosul._

A könyvtárban található egy Window struktúra, valamint egy App nevű változó, amely a legfelső szintű ablakot kezeli. Ez a könyvtáron belül előre definiált. Így a tk.App.Wait() függvényt lezáró tk.App.Destroy() függvény feladata a legfelső szintű ablak bezárása.

Most nézzünk meg néhány példát a GitLab \_examples mappájából.

## SVG-fájlok kezelése

Az alábbi példa egy SVG-fájlt jelenít meg egy címke (label) widgetben.

```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()
}
```

![go-tk-svg futtatási eredménye](/assets/images/go-tk-helloworld-svg-etc/go-tk-svg.png)

A könyvtárban az SVG kezelése a következőképpen történik:

1. Az SVG-fájl tartalmát karakterláncként olvassuk be (vagy, mint a fenti példában, közvetlenül beágyazzuk).
2. Ezt a tartalmat a Data függvénynek adjuk át, amely opciókat tartalmazó karakterlánccá alakítja (-data opció).
3. A kapott bájtokat a NewPhoto függvény kapja meg, amely egy Tcl/Tk képet reprezentáló Img struktúra mutatóját adja vissza.
4. Az Image függvényen áthaladva az Img struktúra mutatója olyan karakterlánccá alakul, amelyhez a -Image opció is hozzáadódik.
5. A struktúra RAW értékét tartalmazó karakterláncra azért van szükség, hogy a Label widget létrehozható legyen.

Az ICO- és PNG-fájlok hasonló módon kerülnek feldolgozásra.

## PNG-fájlok kezelése

```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()
}
```

![go-tk-png futtatási eredménye](/assets/images/go-tk-helloworld-svg-etc/go-tk-png.png)

A PNG-fájl feldolgozásának folyamata a következő:

1. A beágyazott gopher.png fájlt opciókat tartalmazó karakterlánc típusává alakítjuk.
2. A NewPhoto függvény segítségével *Img típussá alakítjuk.
3. Az Image függvényen áthaladva RAW karakterlánccá alakul, majd címke (label) widgetként jön létre.

Az ICO-fájl feldolgozása ugyanazon módon történik, az SVG formátumtól csak a Data függvény belső feldolgozási módjában tér el.

Vizsgáljuk meg, mit is jelent a „opciókat tartalmazó karakterlánc”:

```go
type rawOption string
```

A korábban említett, opciókat tartalmazó karakterlánc egyszerűen egy formázott karakterlánc.

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

Az optionString metódus a Window mutatóhoz tartozik, és egy karakterláncot ad vissza.

Végül tekintsük át röviden a Data függvény belső felépítését:

```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)))
}
```

A kódból látható, hogy ICO- és PNG-fájlok esetén kódolási/dekódolási folyamatra van szükség.
Más esetekben nincs szükség különleges átalakításra; a bájtokká alakított karakterlánchoz csupán a -data opciót adjuk hozzá, jelezve, hogy a Data függvény eredményéről van szó.

### Kép betöltése menüwidgettel

A korábban gyakorolt PNG- és ICO-betöltő példához menüwidgetet adva olyan alkalmazást hozhatunk létre, amely a kiválasztott képet megjeleníti.

Először nézzünk egy egyszerű menüwidget-példát.

```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()
}
```

![go-tk-menüsor futtatási eredménye](/assets/images/go-tk-helloworld-svg-etc/go-tk-menu-bar.png)

Ebben a példában bemutattuk a menüsort és a menük létrehozását, a betűk kiemelését, a Command opciót, a billentyűparancs-kötéseket, valamint az alkalmazásablak kezdeti méretének beállítását.

Most módosítsuk a GetOpenFile által meghívott Command függvényt úgy, hogy egy képet töltsön be és jelenítsen meg.

A program tervezett lépései a következők:

1. Csak PNG- és ICO-fájlok megnyitásának engedélyezése
2. A fájlválasztó párbeszédablakban kiválasztott fájl feldolgozása
3. A képmegjelenítéshez szükséges widget megvalósítása

Az ezt a tervet követő kód:

```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()
}
```

![go-tk-képmegnyitás futtatási eredménye](/assets/images/go-tk-helloworld-svg-etc/go-tk-image-open.png)

A fenti kód a következőképpen működik:

1. A strings csomag karakterlánc-összehasonlító függvényével ellenőrzi a fájlkiterjesztést
2. Az os csomag segítségével megnyitja, beolvassa, majd bezárja a fájlt
3. A beolvasott képet címke (label) widgetben jeleníti meg

## Összefoglalás

Ebben a cikkben a Go nyelv Tcl/Tk könyvtárának felhasználásával a következő témákat vizsgáltuk:

1. A GUI-alkalmazások alapvető szerkezete
2. Különböző képformátumok, mint az SVG, PNG és ICO kezelése
3. Widgetek pack-elése és elrendezés-kezelése
4. Képadat-feldolgozási szerkezet
5. Billentyűparancs-kötések és widget-parancsok

A Go nyelv és a Tcl/Tk együttes használatával egyszerű, mégis gyakorlati GUI-programok készíthetők. Erre építve érdemes további, összetettebb GUI-alkalmazások fejlesztésére is vállalkozni.
