GoSuda

Création d'une interface graphique avec Tk en Go

By Yunjin Lee
views ...

Python intègre nativement des bibliothèques GUI telles que Tkinter. Récemment, une bibliothèque Tk multiplateforme sans CGo a été développée pour permettre l'utilisation de Tcl/Tk avec le langage Go. Aujourd'hui, nous allons examiner son utilisation fondamentale.

Créer un "Hello, Tk"

Commençons par un exemple simple : "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}

Résultat de l'exécution de hello-tk


Examinons plus en détail le code d'exemple et le résultat de l'exécution ci-dessus.

Si vous avez déjà utilisé Tk avec Python, vous comprendrez la structure où les widgets sont "packés" à l'intérieur d'une fenêtre ou directement sous celle-ci. Des éléments tels que des étiquettes sont inclus dans les widgets, selon leur type.

Ipadx et Ipady sont des abréviations de "Internal padding", ajustant la marge intérieure des widgets. Dans cet exemple, la marge du bouton est ajustée.

Cette bibliothèque possède une structure Window, et une variable nommée App gère la fenêtre de niveau supérieur. Celle-ci est prédéfinie au sein de la bibliothèque. Par conséquent, la fonction tk.App.Destroy() qui termine tk.App.Wait() est responsable de la fermeture de la fenêtre de niveau supérieur.

Examinons maintenant quelques exemples tirés du dossier _examples de GitLab.

Traitement des fichiers SVG

Voici un exemple d'affichage d'un fichier SVG dans un widget 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}

Résultat de l'exécution de go-tk-svg

Le traitement des fichiers SVG dans cette bibliothèque s'effectue comme suit :

  1. Le contenu du fichier SVG est lu sous forme de chaîne de caractères (ou inclus directement comme dans l'exemple ci-dessus).
  2. Ce contenu est passé à la fonction Data pour être converti en une chaîne de caractères contenant des options (option -data).
  3. La valeur d'octet convertie est passée à la fonction NewPhoto, qui renvoie un pointeur de structure Img représentant une image Tcl/Tk.
  4. En passant par la fonction Image, le pointeur de structure Img est converti en une chaîne de caractères à laquelle l'option -Image est ajoutée.
  5. La raison de la conversion en une chaîne de caractères contenant la valeur RAW de la structure est la création du widget Label.

Les fichiers ICO et PNG sont traités de manière similaire.

Traitement des fichiers 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}

Résultat de l'exécution de go-tk-png

Le processus de traitement des fichiers PNG est le suivant :

  1. Le fichier gopher.png intégré est converti en un type de chaîne de caractères contenant des options.
  2. Il est converti en un type *Img via la fonction NewPhoto.
  3. Après être passé par la fonction Image et être converti en une chaîne de caractères RAW, il est utilisé pour créer un widget Label.

Les fichiers ICO sont traités de la même manière, la seule différence avec le format SVG réside dans la manière dont la fonction Data gère le traitement interne.

Examinons la nature de la "chaîne de caractères contenant des options" :

1type rawOption string

La chaîne de caractères contenant des options mentionnée précédemment n'est rien d'autre qu'une chaîne de caractères formatée.

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

La méthode optionString est une méthode pour un pointeur Window, qui renvoie une chaîne de caractères.

Enfin, examinons brièvement la structure interne de la fonction 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}

Le code indique que pour les fichiers ICO ou PNG, un processus d'encodage/décodage est nécessaire. Dans les autres cas, aucune conversion spéciale n'est effectuée, seule l'option -data est ajoutée à la chaîne de caractères convertie en type byte pour indiquer qu'il s'agit du résultat de la fonction Data.

Charger une image avec un widget de menu

En ajoutant un widget de menu aux exemples de chargement de PNG et d'ICO précédemment pratiqués, il est possible de créer une application qui affiche les images nécessaires.

Examinons d'abord un exemple simple de widget de 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}

Résultat de l'exécution de go-tk-menu-bar

Dans cet exemple, nous avons configuré une barre de menu et un menu, mis en évidence du texte, utilisé l'option Command, lié des raccourcis clavier et défini la taille initiale de la fenêtre de l'application.

Maintenant, nous allons modifier la fonction Command désignée par GetOpenFile pour créer un programme qui charge et affiche des images.

Le plan de développement du programme est le suivant :

  1. Restreindre l'ouverture aux seuls fichiers PNG et ICO.
  2. Traiter le fichier sélectionné dans la boîte de dialogue de fichier.
  3. Implémenter un widget pour l'affichage de l'image.

Voici le code qui reflète ce 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}

Résultat de l'exécution de go-tk-image-open

Le code ci-dessus fonctionne de la manière suivante :

  1. La fonction de comparaison de chaînes du package strings est utilisée pour vérifier l'extension du fichier.
  2. Le fichier est ouvert, lu puis fermé à l'aide du package os.
  3. L'image lue est affichée sous forme de widget Label.

Résumé

Dans cet article, nous avons abordé les points suivants en utilisant la bibliothèque Tcl/Tk du langage Go :

  1. Structure de base des applications GUI.
  2. Traitement de divers formats d'image tels que SVG, PNG, ICO.
  3. Gestion de l'empaquetage des widgets et de la mise en page.
  4. Structure de traitement des données d'image.
  5. Liaison de raccourcis clavier et commandes de widgets.

L'utilisation conjointe du langage Go et de Tcl/Tk permet de créer des programmes GUI simples mais pratiques. Nous vous encourageons à relever le défi du développement d'applications GUI plus complexes en vous appuyant sur ces bases.