---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Implementação de um visualizador de imagens baseado em listbox de múltiplos arquivos com Go e Tcl/Tk 9.0
description: Implementa-se um visualizador de imagens com interface gráfica, no modernc.org/tk9.0, mediante a adição de associações (bindings) de comandos Tcl listbox e de manipuladores de eventos virtuais, possibilitando a abertura de múltiplos arquivos de imagem, a alternância de seleção e a exclusão de itens da lista.
language: pt
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

No post anterior, examinamos brevemente a biblioteca CGo-Free Tcl/Tk.
Nesta vez, vamos aplicar o exemplo anterior para criar um visualizador de imagens.

## Plano do Visualizador de Imagens

1. O exibidor de imagens da vez anterior não tinha função de exclusão de imagens, por isso, à medida que se carregavam várias imagens, a janela ficava sem espaço.
   Vamos excluir os rótulos que não são utilizados.
2. Se o exibidor de imagens for listar várias imagens, vamos criar uma lista.
3. É recomendável carregar várias imagens de uma só vez.
4. Também implementaremos a remoção de imagens que não serão visualizadas da lista.
5. Vamos criar uma função para selecionar e visualizar uma imagem específica entre várias imagens.

## Biblioteca Tcl/Tk 9.0 Modificada

A biblioteca existente tem uma implementação insuficiente de Listbox, o que dificulta a exibição da lista de imagens.
Vamos baixar a biblioteca modificada.
Caso o Git CLI não esteja instalado, também é possível baixar o arquivo tarball ou zip.

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

Primeiro, vamos examinar algumas das funcionalidades adicionadas.

Antes de analisar as novas funções, vamos compreender de forma breve a estrutura das funções existentes por meio da função Destroy, localizada na linha 1017 de tk.go.

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

Esta função é implementada transmitindo uma ação à função evalErr no formato de script Tcl.
Isso significa que, para implementar a funcionalidade desejada, basta transmitir o comando no formato do script Tcl correspondente.

Como exemplo, vamos implementar um método que adiciona itens a um listbox.
Primeiro, para o script Tcl, vamos examinar o insert, entre os comandos disponíveis para listbox no manual oficial.

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

Na página de descrição do comando insert, verifica-se que insert insere os itens listados em um índice específico.
Assim, para implementar isso, podemos escrever um código como o seguinte:

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

Agora que compreendemos o princípio geral de implementação, vamos explicar as funcionalidades adicionais para Listbox.

### Lista: Inserção/Exclusão

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

```

No programa acima, AddItem insere, a partir do índice 0 e em ordem, os diferentes itens separados por espaço.
item1 a item3 recebem, respectivamente, os índices 0, 1 e 2.
Vamos executar o exemplo para observar como funciona a exclusão de itens.

### Lista: Obter Itens Selecionados

Agora, vamos obter os itens selecionados no Listbox e verificá-los.

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

O método Selected obtém os índices de todos os itens selecionados no Listbox atual.
O método GetOne obtém o valor do item correspondente ao índice informado.
O resultado é verificado pela saída no console.
Além disso, o método semelhante Get recebe os índices inicial e final e obtém todos os valores dos itens dentro desse intervalo.

Em seguida, vamos alterar as cores do listbox.

Primeiro, vejamos o exemplo abaixo.

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

![Resultado da aplicação de cores](/assets/images/go-tk-imageviewer/listbox_color.png)

Conforme escrito no código acima, a altura aumentou.
Além disso, pode-se observar que as cores foram aplicadas corretamente.
Aqui, foi especificada uma opção que define cores diferentes apenas para itens específicos,
por isso se nota que somente a primeira linha recebeu cores diferentes.

Também, embora não haja grande diferença, com o método Relief é possível alterar o estilo da borda do widget entre flat, groove, raise, ridge, solid e sunken.

## Exemplo de Visualizador de Imagens

Agora, vamos criar um visualizador de imagens usando os widgets que aprendemos.
O programa de exemplo é o seguinte.

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

```

![Resultado da execução do visualizador de imagens](/assets/images/go-tk-imageviewer/imageviewer.png)

Neste exemplo, para simplificar a implementação, todos os widgets de imagem são criados antecipadamente no momento do carregamento,
e não há verificação de arquivos duplicados.
É possível melhorar os problemas mencionados anteriormente,
e também experimentar alterar o tema usando o método DefaultTheme, que está na parte comentada.
Recomenda-se praticar criando um programa aprimorado com essas melhorias.

## Conclusão

Nesta publicação, analisamos como funciona a chamada de comandos da biblioteca Tcl/Tk na linguagem Go
e criamos um visualizador de imagens com lista (Listbox).

1. Método de chamada de comandos da biblioteca Tcl/Tk
2. Uso do Listbox
3. Alteração das propriedades do widget Listbox
4. Criação do visualizador de imagens

Recomenda-se seguir esse mesmo método para tentar modificar outras bibliotecas e escrever programas completos com as funcionalidades adicionadas.
