Création d'une GUI avec Tk en Go
Python intègre nativement des bibliothèques GUI telles que Tkinter. Récemment, une bibliothèque Tk multiplateforme, CGo-Free, a été développée pour permettre l'utilisation de Tcl/Tk dans le langage Go. Aujourd'hui, nous allons examiner ses utilisations fondamentales.
Création de « Hello, Tk »
Nous allons commencer 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}

Examinons plus en détail le code d'exemple et les résultats d'exécution ci-dessus.
Ceux qui ont de l'expérience avec Tk en Python comprendront la structure dans laquelle les widgets sont "packed" à l'intérieur d'une fenêtre ou directement sous celle-ci. Selon le type de widget, des labels, entre autres, y sont inclus.
Ipadx et Ipady sont des abréviations de "Internal padding", qui ajustent la marge des widgets internes. Dans cet exemple, la marge du bouton est ajustée.
Cette bibliothèque possède une structure Window, et la variable App gère la fenêtre de niveau supérieur. Celle-ci est prédéfinie à l'intérieur 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 trouvés dans le 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}

La méthode de traitement des fichiers SVG dans cette bibliothèque est la suivante :
- Le contenu du fichier SVG est lu sous forme de chaîne de caractères (ou inclus directement comme dans l'exemple ci-dessus).
- Ce contenu est passé à la fonction Data pour être converti en une chaîne de caractères contenant des options (option -data).
- La valeur de l'octet converti est passée à la fonction NewPhoto, qui renvoie un pointeur de structure Img représentant une image Tcl/Tk.
- En passant par la fonction Image, le pointeur de structure Img est converti en une chaîne de caractères à laquelle l'option -Image a été ajoutée.
- La raison de la conversion en une chaîne de caractères contenant les valeurs 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}

Le processus de traitement des fichiers PNG est le suivant :
- Le fichier gopher.png intégré est converti en un type de chaîne de caractères contenant des options.
- Il est converti en un type *Img via la fonction NewPhoto.
- Après être passé par la fonction Image et être converti en une chaîne RAW, il est créé en tant que widget de label.
Les fichiers ICO sont traités de la même manière, la seule différence avec le format SVG résidant dans la méthode de traitement interne de la fonction Data.
Examinons maintenant la nature de la « chaîne de caractères contenant des options » :
1type rawOption string
La chaîne de caractères mentionnée précédemment, contenant des options, n'est rien d'autre qu'une chaîne formatée.
1func (w *Window) optionString(_ *Window) string {
2 return w.String()
3}
La méthode optionString est une méthode pour le 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 montre que pour les fichiers ICO ou PNG, un processus d'encodage/décodage est nécessaire. Dans les autres cas, sans conversion particulière, seule l'option -data est ajoutée à la chaîne convertie en type byte pour indiquer qu'il s'agit du résultat de la fonction Data.
Chargement d'images avec le widget Menu
En ajoutant un widget Menu aux exemples de chargement de PNG et d'ICO précédemment pratiqués, nous pouvons créer une application qui affiche les images nécessaires.
Examinons d'abord un exemple simple de widget 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}

Dans cet exemple, nous avons configuré une barre de menus 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.
Modifions maintenant 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 :
- Restreindre l'ouverture aux seuls fichiers PNG et ICO.
- Traiter les fichiers sélectionnés dans la boîte de dialogue de fichier.
- Implémenter un widget pour l'affichage des images.
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}

Le code ci-dessus fonctionne de la manière suivante :
- Il vérifie l'extension du fichier à l'aide de la fonction de comparaison de chaînes du package strings.
- Il ouvre, lit puis ferme le fichier à l'aide du package os.
- L'image lue est affichée sous forme de widget de label.
Résumé
Dans cet article, nous avons exploré l'utilisation de la bibliothèque Tcl/Tk de Go pour les points suivants :
- Structure de base d'une application GUI.
- Traitement de divers formats d'image tels que SVG, PNG et ICO.
- Gestion du "packing" des widgets et de la mise en page.
- Structure de traitement des données d'image.
- Liaison de raccourcis clavier et commandes de widgets.
L'utilisation conjointe de Go et Tcl/Tk permet de créer des programmes GUI simples mais pratiques. Nous vous encourageons à vous lancer dans le développement d'applications GUI plus complexes sur cette base.