---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Implementering av en bildevisar med flerfilslisteboks i Go og Tcl/Tk 9.0
description: Det implementeres en GUI-bildevisningsapplikasjon i modernc.org/tk9.0 ved å legge til bindinger for Tcl listbox-kommandoer og virtuelle hendelsesbehandlere, som muliggjør åpning av flere bildefiler, bytte av valg og sletting av listeelementer.
language: "no"
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

Forrige innlegg så på CGo-Free Tcl/Tk-biblioteket i korte trekk.
I dette innlegget skal vi bygge en bildevisning ved å videreutvikle eksemplet fra sist.

## Plan for bildevisningen

1. Bildevisningen fra forrige gang hadde ingen funksjon for å slette bilder, så vinduet ble for lite etter hvert som flere bilder ble lastet inn.
   La oss slette etiketter som ikke lenger brukes.
2. Hvis bildevisningen skal liste flere bilder, bør vi lage en liste.
3. Det er lurt å kunne laste inn flere bilder samtidig.
4. Vi implementerer også funksjonen for å fjerne bilder fra listen når de ikke skal vises.
5. La oss lage en funksjon der man kan velge og vise ett bestemt bilde blant flere.

## Endret Tcl/Tk 9.0-bibliotek

Det opprinnelige biblioteket har en mangelfull implementasjon av Listbox, og det er vanskelig å vise en bildeliste med det.
La oss laste ned det endrede biblioteket.
Hvis Git CLI ikke er installert, kan du i stedet laste ned tarball- eller zip-filen.

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

La oss først se på noen av de nye funksjonene.

Før vi ser på de nye funksjonene, skal vi først undersøke hvordan de eksisterende funksjonene er bygget opp. Vi tar utgangspunkt i funksjonen Destroy i tk.go på linje 1017.

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

Denne funksjonen er implementert ved å sende handlingen som et Tcl-skript til funksjonen evalErr.
Det betyr at for å implementere en ønsket funksjon er det tilstrekkelig å sende kommandoen i form av det tilsvarende Tcl-skriptet.

Som et eksempel implementerer vi en metode som legger til elementer i en listboks.
Først undersøker vi insert, en av kommandoene for listbox, i den offisielle manualen.

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

Av beskrivelsen av insert-kommandoen fremgår det at insert setter inn de oppførte elementene ved en bestemt indeks.
For å implementere dette kan vi skrive for eksempel:

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

Nå som vi har forstått det grunnleggende prinsippet, skal vi forklare de ekstra funksjonene for Listbox.

### Liste: innsetting/sletting

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

```

I programmet over setter AddItem elementene som er atskilt med mellomrom inn fortløpende fra indeks 0.
item1 til item3 får henholdsvis indeksene 0, 1 og 2.
La oss kjøre eksemplet for å se hvordan sletting av elementer fungerer.

### Liste: hente valgte elementer

Nå henter vi de valgte elementene fra Listbox og kontrollerer dem.

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

Metoden Selected henter indeksene til alle elementene som er valgt i Listbox.
Metoden GetOne henter verdien til elementet med den gitte indeksen.
Dette kan vi se av resultatet som skrives ut i konsollen.
Merk at den tilsvarende metoden Get henter verdiene til alle elementene innenfor et område, gitt ved start- og sluttindeks.

Deretter skal vi endre fargene på listboksen.

La oss først se på eksemplet nedenfor.

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

![Resultat av fargeinnstillinger](/assets/images/go-tk-imageviewer/listbox_color.png)

Som koden over viser, har høyden økt slik vi angav.
Vi ser også at fargene er brukt korrekt.
Her er det angitt et alternativ som gir bestemte elementer en annen farge, så det er bare den første linjen som har en annen farge.

Relief-metoden kan dessuten brukes til å endre stilen på kanten av en widget til flat, groove, raise, ridge, solid eller sunken. Forskjellen er ikke stor, men det er en mulighet.

## Eksempel på bildevisning

La oss nå bruke widgetene vi har lært om til å lage en bildevisning.
Eksempelprogrammet ser slik ut:

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

```

![Kjøring av bildevisningen](/assets/images/go-tk-imageviewer/imageviewer.png)

I dette eksemplet lages alle bildewidgetene på forhånd når bildene lastes inn, for å gjøre implementasjonen enkel, og det blir ikke kontrollert for duplikate filer.
De nevnte problemene kan forbedres, og du kan også prøve å endre temaet ved hjelp av DefaultTheme-metoden som er satt som kommentar.
Vi oppfordrer deg til å øve ved å lage et forbedret program med disse endringene.

## Oppsummering

I denne artikkelen så vi på hvordan kommandokall i Tcl/Tk-biblioteket for Go fungerer, og vi laget en bildevisning med listboks.

1. Metoden for kommandokall i Tcl/Tk-biblioteket
2. Bruk av listboks
3. Endring av egenskaper for listbox-widgeten
4. Utvikling av bildevisning

Vi oppfordrer deg til å prøve den samme tilnærmingen på andre biblioteker og å lage et komplett program med de nye funksjonene.
