---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Implémentation d'une visionneuse d'images à liste multi-fichiers basée sur Go et Tcl/Tk 9.0
description: Implémentation d'une visionneuse d'images dotée d'une interface graphique, permettant l'ouverture de plusieurs fichiers image, la commutation de sélection et la suppression d'éléments de liste, par l'ajout de liaisons de commandes Tcl listbox et de gestionnaires d'événements virtuels à modernc.org/tk9.0.
language: fr
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

Dans le précédent article, nous avons brièvement examiné la bibliothèque CGo-Free Tcl/Tk.
Dans cette séance, nous allons adapter l'exemple de la fois précédente pour créer une visionneuse d'images.

## Plan de la visionneuse d'images

1. L'afficheur d'images de la dernière fois ne disposait d'aucune fonction de suppression d'images ; plus on chargeait d'images, plus la fenêtre manquait de place.
   Supprimons les étiquettes inutilisées.
2. Si l'afficheur d'images doit présenter plusieurs images sous forme de liste, créons une liste.
3. Il est préférable de charger plusieurs images en une seule fois.
4. Implémentons également la possibilité de retirer de la liste les images que l'on ne souhaite pas consulter.
5. Créons une fonction permettant de sélectionner et d'afficher une image particulière parmi plusieurs images.

## Bibliothèque Tcl/Tk 9.0 modifiée

La bibliothèque existante ne propose qu'une implémentation insuffisante du Listbox, ce qui rend difficile l'affichage d'une liste d'images.
Téléchargeons la bibliothèque modifiée.
Si Git CLI n'est pas installé, vous pouvez également télécharger l'archive tarball ou zip.

```bash
git clone https://github.com/gg582/tk9.0
```

Examinons d'abord quelques-unes des fonctions ajoutées.

Avant d'étudier les nouvelles fonctions, voyons la structure des fonctions existantes en analysant brièvement la fonction Destroy située à la ligne 1017 de tk.go.

```go
func Destroy(options ...Opt) {
	evalErr(fmt.Sprintf("destroy %s", collect(options...)))
}
```

Cette fonction est implémentée en transmettant une action, sous forme de script Tcl, à la fonction evalErr.
Autrement dit, pour réaliser la fonction souhaitée, il suffit de transmettre la commande au format du script Tcl correspondant.

À titre d'exemple, implémentons une méthode qui ajoute un élément à une listbox.
Tout d'abord, pour le scripting Tcl, consultons la commande insert parmi les commandes utilisables sur un listbox dans le manuel officiel.

[Commande insert](https://www.tcl.tk/man/tcl8.6/TkCmd/listbox.htm#M29)

D'après la page de description de la commande insert, insert insère les éléments énumérés à un index spécifié.
Pour l'implémenter, on peut donc écrire un code tel que :

```go
func (l *ListboxWidget) AddItem(index int, items string) {
	evalErr(fmt.Sprintf("%s insert %d %s", l.fpath, index, items))
}
```

Maintenant que nous avons compris le principe général de l'implémentation, présentons les fonctions supplémentaires du Listbox.

### Liste : insertion et suppression

```go
package main
import . "modernc.org/tk9.0"

func main() {
    length := 3
    l := Listbox()
    l.AddItem(0, "item1 item2 item3")
    b1 := TButton(Txt("Delete Multiple Items, index (0-1)"), Command( func(){
        if length >= 2 {
            l.DeleteItems(0,1)
            length-=2
        }
    }))
    b2 := TButton(Txt("Delete One Item, index (0)"), Command( func () {
        if length > 0 {
            l.DeleteOne(0)
            length-=1
        }
    }))
    Pack(TExit(),l,b1,b2)
    App.Wait()
}

```

Dans le programme ci-dessus, AddItem insère, à partir de l'index 0, les différents éléments séparés par des espaces.
Les éléments item1 à item3 reçoivent respectivement les index 0, 1 et 2.
Exécutons l'exemple pour observer le fonctionnement de la suppression d'éléments.

### Liste : obtention des éléments sélectionnés

Récupérons maintenant les éléments sélectionnés dans le Listbox et vérifions-les.

```go
package main

import . "modernc.org/tk9.0"

func main() {
    l := Listbox()
    l.SelectMode("multiple")
    l.AddItem(0, "item1 item2 item3")
    btn := TButton(Txt("Print Selected"), Command( func() {
        sel := l.Selected()
        for _, i := range sel {
            println(l.GetOne(i))
        }
    }))

    Pack(TExit(), l, btn)
    App.Wait()
}
```

La méthode Selected récupère les index de tous les éléments actuellement sélectionnés dans le Listbox.
La méthode GetOne récupère la valeur de l'élément correspondant à l'index donné.
Le résultat affiché dans la console permet de le vérifier.
À noter que la méthode analogue Get reçoit un index de début et un index de fin, et récupère les valeurs de tous les éléments de la plage.

Modifions maintenant les couleurs du listbox.

Examinons d'abord l'exemple suivant.

```go
package main

import . "modernc.org/tk9.0"

func main() {
    l := Listbox()
    l.Background("blue")
    l.Foreground("yellow")
    l.SelectBackground("black")
    l.SelectForeground("white")
    l.Height(20)
    l.Width(6)
    l.AddItem(0, "item1 item2 item3")
    l.ItemForeground(0,"red")
    l.ItemBackground(0,"green")
    l.ItemSelectBackground(0,"white")
    l.ItemSelectForeground(0,"black")
    l.Relief("ridged")
    Pack(TExit(),l)
    App.Wait()
}
```

![Résultat de l'application des couleurs](/assets/images/go-tk-imageviewer/listbox_color.png)

Comme indiqué dans le code ci-dessus, la hauteur a augmenté.
On constate également que les couleurs ont bien été appliquées.
Ici, une option permettant de définir une couleur différente pour un élément particulier a été spécifiée,
c'est pourquoi seule la première ligne présente une couleur différente.

Par ailleurs, bien que la différence ne soit pas importante, la méthode Relief permet de modifier le style de la bordure du widget parmi flat, groove, raise, ridge, solid et sunken.

## Exemple de visionneuse d'images

Créons maintenant une visionneuse d'images à l'aide des widgets que nous venons d'étudier.
Voici le programme d'exemple :

```go
package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "strings"
    "path"

    . "modernc.org/tk9.0"
)

var pbuttons []*TButtonWidget
var extensions []FileType
var pbutton *TButtonWidget = nil
var listbox, listbox2 *ListboxWidget
var cur *LabelWidget = nil
var imagesLoaded []*LabelWidget
func PhotoName(fileName string) string {
        fileName = path.Base(fileName)
        return fileName[:len(fileName)-len(path.Ext(fileName))]
}

func handleFileOpen() {
    res := GetOpenFile(Multiple(true),Filetypes(extensions)) //다중 선택을 활성화하고 필터를 켭니다.
    s := make([]string,0,1000)
    for _, itm := range res {
        if itm != "" {
            tmp := strings.Split(itm," ")
            s = append(s,tmp...)
        }
    }

    for _, photo := range s {
        formatCheck := strings.Split(photo, ".")
        format := formatCheck[len(formatCheck)-1]

        if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
            picFile, err := os.Open(photo)
            if err != nil {
                log.Println("Error while opening photo, error is: ", err)
            }

            pictureRawData := make([]byte, 10000*10000)
            picFile.Read(pictureRawData)

            imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
                        imagesLoaded = append(imagesLoaded,imageLabel)
            var deleteTestButton *TButtonWidget
            deleteTestButton = TButton(
                Txt("Unshow Image"),
            Command(func() {
                GridForget(imageLabel.Window)
                GridForget(deleteTestButton.Window)
            }))

            pbuttons = append(pbuttons,deleteTestButton)

                        listbox.AddItem(len(imagesLoaded)-1,PhotoName(photo))
                        listbox2.AddItem(len(imagesLoaded)-1,PhotoName(photo))
            picFile.Close()
        }
    }
}

func DeleteSelected () {
    s:=listbox.Selected()
    if len(s) == 0 {
        return
        }
    for _, i := range s {
        listbox.DeleteOne(i)
        listbox2.DeleteOne(i)
        if len(imagesLoaded)-1>i {
            continue
        }
        if cur == imagesLoaded[i] {
            pbutton = nil
            cur = nil
        }
        Destroy(imagesLoaded[i])
        Destroy(pbuttons[i])
                imagesLoaded = append(imagesLoaded[:i],imagesLoaded[i+1:]...)
        pbuttons = append(pbuttons[:i], pbuttons[i+1:]...)
    }
}

func SelectImage() {
        s:=listbox2.Selected()
        if len(s) == 0 {
                return
        }

    if len(imagesLoaded) -1 < s[0] {
        return
    }
    if imagesLoaded[s[0]] == nil {
        return
    }
    if cur != nil {
            GridForget(cur.Window)
    }
    if pbutton != nil {
        GridForget(pbutton.Window)
    }

        Grid(imagesLoaded[s[0]], Row(0), Column(2))
    Grid(pbuttons[s[0]], Row(0), Column(3))
    cur = imagesLoaded[s[0]]
    pbutton = pbuttons[s[0]]
}

func SelectIndex(index int) {

    if len(imagesLoaded) -1 <index {
        return
    }
    if imagesLoaded[index] == nil {
        return
    }
    if cur != nil {
            GridForget(cur.Window)
    }
    if pbutton != nil {
        GridForget(pbutton.Window)
    }

        Grid(imagesLoaded[index], Row(0), Column(2))
    Grid(pbuttons[index], Row(0), Column(3))
    cur = imagesLoaded[index]
    pbutton = pbuttons[index]
}

func main() {
    menubar := Menu()
    //DefaultTheme("awdark","themes/awthemes-10.4.0")
    //테마를 사용하고 싶을 때에는 테마 명과 경로를 지정해 줍니다.
    fileMenu := menubar.Menu()
    extensions = make([]FileType,0,1)
    extensions = append(extensions, FileType{ "Supported Images", []string {".png",".ico"}, "" } )
    //필터에 png와 ico를 넣어 줍니다.
    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func () {
        handleFileOpen()
        SelectIndex(len(imagesLoaded)-1)
    } ))
    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
        imagesLoaded = make([]*LabelWidget, 0, 10000)
    pbuttons = make([]*TButtonWidget,0,10000)
    var scrollx, scroll, scroll2, scrollx2 *TScrollbarWidget
        listbox = Listbox(Yscrollcommand(func(e *Event) { e.ScrollSet(scroll)}) , Xscrollcommand( func(e *Event) { e.ScrollSet(scrollx)}))
        listbox2 = Listbox(Yscrollcommand(func(e *Event) { e.ScrollSet(scroll2)}), Xscrollcommand(func(e *Event) { e.ScrollSet(scrollx2)}))
        listbox.SelectMode("multiple")
        listbox2 = Listbox()
        listbox.Background("white")
        listbox.SelectBackground("blue")
        listbox.SelectForeground("yellow")
        listbox2.Background("grey")
        listbox2.SelectBackground("green")
    listbox2.SelectForeground("blue")
    listbox2.SelectBackground("brown")
        listbox.Height(5)
        listbox.Width(4)
        listbox2.Height(5)
        listbox2.Width(4)
        delBtn := Button(Txt("Delete"), Command(func () { DeleteSelected() }))
        selBtn := Button(Txt("Select"), Command(func () { SelectImage() }))
        scroll = TScrollbar(Command(func(e *Event) { e.Yview(listbox) }))
        scrollx = TScrollbar(Orient("horizontal"),Command(func(e *Event) { e.Xview(listbox) }))
    scroll2 = TScrollbar(Command(func(e *Event) { e.Yview(listbox2) }))
        scrollx2 = TScrollbar(Orient("horizontal"),Command(func(e *Event) { e.Xview(listbox2) }))
        Grid(listbox,Row(1),Column(0), Sticky("nes"))
        Grid(scroll,Row(1),Column(1), Sticky("nes"))
    Grid(scrollx,Row(2),Column(0),  Sticky("nes"))
        Grid(delBtn,Row(3),Column(0), Sticky("nes"))
        Grid(listbox2,Row(1),Column(2), Sticky("nes"))
        Grid(scroll2,Row(1),Column(3), Sticky("nes"))
    Grid(scrollx2,Row(2),Column(2), Sticky("nes"))
        Grid(selBtn,Row(3),Column(2), Sticky("nes"))
    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
    App.Configure(Mnu(menubar), Width("80c"), Height("60c")).Wait()
}

```

![Résultat d'exécution de la visionneuse d'images](/assets/images/go-tk-imageviewer/imageviewer.png)

Dans cet exemple, afin de simplifier l'implémentation, tous les widgets d'image sont créés à l'avance lors du chargement, et les fichiers en double ne sont pas vérifiés.
Vous pouvez améliorer les problèmes évoqués précédemment,
et essayer de modifier le thème en utilisant la méthode DefaultTheme, dont la partie est commentée.
Nous vous encourageons à vous exercer en créant un programme amélioré intégrant ces points.

## Synthèse

Dans cet article, nous avons vu comment fonctionne l'appel de commandes de la bibliothèque Tcl/Tk en Go, et nous avons créé une visionneuse d'images dotée d'un listbox.

1. Méthode d'appel de commandes de la bibliothèque Tcl/Tk
2. Utilisation du listbox
3. Modification des propriétés du widget listbox
4. Création d'une visionneuse d'images

Nous vous invitons à appliquer la même démarche pour modifier d'autres bibliothèques, et à réaliser un programme complet à partir des fonctions que vous aurez ajoutées.
