GoSuda

GUI-Erstellung mit Tk in Go

By Yunjin Lee
views ...

Python verfügt standardmäßig über integrierte GUI-Bibliotheken wie Tkinter. Kürzlich wurde für die Sprache Go eine CGo-freie, plattformübergreifende Tk-Bibliothek entwickelt, um Tcl/Tk nutzen zu können. Heute werden wir uns die grundlegende Verwendung dieser Bibliothek ansehen.

Erstellen von „Hello, Tk“

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

 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}

Ausführungsergebnis von hello-tk


Betrachten wir den obigen Beispielcode und das Ausführungsergebnis genauer.

Wer Erfahrung mit Tk in Python hat, wird die Struktur verstehen, in der Widgets in einem Fenster oder direkt unter einem Fenster gepackt werden. Je nach Widget-Typ sind Labels etc. darin enthalten.

Ipadx und Ipady sind Abkürzungen für „Internal padding“ und steuern den Abstand der internen Widgets. In diesem Beispiel wird der Abstand des Buttons angepasst.

Diese Bibliothek enthält eine Window-Struktur, und die Variable App verwaltet das oberste Fenster. Dies ist in der Bibliothek vordefiniert. Daher schließt die Funktion tk.App.Destroy(), die tk.App.Wait() beendet, das oberste Fenster.

Sehen wir uns nun einige Beispiele aus dem _examples-Ordner von GitLab an.

SVG-Dateien verarbeiten

Im Folgenden finden Sie ein Beispiel für die Anzeige einer SVG-Datei in einem Label-Widget.

 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}

Ausführungsergebnis von go-tk-svg

Die Verarbeitung von SVG in dieser Bibliothek erfolgt wie folgt:

  1. Der Inhalt der SVG-Datei wird als String eingelesen (oder wie im obigen Beispiel direkt eingebunden).
  2. Dieser Inhalt wird an die Data-Funktion übergeben, um ihn in einen String mit Optionen umzuwandeln (-data Option).
  3. Der umgewandelte Byte-Wert wird an die NewPhoto-Funktion übergeben, die einen Img-Strukturzeiger zurückgibt, der ein Tcl/Tk-Bild darstellt.
  4. Beim Durchlaufen der Image-Funktion wird der Img-Strukturzeiger in einen String mit der hinzugefügten -Image-Option umgewandelt.
  5. Der Grund für die Umwandlung in einen String, der RAW-Werte der Struktur enthält, ist die Erstellung des Label-Widgets.

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

PNG-Dateien verarbeiten

 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}

Ausführungsergebnis von go-tk-png

Der PNG-Dateiverarbeitungsprozess ist wie folgt:

  1. Die eingebettete gopher.png wird in einen String-Typ mit Optionen umgewandelt.
  2. Sie wird über die NewPhoto-Funktion in den Typ *Img umgewandelt.
  3. Nach der Umwandlung in einen RAW-String über die Image-Funktion wird sie als Label-Widget erstellt.

ICO-Dateien werden auf die gleiche Weise verarbeitet, wobei der einzige Unterschied zum SVG-Format die interne Verarbeitung der Data-Funktion ist.

Betrachten wir nun die Natur des „Strings mit Optionen“:

1type rawOption string

Der zuvor erwähnte String mit Optionen ist lediglich ein formatierter String.

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

Die optionString-Methode ist eine Methode für den Window-Pointer, die einen String zurückgibt.

Schließlich werfen wir einen kurzen Blick auf die interne Struktur der Data-Funktion:

 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}

Der Code zeigt, dass bei ICO- oder PNG-Dateien ein Kodierungs-/Dekodierungsprozess erforderlich ist. In anderen Fällen wird dem in Bytes umgewandelten String ohne spezielle Umwandlung lediglich die Option „-data“ hinzugefügt, um anzuzeigen, dass es sich um das Ergebnis der Data-Funktion handelt.

Bilder mit einem Menü-Widget laden

Durch das Hinzufügen eines Menü-Widgets zu den zuvor geübten Beispielen zum Laden von PNG- und ICO-Dateien kann eine Anwendung erstellt werden, die die erforderlichen Bilder anzeigt.

Schauen wir uns zunächst ein einfaches Menü-Widget-Beispiel an.

 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}

Ausführungsergebnis der Go-Tk-Menüleiste

In diesem Beispiel haben wir das Erstellen einer Menüleiste und eines Menüs, die Hervorhebung von Text, die Command-Option, die Tastenkombinationsbindung und die Einstellung der anfänglichen Größe des Anwendungsfensters ausprobiert.

Nun werden wir die als GetOpenFile definierte Command-Funktion ändern, um ein Programm zu erstellen, das Bilder lädt und anzeigt.

Der Plan für die Programmentwicklung ist wie folgt:

  1. Beschränkung auf das Öffnen von PNG- und ICO-Dateien
  2. Verarbeitung der im Dateidialog ausgewählten Datei
  3. Implementierung eines Widgets zur Bildanzeige

Im Folgenden finden Sie den Code, der diese Planung widerspiegelt:

 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}

Ausführungsergebnis des Go-Tk-Bildladers

Der obige Code funktioniert wie folgt:

  1. Er überprüft die Dateierweiterung mit der String-Vergleichsfunktion des strings-Pakets.
  2. Er öffnet, liest und schließt die Datei mit dem os-Paket.
  3. Das eingelesene Bild wird als Label-Widget angezeigt.

Zusammenfassung

In diesem Artikel haben wir die Tcl/Tk-Bibliothek der Sprache Go verwendet, um folgende Themen zu behandeln:

  1. Grundstruktur von GUI-Anwendungen
  2. Verarbeitung verschiedener Bildformate wie SVG, PNG, ICO
  3. Widget-Packing und Layout-Management
  4. Struktur der Bilddatenverarbeitung
  5. Tastenkombinationsbindung und Widget-Befehle

Die gemeinsame Verwendung von Go und Tcl/Tk ermöglicht die Erstellung einfacher und dennoch praktischer GUI-Programme. Basierend darauf sollten Sie versuchen, komplexere GUI-Anwendungen zu entwickeln.