---
id: 974f36e12dfcece861bfda1af9303980
author: Yunjin Lee
title: 'CGo-freies Go: Implementierung einer Tcl/Tk-GUI (modernc.org/tk9.0): Widget-Packing und Bildrendering'
description: Die Implementierung von Fenstererstellung, Packen von Schaltflächen- und Menüleisten-Widgets sowie Rendering von SVG-, PNG- und ICO-Bildern in Go erfolgt mit der plattformübergreifenden Bibliothek modernc.org/tk9.0, die ohne CGo-Abhängigkeiten auskommt.
language: de
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 enthält von Haus aus eine GUI-Bibliothek wie Tkinter.
Kürzlich wurde für die Programmiersprache Go die [CGo-Free, Cross Platform Tk Bibliothek](https://pkg.go.dev/modernc.org/tk9.0) entwickelt, mit der Tcl/Tk verwendet werden kann. Heute werfen wir einen Blick auf ihre grundlegende Verwendung.

## Ein Hello, Tk erstellen

Beginnen wir zunächst mit einem einfachen 'Hello, TK!'-Beispiel.

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

![Ausgabe von hello-tk](/assets/images/go-tk-helloworld-svg-etc/go-tk-hello.png)

---

Sehen wir uns den obigen Beispielcode und seine Ausgabe genauer an.

Wer Erfahrung mit Tk in Python hat, wird die Struktur verstehen, in der Widgets innerhalb eines Fensters oder direkt unterhalb eines Fensters gepackt werden. Je nach Widgettyp sind darin etwa Labels enthalten.

_Ipadx und Ipady stehen für Internal padding und steuern den inneren Abstand der Widgets. In diesem Beispiel wird der Abstand der Schaltfläche angepasst._

Diese Bibliothek verfügt über eine Struktur namens Window, und die Variable App verwaltet das oberste Fenster. Diese ist bereits innerhalb der Bibliothek vordefiniert. Daher schließt die Funktion tk.App.Destroy(), die tk.App.Wait() beendet, das oberste Fenster.

Schauen wir uns nun einige Beispiele aus dem Ordner \_examples im GitLab an.

## SVG-Dateien verarbeiten

Im Folgenden sehen Sie ein Beispiel, das eine SVG-Datei in einem Label-Widget anzeigt.

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

![Ausgabe von go-tk-svg](/assets/images/go-tk-helloworld-svg-etc/go-tk-svg.png)

Die Verarbeitung von SVG in dieser Bibliothek erfolgt wie folgt:

1. Der Inhalt der SVG-Datei wird als Zeichenkette eingelesen (oder wie im obigen Beispiel direkt eingebettet).
2. Dieser Inhalt wird an die Funktion Data übergeben und in eine Zeichenkette mit Optionen umgewandelt (Option -data).
3. Die resultierenden Bytewerte werden an die Funktion NewPhoto übergeben, die einen Zeiger auf die Struktur Img zurückgibt, welche ein Tcl/Tk-Bild repräsentiert.
4. Beim Durchlauf durch die Funktion Image wird der Zeiger auf die Struktur Img in eine Zeichenkette mit der Option -Image umgewandelt.
5. Die Umwandlung in eine Zeichenkette mit dem RAW-Wert der Struktur erfolgt, um das Label-Widget zu erzeugen.

ICO- und PNG-Dateien werden auf ähnliche Weise verarbeitet.

## PNG-Dateien verarbeiten

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

![Ausgabe von go-tk-png](/assets/images/go-tk-helloworld-svg-etc/go-tk-png.png)

Der Ablauf bei der Verarbeitung von PNG-Dateien ist wie folgt:

1. Die eingebettete Datei gopher.png wird in einen Zeichenkettentyp mit Optionen umgewandelt.
2. Über die Funktion NewPhoto wird sie in den Typ *Img umgewandelt.
3. Nach dem Durchlauf durch die Funktion Image wird sie in eine RAW-Zeichenkette umgewandelt und anschließend als Label-Widget erzeugt.

ICO-Dateien werden auf die gleiche Weise verarbeitet; der Unterschied zum SVG-Format liegt allein in der internen Verarbeitung der Funktion Data.

Schauen wir uns nun das Wesen der "Zeichenkette mit Optionen" an:

```go
type rawOption string
```

Die zuvor erwähnte Zeichenkette mit Optionen ist lediglich eine formatierte Zeichenkette.

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

Die Methode optionString ist eine Methode für einen Window-Zeiger und gibt eine Zeichenkette zurück.

Zum Schluss betrachten wir kurz den internen Aufbau der Funktion 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)))
}
```

Der Code zeigt, dass bei ICO- und PNG-Dateien Kodierungs- und Dekodierungsvorgänge erforderlich sind.
In allen anderen Fällen wird keine besondere Umwandlung vorgenommen; der bereits in Bytes umgewandelten Zeichenkette wird lediglich die Option -data hinzugefügt, um sie als Ergebnis der Funktion Data zu kennzeichnen.

### Bilder über ein Menü-Widget laden

Wenn man das zuvor geübte Beispiel zum Laden von PNG- und ICO-Dateien um ein Menü-Widget erweitert, lässt sich eine Anwendung erstellen, die die gewünschten Bilder anzeigt.

Zunächst betrachten wir ein einfaches Beispiel für ein Menü-Widget.

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

![Ausgabe von go-tk-menu-bar](/assets/images/go-tk-helloworld-svg-etc/go-tk-menu-bar.png)

In diesem Beispiel wurden eine Menüleiste und Menüs erstellt, Buchstaben hervorgehoben, die Option Command verwendet, Tastenkombinationen gebunden und die anfängliche Größe des Anwendungsfensters festgelegt.

Nun ändern wir die mit GetOpenFile verknüpfte Funktion Command so, dass ein Programm entsteht, das Bilder lädt und anzeigt.

Der Plan für die Programmerstellung lautet wie folgt:

1. Nur das Öffnen von PNG- und ICO-Dateien zulassen
2. Die im Dateidialog ausgewählte Datei verarbeiten
3. Ein Widget zur Anzeige des Bildes implementieren

Der folgende Code setzt diesen Plan um:

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

![Ausgabe von go-tk-image-open](/assets/images/go-tk-helloworld-svg-etc/go-tk-image-open.png)

Der obige Code arbeitet auf folgende Weise:

1. Die Dateiendung wird mithilfe der Zeichenkettenvergleichsfunktion des Pakets strings überprüft
2. Mit dem Paket os wird die Datei geöffnet, gelesen und anschließend geschlossen
3. Das eingelesene Bild wird in einem Label-Widget angezeigt

## Zusammenfassung

In diesem Beitrag haben wir mithilfe der Tcl/Tk-Bibliothek für Go folgende Inhalte behandelt:

1. Grundstruktur einer GUI-Anwendung
2. Verarbeitung verschiedener Bildformate wie SVG, PNG und ICO
3. Packen von Widgets und Layoutverwaltung
4. Struktur der Bildverarbeitung
5. Tastenkombinationen und Widget-Befehle

Mit Go und Tcl/Tk lassen sich einfache und dennoch praxisnahe GUI-Programme erstellen. Auf dieser Grundlage wird empfohlen, sich an die Entwicklung komplexerer GUI-Anwendungen zu wagen.
