GoSuda

Bygg GUI med Tk i Go

By Yunjin Lee
views ...

Python har GUI-biblioteker som Tkinter innebygd som standard. Nylig er et CGo-Free, Cross Platform Tk library utviklet for å muliggjøre bruk av Tcl/Tk også i Go-språket. I dag skal vi se på den grunnleggende bruken av dette.

Lage "Hello, Tk"

La oss først starte med et enkelt "Hello, TK!"-eksempel.

 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 kjøringsresultat


La oss se nærmere på eksemplarisk kode og kjøringsresultater ovenfor.

De som har erfaring med å bruke Tks Python vil forstå strukturen der Widgets pakkes inn i et vindu eller direkte under et vindu. Avhengig av Widget-typen, vil Labels og lignende være inkludert.

Ipadx og Ipady er forkortelser for Internal padding, som justerer margene for interne Widgets. I dette eksempelet justeres margene for knappen.

Dette biblioteket har en Window-struktur, og variabelen App administrerer toppnivåvinduet. Dette er forhåndsdefinert i biblioteket. Derfor er det tk.App.Destroy()-funksjonen, som avslutter tk.App.Wait(), som lukker toppnivåvinduet.

La oss nå se på noen eksempler fra GitLab sin _examples-mappe.

Behandle SVG-filer

Følgende er et eksempel som viser en SVG-fil i en 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}

go-tk-svg kjøringsresultat

Måten dette biblioteket behandler SVG på er som følger:

  1. Innholdet i SVG-filen leses inn som en streng (eller inkluderes direkte som i eksempelet ovenfor).
  2. Dette innholdet sendes til Data-funksjonen for å konverteres til en streng som inneholder Options (-data Option).
  3. De konverterte byteverdiene sendes til NewPhoto-funksjonen, som returnerer en Img-strukturpeker som representerer Tcl/Tk-bildet.
  4. Når Img-strukturpekeren passerer gjennom Image-funksjonen, konverteres den til en streng med -Image Option lagt til.
  5. Årsaken til konvertering til en streng som inneholder RAW-verdien av strukturen er for å opprette Label-widgeten.

ICO- og PNG-filer behandles på en lignende måte.

Behandle PNG-filer

 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 kjøringsresultat

PNG-filbehandlingen er som følger:

  1. Den innebygde gopher.png konverteres til en strengtype som inneholder Options.
  2. Den konverteres til en *Img-type via NewPhoto-funksjonen.
  3. Etter å ha gått gjennom Image-funksjonen og blitt konvertert til en RAW-streng, opprettes den som en Label-widget.

ICO-filer behandles på samme måte, og forskjellen fra SVG-formatet er kun i den interne behandlingen av Data-funksjonen.

La oss nå se på hva "streng med Options" er:

1type rawOption string

Strengen som inneholder opsjonen som er nevnt tidligere, er bare en formatert streng.

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

Metoden optionString er en metode for Window-pekeren, som returnerer en streng.

Til slutt, la oss kort se på den interne strukturen til Data-funksjonen:

 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}

Koden viser at ICO- eller PNG-filer krever en koding/dekoding-prosess. I andre tilfeller legges kun -data Option til den byte-konverterte strengen, uten spesiell konvertering, for å indikere at det er Data-funksjonens resultat.

Laste inn bilder med menywidget

Ved å legge til en menywidget til eksemplene for lasting av PNG og ICO som vi øvde på tidligere, kan vi lage en applikasjon som viser nødvendige bilder.

La oss først se på et enkelt menywidget-eksempel.

 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-menubar-kjøringsresultat

I dette eksempelet har vi satt opp en menylinje og menyopprettelse, tekstutheving, Command Option, snarveistilknytning og startstørrelsen på applikasjonsvinduet.

Nå skal vi endre Command-funksjonen angitt av GetOpenFile for å lage et program som laster inn og viser bilder.

Planen for programskriving er som følger:

  1. Begrense til kun å åpne PNG- og ICO-filer
  2. Behandle den valgte filen fra fildialogen
  3. Implementere en Widget for bildevisning

Følgende kode reflekterer denne planen:

 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-bilde-lasting-kjøringsresultat

Koden ovenfor fungerer på følgende måte:

  1. Filutvidelsen kontrolleres ved hjelp av strengsammenligningsfunksjonen i strings-pakken
  2. Filen åpnes, leses og lukkes ved hjelp av os-pakken
  3. Det innleste bildet vises som en Label-widget

Oppsummering

I denne artikkelen har vi, ved å bruke Go-språkets Tcl/Tk-bibliotek, dekket følgende innhold:

  1. Grunnleggende struktur for GUI-applikasjoner
  2. Behandling av forskjellige bildeformater som SVG, PNG, ICO
  3. Widget-pakking og layout-administrasjon
  4. Struktur for bildebehandling
  5. Snarveistilknytning og Widget-kommandoer

Ved å bruke Go-språket og Tcl/Tk sammen, kan man lage enkle, men praktiske GUI-programmer. Basert på dette, oppfordres du til å prøve å utvikle mer komplekse GUI-applikasjoner.