GoSuda

GUI ontwikkelen met Tk in Go

By Yunjin Lee
views ...

Python heeft GUI-bibliotheken zoals Tkinter standaard ingebouwd. Recentelijk is voor de Go-taal een CGo-Free, Cross Platform Tk-bibliotheek ontwikkeld, waarmee Tcl/Tk kan worden gebruikt. Vandaag bekijken we de basisprincipes van het gebruik hiervan.

Hello, Tk aanmaken

Laten we beginnen met een eenvoudig 'Hello, TK!'-voorbeeld.

 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}

hello-tk 실행 결과


Laten we de bovenstaande voorbeeldcode en het uitvoerresultaat nauwkeurig bekijken.

Indien u ervaring heeft met het gebruik van Tk in Python, zult u de structuur begrijpen waarbij widgets in een venster of direct onder een venster worden 'packed'. Afhankelijk van het type widget worden labels en dergelijke daarin opgenomen.

Ipadx en Ipady zijn afkortingen van Internal padding en regelen de marge van interne widgets. In dit voorbeeld wordt de marge van de knop aangepast.

Deze bibliotheek bevat een Window-structuur, en de variabele App beheert het bovenste venster. Dit is vooraf gedefinieerd binnen de bibliotheek. Daarom sluit de tk.App.Destroy() functie, die tk.App.Wait() beëindigt, het bovenste venster.

Laten we nu enkele voorbeelden bekijken uit de _examples-map van GitLab.

SVG-bestanden verwerken

Hieronder volgt een voorbeeld van het weergeven van een SVG-bestand in een label-widget.

 1package main
 2
 3import . "modernc.org/tk99.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}

go-tk-svg 실행 결과

De verwerking van SVG in deze bibliotheek verloopt als volgt:

  1. De inhoud van het SVG-bestand wordt als een string ingelezen (of direct opgenomen, zoals in het bovenstaande voorbeeld).
  2. Deze inhoud wordt doorgegeven aan de Data-functie om te worden geconverteerd naar een string met opties (-data optie).
  3. De geconverteerde byte-waarde wordt doorgegeven aan de NewPhoto-functie, die een Img-structuurpointer retourneert die een Tcl/Tk-afbeelding representeert.
  4. Door de Image-functie te passeren, wordt de Img-structuurpointer geconverteerd naar een string waaraan de -Image optie is toegevoegd.
  5. De reden voor het converteren naar een string die de RAW-waarde van de structuur bevat, is voor het aanmaken van de Label-widget.

ICO- en PNG-bestanden worden op vergelijkbare wijze verwerkt.

PNG-bestanden verwerken

 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}

go-tk-png 실행결과

Het PNG-bestandsverwerkingsproces is als volgt:

  1. Het ingebedde gopher.png wordt geconverteerd naar een stringtype met opties.
  2. Via de NewPhoto-functie wordt het geconverteerd naar een *Img-type.
  3. Na conversie naar een RAW-string via de Image-functie, wordt het als een label-widget aangemaakt.

ICO-bestanden worden op dezelfde wijze verwerkt; het enige verschil met het SVG-formaat zit in de interne verwerking van de Data-functie.

Laten we nu de aard van de "string met opties" onderzoeken:

1type rawOption string

De eerder genoemde string met opties is slechts een geformatteerde string.

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

De optionString-methode is een methode voor een Window-pointer en retourneert een string.

Laten we tot slot kort de interne structuur van de Data-functie bekijken:

 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}

Uit de code blijkt dat in het geval van ICO- of PNG-bestanden een coderings-/decoderingsproces noodzakelijk is. In andere gevallen wordt, zonder specifieke conversie, alleen de -data optie toegevoegd aan de naar bytes geconverteerde string om aan te geven dat het een resultaat van de Data-functie betreft.

Afbeeldingen laden via de menu-widget

Door een menu-widget toe te voegen aan de eerder geoefende voorbeelden van PNG- en ICO-laden, kan een applicatie worden gemaakt die de gewenste afbeeldingen weergeeft.

Laten we eerst een eenvoudig voorbeeld van een menu-widget bekijken.

 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}

go-tk-메뉴바-실행결과

In dit voorbeeld hebben we het aanmaken van een menubalk en menu, tekstaccentuering, de Command-optie, sneltoetsbindingen en het instellen van de initiële grootte van het applicatievenster behandeld.

Laten we nu de Command-functie die is toegewezen aan GetOpenFile aanpassen om een programma te maken dat afbeeldingen laadt en weergeeft.

Het plan voor de programmacodering is als volgt:

  1. Beperk de mogelijkheid om alleen PNG- en ICO-bestanden te openen.
  2. Verwerk het geselecteerde bestand uit het bestandendialoogvenster.
  3. Implementeer een widget voor het weergeven van afbeeldingen.

Hieronder volgt de code die dit plan weerspiegelt:

 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}

go-tk-이미지-불러오기-실행결과

De bovenstaande code werkt op de volgende manier:

  1. De bestandsextensie wordt gecontroleerd met behulp van de stringvergelijkingsfunctie van het strings-pakket.
  2. Het bestand wordt geopend, gelezen en gesloten met behulp van het os-pakket.
  3. De ingelezen afbeelding wordt weergegeven als een label-widget.

Samenvatting

In dit artikel hebben we de volgende onderwerpen behandeld met behulp van de Tcl/Tk-bibliotheek in de Go-taal:

  1. Basisstructuur van een GUI-applicatie
  2. Verwerking van verschillende afbeeldingsformaten zoals SVG, PNG en ICO
  3. Widget-packing en lay-outbeheer
  4. Structuur van afbeeldingsdataverwerking
  5. Sneltoetsbindingen en widget-commando's

Door Go en Tcl/Tk samen te gebruiken, kunnen eenvoudige doch praktische GUI-programma's worden gecreëerd. Gebruik dit als basis om complexere GUI-applicaties te ontwikkelen.