GoSuda

Tvorba GUI s využitím Tk v Go

By Yunjin Lee
views ...

Python standardně obsahuje vestavěné GUI knihovny, jako je Tkinter. V nedávné době byla pro jazyk Go vyvinuta knihovna CGo-Free, Cross Platform Tk 라이브러리, která umožňuje využití Tcl/Tk. Dnes se zaměříme na její základní způsoby použití.

Vytvoření aplikace Hello, Tk

Nejprve zahájíme s jednoduchým příkladem „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}

Výsledek spuštění hello-tk


Detailně prozkoumejme výše uvedený ukázkový kód a výsledek jeho spuštění.

Osoby, které mají zkušenost s použitím Tk v Pythonu, porozumí struktuře, kde jsou widgety baleny (packed) uvnitř okna nebo přímo pod oknem. V závislosti na typu widgetu jsou v něm obsaženy například popisky (labels).

Ipadx a Ipady jsou zkratky pro Internal padding a slouží k úpravě okrajů (mezery) vnitřních widgetů. V tomto příkladu jsou upraveny okraje tlačítka.

Tato knihovna obsahuje strukturu Window a proměnná nazvaná App spravuje okno nejvyšší úrovně. Ta je v rámci knihovny předem definována. Funkce tk.App.Destroy(), která ukončuje tk.App.Wait(), tedy zajišťuje uzavření okna nejvyšší úrovně.

Nyní si prohlédněme několik příkladů, které se nacházejí ve složce _examples v GitLabu.

Zpracování souborů SVG

Následující příklad demonstruje zobrazení souboru SVG ve widgetu Label.

 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}

Výsledek spuštění go-tk-svg

Způsob zpracování SVG v této knihovně je následující:

  1. Obsah souboru SVG je načten jako řetězec (nebo je přímo vložen, jako v předchozím příkladu).
  2. Tento obsah je předán funkci Data, čímž se převede na řetězec obsahující možnosti (opci -data).
  3. Převedená bajtová hodnota je předána funkci NewPhoto, která vrací ukazatel na strukturu Img reprezentující obraz Tcl/Tk.
  4. Při průchodu funkcí Image je ukazatel na strukturu Img převeden na řetězec s přidanou volbou -Image.
  5. Důvodem konverze na řetězec obsahující RAW hodnotu struktury je umožnění vytvoření widgetu Label.

Podobným způsobem jsou zpracovávány i soubory ICO a PNG.

Zpracování souborů PNG

 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}

Výsledek spuštění go-tk-png

Proces zpracování souboru PNG je následující:

  1. Vložený soubor gopher.png se převede na typ řetězce obsahující možnosti.
  2. Převede se na typ *Img prostřednictvím funkce NewPhoto.
  3. Po průchodu funkcí Image se převede na RAW řetězec, načež je vytvořen jako widget Label.

Soubory ICO se zpracovávají shodným způsobem, přičemž rozdíl oproti formátu SVG spočívá pouze v interním zpracování uvnitř funkce Data.

Zde se podíváme na podstatu „řetězce obsahujícího možnosti“:

1type rawOption string

Dříve zmíněný řetězec obsahující možnosti je pouze formátovaný řetězec.

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

Metoda optionString je metodou pro ukazatel typu Window a vrací řetězec.

Nakonec se stručně podíváme na vnitřní strukturu funkce 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}

Při pohledu na kód je u souborů ICO či PNG nutný proces kódování/dekódování. V ostatních případech se bez zvláštní konverze přidá pouze volba -data k řetězci převedenému do bajtového formátu, čímž se označí jako výsledek funkce Data.

Načítání obrázků pomocí widgetu Menu

Přidáním widgetu Menu k dříve procvičeným příkladům načítání PNG a ICO můžeme vytvořit aplikaci schopnou zobrazit požadovaný obrázek.

Nejprve si prohlédněme jednoduchý příklad widgetu Menu.

 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}

Výsledek spuštění go-tk-menu-bar

V tomto příkladu jsme nastavili vytvoření menu baru a menu, zvýraznění písma, volbu Command, navázání klávesových zkratek a počáteční rozměry okna aplikace.

Nyní upravíme funkci Command určenou pomocí GetOpenFile, abychom vytvořili program pro načítání a zobrazení obrázku.

Plán vytvoření programu je následující:

  1. Omezení tak, aby bylo možné otevírat pouze soubory PNG a ICO.
  2. Zpracování souboru vybraného v dialogovém okně pro soubory.
  3. Implementace widgetu pro zobrazení obrázku.

Následuje kód, který tyto plány zohledňuje:

 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}

Výsledek spuštění go-tk-načítání-obrázku

Výše uvedený kód funguje následujícím způsobem:

  1. Kontroluje příponu souboru pomocí funkce pro porovnávání řetězců z balíčku strings.
  2. Používá balíček os k otevření, přečtení a následnému uzavření souboru.
  3. Přečtený obrázek je zobrazen jako widget Label.

Shrnutí

V tomto článku jsme s využitím knihovny Tcl/Tk jazyka Go probrali následující témata:

  1. Základní struktura GUI aplikace.
  2. Zpracování různých formátů obrázků, jako jsou SVG, PNG a ICO.
  3. Správa balení (packing) widgetů a rozvržení (layout).
  4. Struktura zpracování obrazových dat.
  5. Navázání klávesových zkratek a příkazy widgetů (widget commands).

Použitím jazyka Go společně s Tcl/Tk lze vytvořit jednoduché, avšak praktické GUI programy. Na základě toho doporučujeme pokusit se o vývoj složitějších GUI aplikací.