---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Имплементация на многофайлов визуализатор на изображения с listbox на базата на Go и Tcl/Tk 9.0
description: Реализираме GUI визуализатор на изображения, който поддържа отваряне на множество файлове с изображения, превключване на избора и изтриване на елементи от списъка, като за целта добавяме обвързвания на Tcl listbox команди и обработчици на виртуални събития към modernc.org/tk9.0.
language: bg
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

Миналата публикация разгледа накратко CGo-Free библиотеката Tcl/Tk.
В тази част ще приложим примера от миналия път, за да създадем програма за преглед на изображения.

## План за програмата за преглед на изображения

1. Досегашният индикатор за изображения нямаше функция за изтриване на изображения, затова с добавянето на повече изображения прозорецът ставаше твърде малък.
   Нека изтрием етикетите, които не се използват.
2. Ако искаме да изброим няколко изображения в индикатора, нека създадем списък.
3. Добре е да можем да зареждаме няколко изображения едновременно.
4. Нека реализираме и премахване от списъка на изображения, които не искаме да гледаме.
5. Нека създадем функция за избор и преглед на определено изображение от множеството изображения.

## Модифицирана библиотека Tcl/Tk 9.0

Съществуващата библиотека има недостатъчна реализация на Listbox, поради което е трудно да се показва списък с изображения.
Нека изтеглим модифицираната библиотека.
Ако Git CLI не е инсталиран, можете да изтеглите архива tarball или zip файл.

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

Първо нека разгледаме някои от добавените функции.

Преди да разгледаме новите функции, нека с помощта на функцията Destroy в ред 1017 на tk.go накратко да проследим структурата на съществуващите функции.

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

Тази функция е реализирана чрез предаване на действието във формата на Tcl скрипт към функцията evalErr.
Това означава, че за реализиране на желаната функционалност е достатъчно да предадем команда във формата на съответния Tcl скрипт.

Като пример нека реализираме метод за добавяне на елемент към списъка.
Първо, за скриптиране на Tcl, нека разгледаме insert от командите, достъпни за listbox, в официалното ръководство.

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

От описанието на командата insert се вижда, че insert вмъква изброените елементи на определен индекс.
Тогава за да го реализираме, можем да напишем код като следния:

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

Сега, след като разбрахме приблизителния принцип на реализация, нека обясним допълнителните функции за Listbox.

### Списък: вмъкване/изтриване

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

```

В горната програма AddItem добавя отделените с интервал различни елементи последователно, започвайки от индекс 0.
item1-item3 получават съответно индекси 0, 1 и 2.
Нека изпълним примера, за да видим как работи изтриването на елементи.

### Списък: получаване на избраните елементи

Сега нека вземем избраните елементи от Listbox и ги проверим.

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

Методът Selected извлича индексите на всички избрани в момента елементи от Listbox.
Методът GetOne извлича стойността на елемента с дадения индекс.
Резултатът се вижда в конзолата.
За сведение, подобният метод Get получава начален и краен индекс и извлича стойностите на всички елементи в диапазона.

Следва да променим цветовете на списъка.

Първо нека разгледаме следния пример.

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

![색상 적용 결과](/assets/images/go-tk-imageviewer/listbox_color.png)

Както е написано в кода по-горе, височината се е увеличила.
Освен това цветовете са приложени правилно.
Тук е зададена опция за различни цветове само за определен елемент,
поради което се вижда, че само първият ред има различни цветове.

Също така, макар и без голяма разлика, с метода Relief можете да промените стила на рамката на уиджета сред flat, groove, raise, ridge, solid и sunken.

## Пример за програма за преглед на изображения

Сега нека създадем програма за преглед на изображения с помощта на току-що научените уиджети.
Примерната програма е следната:

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

```

![이미지 뷰어 실행 결과](/assets/images/go-tk-imageviewer/imageviewer.png)

В този пример, за да опростим реализацията, всички уиджети за изображения се създават предварително при зареждането им,
като не се проверява за дублиращи се файлове.
Можете да подобрите посочения по-горе проблем,
а също да опитате да промените темата, като използвате метода DefaultTheme, който е в коментар.
Препоръчваме да practicете, като създадете нова програма с подобрения в тези части.

## Обобщение

В тази статия разгледахме как работи извикването на команди в Tcl/Tk библиотеката за Go и създадохме програма за преглед на изображения с добавен списък (Listbox).

1. Начин на извикване на команди в Tcl/Tk библиотеката
2. Начин на използване на Listbox
3. Промяна на атрибутите на уиджета Listbox
4. Създаване на програма за преглед на изображения

Препоръчваме да опитате по същия начин да модифицирате и други библиотеки и да създадете завършена програма с добавените функции.
