GoSuda

Implementatie van een Tcl/Tk-GUI in Go zonder CGo (modernc.org/tk9.0): widgetpacking en beeldweergave

By Yunjin Lee views ...

Python bevat standaard een GUI-bibliotheek zoals Tkinter. Onlangs is ook de CGo-Free, Cross Platform Tk-bibliotheek ontwikkeld, waarmee Tcl/Tk in de Go-taal kan worden gebruikt. Vandaag bekijken we het basisgebruik ervan.

Hello, Tk maken

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 uitvoerresultaat


Laten we de bovenstaande voorbeeldcode en het uitvoerresultaat nader bekijken.

Wie ervaring heeft met Tk in Python, zal de structuur herkennen waarin widgets in een venster worden geplaatst of rechtstreeks in een venster worden ingepakt. Afhankelijk van het type widget worden labels en dergelijke daarin opgenomen.

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

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

Laten we nu enkele voorbeelden bekijken in de map _examples op GitLab.

SVG-bestanden verwerken

Hieronder volgt een voorbeeld waarin een SVG-bestand in een Label-widget wordt weergegeven.

 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 uitvoerresultaat

Zo verwerkt deze bibliotheek SVG's:

  1. Lees de inhoud van het SVG-bestand in als een string (of neem deze direct op, zoals in het bovenstaande voorbeeld).
  2. Geef deze inhoud door aan de functie Data, die deze omzet naar een string met opties (-data-optie).
  3. De omgezette bytewaarden worden doorgegeven aan de functie NewPhoto, die een pointer naar de Img-structuur retourneert die een Tcl/Tk-afbeelding voorstelt.
  4. Bij doorgave via de functie Image wordt de Img-structuurpointer omgezet naar een string met de toegevoegde -Image-optie.
  5. De omzetting naar een string met de RAW-waarde van de structuur gebeurt om de Label-widget te kunnen aanmaken.

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 uitvoerresultaat

Het verwerkingsproces van PNG-bestanden verloopt als volgt:

  1. Zet het ingesloten gopher.png om naar een string-type met opties.
  2. Zet dit via de functie NewPhoto om naar het type *Img.
  3. Na de functie Image wordt het omgezet naar een RAW-string, waarna het als Label-widget wordt aangemaakt.

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

Laten we hier de aard van de "string met opties" bekijken:

1type rawOption string

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

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

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

Tot slot bekijken we kort de interne structuur van de functie 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}

Uit de code blijkt dat bij ICO- en PNG-bestanden een codeer- en decodeerproces nodig is. In andere gevallen wordt er geen speciale conversie uitgevoerd; er wordt alleen de -data-optie toegevoegd aan de naar bytes omgezette string, om aan te geven dat het resultaat van de functie Data is.

Afbeeldingen laden met een menuwidget

Door het menuwidget toe te voegen aan het eerder geoefende voorbeeld voor het laden van PNG- en ICO-bestanden, kunt u een applicatie maken die de gewenste afbeelding weergeeft.

Laten we eerst een eenvoudig voorbeeld van een menuwidget 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-menubalk uitvoerresultaat

In dit voorbeeld hebben we een menubalk en menu's gemaakt, tekst gemarkeerd, de Command-optie gebruikt, een sneltoets gekoppeld en de initiële grootte van het applicatievenster ingesteld.

Nu gaan we de Command-functie die aan GetOpenFile is gekoppeld aanpassen, zodat er een programma ontstaat dat afbeeldingen laadt en weergeeft.

Het plan voor het programma is als volgt:

  1. Beperk het openen tot alleen PNG- en ICO-bestanden
  2. Verwerk het bestand dat in het bestandsdialoogvenster is geselecteerd
  3. Implementeer een widget voor het weergeven van de afbeelding

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-afbeelding-openen uitvoerresultaat

De bovenstaande code werkt als volgt:

  1. Controleer de bestandsextensie met de stringvergelijkingsfunctie van het pakket strings
  2. Open, lees en sluit het bestand met het pakket os
  3. De ingelezen afbeelding wordt weergegeven in een Label-widget

Samenvatting

In dit artikel hebben we de Tcl/Tk-bibliotheek van de Go-taal gebruikt om de volgende onderwerpen te behandelen:

  1. De basisstructuur van een GUI-applicatie
  2. Verwerking van verschillende afbeeldingsformaten zoals SVG, PNG en ICO
  3. Het inpakken van widgets en het beheren van de lay-out
  4. De structuur voor het verwerken van afbeeldingsdata
  5. Sneltoetsbinding en widgetcommando's

Door Go en Tcl/Tk samen te gebruiken, kunt u eenvoudige maar praktische GUI-programma's maken. Op basis hiervan kunt u zich wagen aan de ontwikkeling van complexere GUI-applicaties.