---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Implementering av en bildvisare med flerfilslistbox i Go och Tcl/Tk 9.0
description: Vi implementerar en GUI-bildvisare i modernc.org/tk9.0 genom att lägga till bindningar för Tcl-listbox-kommandon och virtuella händelsehanterare, vilket möjliggör öppning av flera bildfiler, byte av val samt borttagning av listobjekt.
language: sv
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

I föregående inlägg tittade vi kort på det CGo-fria Tcl/Tk-biblioteket.
I detta avsnitt ska vi tillämpa exemplet från förra gången för att skapa en bildvisare.

## Plan för bildvisaren

1. Bildvisaren från förra gången saknade funktion för att ta bort bilder, så fönstrets storlek räckte inte till när fler bilder lästes in.
   Låt oss ta bort etiketter som inte används.
2. Om bildvisaren ska lista flera bilder, låt oss skapa en lista.
3. Det är bra att kunna läsa in flera bilder på en gång.
4. Vi implementerar även funktionen att ta bort bilder som inte ska visas från listan.
5. Låt oss skapa en funktion för att välja och visa en specifik bild bland flera bilder.

## Modifierat Tcl/Tk 9.0-bibliotek

Det befintliga biblioteket har bristfälligt stöd för Listbox, vilket gör det svårt att visa en bildlista.
Låt oss ladda ner det modifierade biblioteket.
Om Git CLI inte är installerat kan du också ladda ner det som tarball eller zip-fil.

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

Låt oss först titta på några av de tillagda funktionerna.

Innan vi undersöker de nya funktionerna, låt oss kort förstå strukturen hos de befintliga funktionerna genom Destroy-funktionen på rad 1017 i tk.go.

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

Denna funktion är implementerad genom att skicka en åtgärd i form av ett Tcl-skript till funktionen evalErr.
Det innebär att för att implementera önskad funktion räcker det att skicka kommandot i formatet för motsvarande Tcl-skript.

Som ett exempel implementerar vi en metod som lägger till ett objekt i en listbox.
Först tittar vi i den officiella manualen på insert, ett av de kommandon som kan användas för listbox.

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

På beskrivningssidan för insert framgår att insert infogar de angivna objekten vid ett specifikt index.
För att implementera detta kan vi därför skriva kod såsom

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

Nu när vi förstått den grundläggande implementeringsprincipen, förklarar vi först ytterligare funktioner för Listbox.

### Lista: infogning/borttagning

```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 ovan lägger AddItem in de olika objekten, separerade med mellanslag, i följd från index 0.
item1 till item3 får därmed indexen 0, 1 respektive 2.
Vi kör exemplet för att se hur borttagning av objekt fungerar.

### Lista: hämta valda objekt

Nu hämtar vi de objekt som valts i Listbox och kontrollerar 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 hämtar indexen för alla objekt som är valda i den aktuella Listbox.
Metoden GetOne hämtar värdet för objektet på det angivna indexet.
Vi kan se resultatet i konsolutskriften.
Observera att den liknande metoden Get tar emot start- och slutindex och hämtar värdena för alla objekt inom intervallet.

Därefter ska vi ändra färgerna på listboxen.

Låt oss först titta på exemplet nedan.

```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 med färgtillämpning](/assets/images/go-tk-imageviewer/listbox_color.png)

Som koden ovan anger har höjden ökat.
Dessutom ser vi att färgerna har tillämpats korrekt.
Eftersom en inställning för färg endast för specifika objekt har angetts, ser vi att endast första raden har en annan färg.

Även om skillnaden inte är stor, kan metoden Relief användas för att ändra kantlinjens stil för widgeten bland flat, groove, raise, ridge, solid och sunken.

## Exempel på bildvisare

Låt oss nu använda de widgetar vi just lärt oss för att skapa en bildvisare.
Exempelprogrammet ser ut så här.

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

```

![Körresultat för bildvisaren](/assets/images/go-tk-imageviewer/imageviewer.png)

I detta exempel skapas alla bildwidgetar i förväg när bilderna läses in, för att göra implementeringen enkel, och dubbletter av filer kontrolleras inte.
Du kan förbättra de problem som nämnts ovan, och du kan också prova att ändra temat genom att använda metoden DefaultTheme som är utkommenterad.
Prova gärna att skriva ett nytt program med dessa förbättringar för att öva.

## Sammanfattning

I detta inlägg har vi undersökt hur kommandoanrop i Tcl/Tk-biblioteket för Go fungerar, och skapat en bildvisare med listbox.

1. Metod för kommandoanrop i Tcl/Tk-biblioteket
2. Användning av listbox
3. Ändring av egenskaper för listbox-widgeten
4. Skapande av en bildvisare

Vi uppmuntrar dig att på liknande sätt utmana dig själv med att modifiera andra bibliotek och skriva ett färdigt program med de funktioner du lagt till.
