GoSuda

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 kommer vi att titta på 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    // Packar en knapp till huvudfönstret.
 7    // Knappen har texten "Hello, TK!" och en Command som stänger applikationen när den klickas.
 8    // Ipadx, Ipady, Padx och Pady används för att justera knappens padding.
 9    tk.Pack(
10        tk.TButton(
11            tk.Txt("Hello, TK!"),
12            tk.Command(func() {
13                tk.Destroy(tk.App)
14            })),
15        tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10),
16    )
17    // Väntar tills applikationen stängs.
18    tk.App.Wait()
19}

hello-tk 실행 결과


Låt oss noggrant granska exempelkoden och exekveringsresultatet ovan.

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

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

Detta bibliotek har en Window-struktur, och en variabel vid namn App hanterar det översta fönstret. Denna är fördefinierad internt i biblioteket. Följaktligen fungerar funktionen tk.App.Destroy(), som avslutar tk.App.Wait(), för att stänga det översta fönstret.

Låt oss nu titta på några exempel som finns i GitLab-mappen _examples.

Hantera SVG-filer

Följande är ett exempel på hur man visar en SVG-fil i en Label Widget.

 1package main
 2
 3import . "modernc.org/tk9.0"
 4
 5// https://en.wikipedia.org/wiki/SVG
 6// SVG-data som en sträng.
 7const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 8<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 9<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">
10<rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
11<g opacity="0.8">
12    <rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
13    <circle cx="125" cy="125" r="75" fill="orange" />
14    <polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
15    <line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
16</g>
17</svg>`
18
19func main() {
20    // Packar en Label Widget som visar SVG-bilden.
21    // TExit skapar en knapp för att avsluta applikationen.
22    // Padx, Pady, Ipadx, Ipady används för att justera padding.
23    Pack(Label(Image(NewPhoto(Data(svg)))),
24        TExit(),
25        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
26    // Centrerar applikationen och väntar tills den stängs.
27    App.Center().Wait()
28}

go-tk-svg 실행 결과

Metoden för att hantera 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 alternativ (-data-alternativet).
  3. Det konverterade bytevärdet skickas till NewPhoto-funktionen, som returnerar en Img-strukturpekare som representerar en Tcl/Tk-bild.
  4. När den passerar Image-funktionen konverteras Img-strukturpekaren till en sträng med tillägget -Image-alternativet.
  5. Anledningen till att konvertera till en sträng som innehåller strukturernas RAW-värden är för att skapa Label Widget.

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

Hantera PNG-filer

 1package main
 2
 3import _ "embed"
 4import . "modernc.org/tk9.0"
 5
 6//go:embed gopher.png
 7// Inbäddad PNG-bild.
 8var gopher []byte
 9
10func main() {
11    // Packar en Label Widget som visar gopher-bilden.
12    // TExit skapar en knapp för att avsluta applikationen.
13    // Padx, Pady, Ipadx, Ipady används för att justera padding.
14    Pack(Label(Image(NewPhoto(Data(gopher)))),
15        TExit(),
16        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
17    // Centrerar applikationen och väntar tills den stängs.
18    App.Center().Wait()
19}

go-tk-png 실행결과

Processen för att hantera PNG-filer är som följer:

  1. Den inbäddade gopher.png konverteras till en strängtyp som innehåller alternativ.
  2. Den konverteras till typen *Img 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 hanteras på samma sätt, och skillnaden från SVG-formatet ligger endast i Data-funktionens interna bearbetningsmetod.

Låt oss nu titta på vad "sträng med alternativ" innebär:

1type rawOption string

Den tidigare nämnda strängen som innehåller alternativ ä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 en Window-pekare och 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}

Om man tittar på koden krävs en kodnings-/avkodningsprocess för ICO- och PNG-filer. I andra fall, utan någon särskild konvertering, läggs endast -data-alternativet till i den bytedata-konverterade strängen för att indikera att det är ett resultat från Data-funktionen.

Ladda bilder med Widget för menyer

Genom att lägga till en meny-Widget till de tidigare exemplen för 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 på en meny-Widget.

 1package main
 2
 3import (
 4    "fmt"
 5    . "modernc.org/tk9.0"
 6    "runtime"
 7)
 8
 9func main() {
10    // Skapar en menyrad.
11    menubar := Menu()
12
13    // Skapar en "File"-meny.
14    fileMenu := menubar.Menu()
15    // Lägger till kommandon i "File"-menyn.
16    fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N"))
17    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() }))
18    // Binder Ctrl+O till att anropa det första kommandot i filmenyn (Open...).
19    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) }))
20    fileMenu.AddCommand(Lbl("Save As..."), Underline(5))
21    fileMenu.AddSeparator() // Lägger till en avdelare.
22    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
23    // Binder Ctrl+Q till att anropa det sista kommandot i filmenyn (Exit).
24    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) }))
25    // Lägger till "File"-menyn till menyraden.
26    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
27
28    // Sätter fönstertiteln.
29    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
30    // Konfigurerar applikationen med menyraden och ställer in initial storlek, sedan väntar.
31    App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait()
32}

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

I detta exempel har vi skapat en menyrad och en meny, betonat text, använt Command-alternativ, 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.

Programplanen ä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 kod å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
13// handleFileOpen hanterar filöppning, filtrerar efter PNG och ICO och visar bilden.
14func handleFileOpen() {
15    // Hämtar sökvägen till den valda filen.
16    s := GetOpenFile()
17    // Itererar över de valda filerna.
18    for _, photo := range s {
19        // Extraherar filformatet från filnamnet.
20        formatCheck := strings.Split(photo, ".")
21        format := formatCheck[len(formatCheck)-1]
22        
23        // Kontrollerar om filformatet är PNG eller ICO.
24        if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
25            // Öppnar filen.
26            picFile, err := os.Open(photo)
27            if err != nil {
28                log.Println("Error while opening photo, error is: ", err)
29                continue // Gå till nästa fil om ett fel uppstår.
30            }
31
32            // Läser bilddata. Observera att detta allokerar en stor buffert och läser hela filen,
33            // vilket kan vara ineffektivt för mycket stora filer.
34            // En mer robust lösning skulle hantera fel och buffertstorlekar mer dynamiskt.
35            pictureRawData := make([]byte, 10000*10000)
36            n, err := picFile.Read(pictureRawData)
37            if err != nil {
38                log.Println("Error while reading photo, error is: ", err)
39                picFile.Close()
40                continue
41            }
42            pictureRawData = pictureRawData[:n] // Justerar slice till faktiskt läst data.
43
44            // Skapar en Label Widget för att visa bilden.
45            imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
46            // Packar Label Widgeten i fönstret.
47            Pack(imageLabel,
48                TExit(), // Lägger till en "Exit"-knapp.
49                Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
50        }
51        // Stänger filen.
52        picFile.Close()
53    }
54}
55
56func main() {
57    // Skapar en menyrad.
58    menubar := Menu()
59
60    // Skapar en "File"-meny.
61    fileMenu := menubar.Menu()
62    // Lägger till "Open..."-kommandot som anropar handleFileOpen.
63    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen))
64    // Binder Ctrl+O till att anropa "Open..."-kommandot i filmenyn.
65    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
66    // Lägger till "Exit"-kommandot.
67    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
68    // Binder Ctrl+Q till att anropa "Exit"-kommandot i filmenyn.
69    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
70    // Lägger till "File"-menyn till menyraden.
71    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
72
73    // Sätter fönstertiteln.
74    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
75    // Konfigurerar applikationen med menyraden och ställer in initial storlek, sedan väntar.
76    App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait()
77}

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

Ovanstående kod fungerar på följande sätt:

  1. strängpaketet används för att kontrollera filändelsen med strängjämförelsefunktioner.
  2. os-paketet används för att öppna, läsa och stänga filen.
  3. Den inlästa bilden visas som en Label Widget.

Sammanfattning

I denna artikel har vi behandlat följande ämnen med hjälp av Tcl/Tk-biblioteket i Go-språket:

  1. Grundläggande struktur för GUI-applikationer
  2. Hantering av olika bildformat som SVG, PNG, ICO
  3. Widget-packning och layout-hantering
  4. Bilddatahanteringsstruktur
  5. Kortkommando-bindning och Widget-kommandon

Genom att använda Go-språket tillsammans med Tcl/Tk kan man skapa enkla men praktiska GUI-program. Baserat på detta, utmana dig själv att utveckla mer komplexa GUI-applikationer.