---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Implementatie van een op meerdere bestanden gebaseerde afbeeldingsviewer met een listbox in Go en Tcl/Tk 9.0
description: Er wordt een GUI-beeldviewer geïmplementeerd in modernc.org/tk9.0 door Tcl listbox-commando's, bindings en virtuele-gebeurtenishandlers toe te voegen, waarmee meerdere afbeeldingsbestanden kunnen worden geopend, selecties kunnen worden gewisseld en lijstitems kunnen worden verwijderd.
language: nl
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

In het vorige bericht hebben we kort gekeken naar de CGo-vrije Tcl/Tk-bibliotheek.
In dit bericht gaan we het vorige voorbeeld uitbreiden om een beeldviewer te maken.

## Plan voor de beeldviewer

1. De beeldweergave van de vorige keer had geen functie om afbeeldingen te verwijderen, dus naarmate er meer afbeeldingen werden geladen, was het venster te klein.
   Laten we labels die niet meer worden gebruikt, verwijderen.
2. Als we de geladen afbeeldingen in de beeldweergave als lijst willen tonen, laten we dan een lijst maken.
3. Het is handig om meerdere afbeeldingen tegelijk te kunnen laden.
4. Laten we ook de functie implementeren om afbeeldingen die we niet meer willen bekijken uit de lijst te verwijderen.
5. Laten we een functie maken waarmee we een specifieke afbeelding uit meerdere afbeeldingen kunnen selecteren en bekijken.

## Gewijzigde Tcl/Tk 9.0-bibliotheek

In de oorspronkelijke bibliotheek is de implementatie van Listbox onvoldoende, waardoor het moeilijk is om een lijst met afbeeldingen weer te geven.
Laten we de gewijzigde bibliotheek downloaden.
Als Git CLI niet is geïnstalleerd, kun je ook het tarball- of zip-bestand downloaden.

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

Laten we eerst enkele van de toegevoegde functies bekijken.

Voordat we naar de nieuwe functies kijken, laten we eerst aan de hand van de functie Destroy in regel 1017 van tk.go kort de structuur van de bestaande functies doorgronden.

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

Deze functie is geïmplementeerd door de actie als Tcl-script door te geven aan de functie evalErr.
Dat betekent dat we, om de gewenste functionaliteit te implementeren, alleen het overeenkomstige commando in de vorm van een Tcl-script hoeven door te geven.

Als voorbeeld implementeren we een methode die items aan een listbox toevoegt.
Laten we eerst in de officiële handleiding kijken naar insert, een van de commando's die voor listbox beschikbaar zijn, voor het Tcl-scripting.

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

Op de beschrijvingspagina van het insert-commando zien we dat insert de opgegeven items invoegt op een bepaalde index.
Om dit te implementeren kunnen we dan code schrijven zoals:

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

Nu we het globale implementatieprincipe begrijpen, lichten we eerst de extra functionaliteit voor Listbox toe.

### Lijst: invoegen/verwijderen

```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()
}

```

In het bovenstaande programma voegt AddItem de items, gescheiden door spaties, in vanaf index 0.
item1 tot en met item3 krijgen respectievelijk de indexen 0, 1 en 2.
Laten we het voorbeeld uitvoeren om te zien hoe het verwijderen van items werkt.

### Lijst: geselecteerde items ophalen

Laten we nu de geselecteerde items uit de Listbox ophalen en controleren.

```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()
}
```

De methode Selected haalt de indexen op van alle items die momenteel in de Listbox zijn geselecteerd.
De methode GetOne haalt de waarde op van het item met de overeenkomstige index.
Dit is te zien aan de resultaten die in de console worden afgedrukt.
Ter informatie: de vergelijkbare methode Get haalt, met een start- en eindindex, de waarden van alle items binnen dat bereik op.

Vervolgens gaan we de kleuren van de listbox aanpassen.

Laten we eerst het onderstaande voorbeeld bekijken.

```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()
}
```

![Resultaat van kleurtoepassing](/assets/images/go-tk-imageviewer/listbox_color.png)

Zoals in de bovenstaande code is opgegeven, is de hoogte toegenomen.
Ook is te zien dat de kleuren correct zijn toegepast.
Doordat er een optie is ingesteld die alleen voor een specifiek item een andere kleur bepaalt, is te zien dat alleen de eerste regel een andere kleur heeft gekregen.

Hoewel het verschil niet groot is, kun je met de methode Relief de stijl van de widgetrand wijzigen in een van de volgende: flat, groove, raise, ridge, solid of sunken.

## Voorbeeld van een beeldviewer

Laten we nu met de zojuist geleerde widgets een beeldviewer maken.
Het voorbeeldprogramma ziet er als volgt uit.

```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()
}

```

![Resultaat van de beeldviewer](/assets/images/go-tk-imageviewer/imageviewer.png)

Om de implementatie eenvoudig te houden, worden in dit voorbeeld alle afbeeldingswidgets vooraf aangemaakt wanneer de afbeeldingen worden geladen, en wordt er niet gecontroleerd op dubbele bestanden.
Je kunt de eerder genoemde problemen verbeteren, en ook het thema wijzigen door de als commentaar gemarkeerde DefaultTheme-methode te gebruiken.
Oefen door een programma te maken waarin deze onderdelen zijn verbeterd.

## Samenvatting

In dit artikel hebben we bekeken hoe de commando-aanroepen van de Tcl/Tk-bibliotheek voor Go werken, en hebben we een beeldviewer gemaakt met een toegevoegde listbox.

1. Methode voor het aanroepen van commando's in de Tcl/Tk-bibliotheek
2. Gebruik van de listbox
3. Wijzigen van eigenschappen van de listbox-widget
4. Het maken van een beeldviewer

Probeer op dezelfde manier ook andere bibliotheken aan te passen, en schrijf een voltooid programma met de toegevoegde functionaliteit.
