GoSuda

Att bygga GUI med Tk i Go

By Yunjin Lee
views ...

Python har GUI-bibliotek som Tkinter inbyggda som standard. Nyligen har ett CGo-Free, Cross Platform Tk-bibliotek utvecklats för att möjliggöra användning av Tcl/Tk även i Go-språket. Idag ska vi undersöka dess grundläggande användning.

Skapa "Hello, Tk"

Låt oss först börja med ett enkelt "Hello, TK!"-exempel.

 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 exekveringsresultat


Låt oss noggrant undersöka exempelkoden och exekveringsresultaten ovan.

Om du har erfarenhet av att använda Pythons Tk, kommer du att förstå strukturen där Widgets packas inuti fönster eller direkt under fönster. Beroende på Widget-typ inkluderas etiketter etc. inuti dem.

Ipadx och Ipady är förkortningar för Internal padding, som justerar marginalerna för interna Widgets. I detta exempel justeras marginalerna för knappen.

Detta bibliotek har en Window-struktur, och variabeln App hanterar det översta fönstret. Detta är fördefinierat i biblioteket. Därför stänger funktionen tk.App.Destroy(), som avslutar tk.App.Wait(), det översta fönstret.

Låt oss nu titta på några exempel från GitLab-mappen _examples.

Bearbeta SVG-filer

Följande är ett exempel på hur man visar en SVG-fil i en etikett-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 exekveringsresultat

Metoden för att bearbeta SVG i detta bibliotek är följande:

  1. Innehållet i SVG-filen läses in som en sträng (eller inkluderas direkt som i exemplet ovan).
  2. Detta innehåll skickas till Data-funktionen för att konverteras till en sträng som innehåller Options (-data Option).
  3. Det konverterade bytevärdet skickas till NewPhoto-funktionen, som returnerar en Img-strukturpekare som representerar Tcl/Tk-bilden.
  4. Genom att passera Image-funktionen konverteras Img-strukturpekaren till en sträng med -Image Option tillagd.
  5. Anledningen till att konvertera till en sträng som innehåller RAW-värdet för strukturen är för att skapa en Label-Widget.

ICO- och PNG-filer bearbetas på ett liknande sätt.

Bearbeta 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 exekveringsresultat

Processen för att bearbeta PNG-filer är följande:

  1. Den inbäddade gopher.png konverteras till en strängtyp som innehåller Options.
  2. Den konverteras till en *Img-typ via NewPhoto-funktionen.
  3. Efter att ha passerat Image-funktionen och konverterats till en RAW-sträng, skapas den som en Label-Widget.

ICO-filer bearbetas på samma sätt, och skillnaden från SVG-formatet ligger endast i Data-funktionens interna bearbetningsmetod.

Låt oss nu titta på identiteten för "strängen som innehåller Options":

1type rawOption string

Den tidigare nämnda strängen som innehåller Options är helt enkelt en formaterad sträng.

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

Metoden optionString är en metod för Window-pekaren som returnerar en sträng.

Slutligen, låt oss kort titta på Data-funktionens interna struktur:

 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 visar att för ICO- eller PNG-filer krävs en kodnings-/avkodningsprocess. I andra fall, utan någon särskild konvertering, läggs endast -data Option till den bytemässigt konverterade strängen för att indikera att det är resultatet av Data-funktionen.

Ladda bilder med Menu Widget

Genom att lägga till en Menu Widget till de tidigare övningarna med att ladda PNG och ICO kan vi skapa en applikation som visar nödvändiga bilder.

Låt oss först titta på ett enkelt exempel med en Menu 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}

go-tk-menyrad-exekveringsresultat

I detta exempel har vi skapat en menyrad och menyer, markerat text, använt Command Option, bundit kortkommandon och ställt in den initiala storleken på applikationsfönstret.

Låt oss nu ändra Command-funktionen som är tilldelad GetOpenFile för att skapa ett program som laddar och visar bilder.

Planen för att skriva programmet är följande:

  1. Begränsa öppning till endast PNG- och ICO-filer
  2. Hantera den valda filen från fildialogen
  3. Implementera en Widget för bildvisning

Följande är koden som återspeglar denna plan:

 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-bild-öppna-exekveringsresultat

Koden ovan fungerar på följande sätt:

  1. Filändelsen kontrolleras med strängjämförelsefunktionen från strings-paketet.
  2. Filen öppnas, läses och stängs med os-paketet.
  3. Den inlästa bilden visas som en Label-Widget.

Sammanfattning

I den här artikeln har vi, med hjälp av Tcl/Tk-biblioteket i Go-språket, täckt följande ämnen:

  1. Grundläggande struktur för GUI-applikationer
  2. Hantering av olika bildformat som SVG, PNG, ICO
  3. Widget-packning och layouthantering
  4. Struktur för bilddatahantering
  5. Kortkommandobindning och Widget-kommandon

Genom att använda Go-språket och Tcl/Tk tillsammans kan du skapa enkla men praktiska GUI-program. Baserat på detta, uppmuntrar vi dig att prova att utveckla mer komplexa GUI-applikationer.