---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Implementering af en billedfremviser med flervalgsfilliste baseret på Go og Tcl/Tk 9.0
description: Vi implementerer en GUI-billedfremviser ved at tilføje Tcl listbox-kommandobindinger og virtuelle hændelseshandlere til modernc.org/tk9.0, hvilket muliggør åbning af flere billedfiler, skift af valg og sletning af listeelementer.
language: da
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

I det forrige indlæg gennemgik vi kort CGo-Free Tcl/Tk-biblioteket.
I dette indlæg vil vi bygge videre på eksemplet fra sidst og lave en billedfremviser.

## Plan for billedfremviseren

1. Billedvisningsprogrammet fra sidste gang har ingen funktion til at slette billeder, så vinduets størrelse slog til, når der blev indlæst flere billeder.
   Lad os slette de labels, der ikke længere bruges.
2. Hvis billedvisningsprogrammet skal vise flere billeder i en liste, lad os oprette en liste.
3. Det er en god idé at indlæse flere billeder på én gang.
4. Lad os også implementere funktionen til at fjerne billeder, som man ikke vil se, fra listen.
5. Lad os lave en funktion, hvor man kan vælge og se et bestemt billede blandt flere billeder.

## Modificeret Tcl/Tk 9.0-bibliotek

Det eksisterende bibliotek har en mangelfuld implementering af Listbox, hvilket gør det svært at vise en billedliste.
Lad os downloade det modificerede bibliotek.
Hvis Git CLI ikke er installeret, kan du også downloade det som tarball- eller zip-fil.

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

Lad os først se på nogle af de tilføjede funktioner.

Før vi kigger på de nye funktioner, lad os kort undersøge strukturen af de eksisterende funktioner ved hjælp af Destroy-funktionen i tk.go, linje 1017.

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

Denne funktion er implementeret ved at sende handlingen som et Tcl-script til funktionen evalErr.
Det betyder, at man blot skal sende kommandoen i form af det tilsvarende Tcl-script for at implementere den ønskede funktion.

Som eksempel lad os implementere en metode, der tilføjer et element til en listbox.
Lad os først se på insert-kommandoen blandt de kommandoer, der kan bruges med listbox, i den officielle manual.

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

På beskrivelsessiden for insert-kommandoen fremgår det, at insert indsætter de angivne elementer ved et bestemt indeks.
For at implementere dette kan vi skrive en kode som:

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

Nu hvor vi har en grov forståelse af implementeringsprincippet, lad os gennemgå de yderligere funktioner til Listbox.

### Liste: indsættelse/sletning

```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 ovenstående program indsætter AddItem de forskellige elementer, der er adskilt af mellemrum, fra indeks 0 og fremefter.
item1-item3 får henholdsvis indeks 0, 1 og 2.
Lad os køre eksemplet for at se, hvordan sletning af elementer fungerer.

### Liste: hent valgte elementer

Lad os nu hente de elementer, der er valgt i Listbox, og kontrollere 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 indekserne for alle elementer, der er valgt i den aktuelle Listbox.
Metoden GetOne henter værdien for elementet med det tilsvarende indeks.
Resultatet kan ses i konsolen.
Bemærk, at den lignende metode Get tager et start- og slutindeks og henter værdierne for alle elementer inden for intervallet.

Dernæst lad os ændre farverne på listboxen.

Lad os 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 af farveanvendelse](/assets/images/go-tk-imageviewer/listbox_color.png)

Som angivet i koden ovenfor er højden øget.
Det ses også, at farverne er blevet anvendt korrekt.
Her er der angivet en indstilling, der kun ændrer farven for bestemte elementer, og derfor ses det, at kun den første linje har en anden farve.

Der er desuden en lille forskel, når man bruger metoden Relief, som gør det muligt at ændre stilen på widgetens kant til en af følgende: flat, groove, raise, ridge, solid eller sunken.

## Eksempel på billedfremviser

Lad os nu bruge de widgets, vi lige har lært, til at lave en billedfremviser.
Eksempelprogrammet er som følger.

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

```

![Billedfremviserens kørselsresultat](/assets/images/go-tk-imageviewer/imageviewer.png)

I dette eksempel er alle billedwidgets oprettet på forhånd, når de indlæses, for at gøre implementeringen enkel, og der kontrolleres ikke for duplikerede filer.
De ovenfor nævnte problemer kan forbedres, og den udkommenterede del, DefaultTheme-metoden, kan bruges til at ændre temaet.
Vi opfordrer dig til at øve dig ved at lave et forbedret program med disse ændringer.

## Opsummering

I denne artikel har vi set, hvordan kommandokald i Tcl/Tk-biblioteket til Go fungerer, og vi har lavet en billedfremviser med en listbox.

1. Metode til kommandokald i Tcl/Tk-biblioteket
2. Brug af listbox
3. Ændring af egenskaber for listbox-widgetten
4. Udvikling af en billedfremviser

Vi opfordrer dig til at prøve at ændre andre biblioteker på samme måde og at skrive et færdigt program med de funktioner, du har tilføjet.
