CGo-fri Go Tcl/Tk-GUI-implementation (modernc.org/tk9.0): widgetpackning och bildrendering

Python har ett GUI-bibliotek såsom Tkinter inbyggt som standard. Nyligen har även CGo-Free, Cross Platform Tk-biblioteket utvecklats, som gör det möjligt att använda Tcl/Tk i språket Go. I dag ska vi titta på de grundläggande användningssätten.
Skapa Hello, Tk
Vi börjar med ett enkelt exempel med 'Hello, TK!'.
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}

Låt oss närmare granska exemplets kod och körningsresultat.
Om du har erfarenhet av Tk i Python känner du igen strukturen där widgets packas inuti ett fönster eller direkt under fönstret. Beroende på widgetens typ inkluderas exempelvis etiketter i den.
Ipadx och Ipady är förkortningar av Internal padding och styr de inre utfyllnaderna i underordnade widgets. I detta exempel justeras knappens utfyllnad.
Biblioteket innehåller en struktur vid namn Window, och variabeln App hanterar det översta fönstret. Den är fördefinierad inuti biblioteket. Därför fungerar funktionen tk.App.Destroy(), som avslutar tk.App.Wait(), till att stänga det översta fönstret.
Låt oss nu titta på några exempel i mappen _examples i GitLab.
Hantera SVG-filer
Följande är ett exempel som visar en SVG-fil i en etikettwidget.
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}

Så här hanterar detta bibliotek SVG:
- Läs in innehållet i SVG-filen som en sträng (eller inkludera det direkt, som i exemplet ovan).
- Skicka innehållet till funktionen Data för att omvandla det till en sträng med alternativ (-data-alternativet).
- De omvandlade bytevärdena skickas till funktionen NewPhoto, som returnerar en pekare till strukturen Img som representerar en Tcl/Tk-bild.
- När pekaren till strukturen Img passerar funktionen Image omvandlas den till en sträng med tillägget -Image-alternativet.
- Anledningen till att den omvandlas till en sträng som innehåller det råa värdet av strukturen är att skapa en Label-widget.
ICO- och PNG-filer hanteras på ett liknande sätt.
Hantera 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}

Processen för att hantera PNG-filer är följande:
- Omvandla den inbäddade gopher.png till en strängtyp som innehåller alternativ.
- Omvandla den till typen *Img med hjälp av funktionen NewPhoto.
- Efter att ha passerat funktionen Image omvandlas den till en RAW-sträng, varefter en Label-widget skapas.
ICO-filer hanteras på samma sätt, och den enda skillnaden mot SVG-formatet ligger i hur funktionen Data bearbetar dem internt.
Låt oss här undersöka vad "strängen som innehåller alternativ" egentligen ä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 pekaren till Window och returnerar en sträng.
Slutligen ska vi kort titta på den interna strukturen i funktionen Data:
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}
Av koden framgår att ICO- och PNG-filer kräver kodning och avkodning. I övriga fall läggs endast alternativet -data till den sträng som omvandlats till bytetyp, för att markera att resultatet är utdata från funktionen Data, utan någon särskild omvandling.
Läsa in bilder med en menywidget
Genom att lägga till en menywidget till exemplen för inläsning av PNG- och ICO-filer kan man skapa ett program som visar önskade bilder.
Låt oss först titta på ett enkelt exempel med en menywidget.
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}

I detta exempel har vi prövat en menyrad och menyer, textmarkering, alternativet Command, bindning av kortkommandon samt inställning av programfönstrets initiala storlek.
Nu ska vi ändra funktionen Command som angetts för GetOpenFile, så att vi får ett program som läser in och visar bilder.
Planen för programmet är följande:
- Tillåt endast öppning av PNG- och ICO-filer
- Hantera filen som valts i fildialogrutan
- Implementera en widget för visning av bilden
Följande är kod 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}

Ovanstående kod fungerar på följande sätt:
- Kontrollerar filändelsen med strängjämförelsefunktionerna i paketet strings
- Öppnar, läser och stänger filen med hjälp av paketet os
- Den inlästa bilden visas i en Label-widget
Sammanfattning
I denna artikel har vi, med hjälp av Tcl/Tk-biblioteket för språket Go, behandlat följande områden:
- Grundstrukturen för GUI-applikationer
- Hantering av olika bildformat såsom SVG, PNG och ICO
- Packning av widgets och layouthantering
- Strukturen för bearbetning av bilddata
- Bindning av kortkommandon och widgetkommandon
Genom att använda Go och Tcl/Tk tillsammans kan man skapa enkla men praktiska GUI-program. Utgå från detta och ta dig an utvecklingen av mer komplexa GUI-applikationer.