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 Go-Sprache eine CGo-Free, Cross Platform Tk Bibliothek entwickelt, um die Nutzung von Tcl/Tk zu ermöglichen. Heute werden wir deren grundlegende Anwendung untersuchen.

Erstellung 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


Wir werden den obigen Beispielcode und das Ausführungsergebnis genauer betrachten.

Wer bereits Erfahrung mit Tk in Python hat, wird die Struktur verstehen, in der Widgets in einem Fenster oder direkt unter einem Fenster gepackt werden. Labels und Ähnliches werden je nach Widget-Typ darin enthalten sein.

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 besitzt eine Window-Struktur, und die Variable App verwaltet das oberste Fenster. Dies ist innerhalb der Bibliothek vordefiniert. Daher schließt die Funktion tk.App.Destroy(), welche tk.App.Wait() beendet, das oberste Fenster.

Betrachten wir nun einige Beispiele aus dem Ordner _examples von GitLab.

Verarbeitung von SVG-Dateien

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

 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 direkt eingebunden, wie im obigen Beispiel).
  2. Dieser Inhalt wird an die Data-Funktion übergeben, um ihn in einen String mit Optionen umzuwandeln (die Option -data).
  3. Der umgewandelte Byte-Wert wird an die NewPhoto-Funktion übergeben, die einen Img-Struktur-Pointer zurückgibt, der ein Tcl/Tk-Bild darstellt.
  4. Beim Durchlaufen der Image-Funktion wird der Img-Struktur-Pointer in einen String mit der hinzugefügten Option -Image 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.

Verarbeitung von PNG-Dateien

 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 Verarbeitungsprozess einer PNG-Datei ist wie folgt:

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

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

Betrachten wir nun die Beschaffenheit des "optionshaltigen Strings":

1type rawOption string

Der zuvor erwähnte optionshaltige String ist lediglich ein formatierter String.

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

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

Abschließend 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 einen Byte-Typ umgewandelten String lediglich die Option -data hinzugefügt, um anzuzeigen, dass es sich um das Ergebnis der Data-Funktion handelt, ohne eine spezielle Konvertierung.

Bilder über das Menü-Widget laden

Wenn wir den zuvor geübten Beispielen zum Laden von PNG- und ICO-Dateien ein Menü-Widget hinzufügen, können wir eine Anwendung erstellen, die die benötigten Bilder anzeigt.

Betrachten wir zunächst ein einfaches Beispiel für ein Menü-Widget.

 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 die Erstellung einer Menüleiste und eines Menüs, die Text hervorhebung, die Command-Option, die Tastenkombinationsbindung und die Einstellung der Anfangsgröße des Anwendungsfensters geübt.

Nun werden wir die mit GetOpenFile spezifizierte Command-Funktion modifizieren, um ein Programm zu erstellen, das Bilder lädt und anzeigt.

Der Plan für die Programmentwicklung lautet wie folgt:

  1. Beschränkung auf das Öffnen von PNG- und ICO-Dateien
  2. Verarbeitung der in einem 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-Bildladevorgangs

Der obige Code funktioniert wie folgt:

  1. Die Dateierweiterung wird mit den String-Vergleichsfunktionen des strings-Pakets überprüft.
  2. Die Datei wird mit dem os-Paket geöffnet, gelesen und anschließend geschlossen.
  3. Das eingelesene Bild wird als Label-Widget angezeigt.

Zusammenfassung

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

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

Durch die Kombination von Go und Tcl/Tk lassen sich einfache, aber praktische GUI-Programme erstellen. Basierend darauf können Sie sich an der Entwicklung komplexerer GUI-Anwendungen versuchen.