---
id: 974f36e12dfcece861bfda1af9303980
author: Yunjin Lee
title: 'Implementação de GUI em Go Tcl/Tk sem CGo (modernc.org/tk9.0): empacotamento de widgets e renderização de imagens'
description: Implementa a criação de janelas, a disposição (packing) de widgets de botão e barra de menus, e a renderização de imagens SVG/PNG/ICO em Go, utilizando a biblioteca multiplataforma modernc.org/tk9.0, que não possui dependências de CGo.
language: pt
date: 2024-10-22T11:51:27.614782351Z
path: /blog/posts/creating-a-gui-with-tk-in-go-z004dd008
category: gui
lang_canonical:
    ko: https://blog.naver.com/bugaku/223629101405
---

Python possui bibliotecas de GUI integradas por padrão, como o Tkinter.
Recentemente, foi desenvolvida a [CGo-Free, Cross Platform Tk 라이브러리](https://pkg.go.dev/modernc.org/tk9.0), que permite usar Tcl/Tk também na linguagem Go. Hoje, vamos examinar seus fundamentos de uso.

## Criando um Hello, Tk

Vamos começar com um exemplo simples de 'Hello, TK!'.

```go
package main

import tk "modernc.org/tk9.0"

func main() {
    tk.Pack(
        tk.TButton(
            tk.Txt("Hello, TK!"),
            tk.Command(func() {
                tk.Destroy(tk.App)
            })),
        tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10),
    )
    tk.App.Wait()
}
```

![resultado da execução de hello-tk](/assets/images/go-tk-helloworld-svg-etc/go-tk-hello.png)

---

Vamos examinar detalhadamente o código de exemplo acima e seu resultado de execução.

Para quem já tem experiência com Tk em Python, a estrutura em que widgets são empacotados dentro de uma janela ou diretamente sob ela será familiar. Dependendo do tipo de widget, rótulos e outros elementos são incluídos dentro dele.

_Ipadx e Ipady são abreviações de Internal padding, que ajustam o espaçamento interno dos widgets. Neste exemplo, o espaçamento do botão é ajustado._

Esta biblioteca possui a estrutura Window, e a variável App gerencia a janela de nível superior. Ela já está predefinida dentro da biblioteca. Assim, a função tk.App.Destroy(), que encerra tk.App.Wait(), exerce o papel de fechar a janela de nível superior.

Agora, vamos examinar alguns exemplos presentes na pasta \_examples do GitLab.

## Processando arquivos SVG

A seguir, um exemplo que exibe um arquivo SVG em um widget de rótulo.

```go
package main

import . "modernc.org/tk9.0"

// https://en.wikipedia.org/wiki/SVG
const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="391" height="391" viewBox="-70.5 -70.5 391 391" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
<g opacity="0.8">
    <rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
    <circle cx="125" cy="125" r="75" fill="orange" />
    <polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
    <line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
</g>
</svg>`

func main() {
    Pack(Label(Image(NewPhoto(Data(svg)))),
        TExit(),
        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
    App.Center().Wait()
}
```

![resultado da execução de go-tk-svg](/assets/images/go-tk-helloworld-svg-etc/go-tk-svg.png)

O processo de tratamento de SVG nesta biblioteca é o seguinte:

1. Lê o conteúdo do arquivo SVG como uma string (ou o inclui diretamente, como no exemplo acima).
2. Passa esse conteúdo para a função Data, convertendo-o em uma string contendo opções (opção -data).
3. Os bytes convertidos são passados para a função NewPhoto, que retorna um ponteiro para a estrutura Img, representando uma imagem Tcl/Tk.
4. Ao passar pela função Image, o ponteiro da estrutura Img é convertido em uma string à qual é adicionada a opção -Image.
5. A conversão para uma string contendo o valor RAW da estrutura se deve à criação do widget Label.

Arquivos ICO e PNG também são processados de maneira semelhante.

## Processando arquivos PNG

```go
package main

import _ "embed"
import . "modernc.org/tk9.0"

//go:embed gopher.png
var gopher []byte

func main() {
    Pack(Label(Image(NewPhoto(Data(gopher)))),
        TExit(),
        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
    App.Center().Wait()
}
```

![resultado da execução de go-tk-png](/assets/images/go-tk-helloworld-svg-etc/go-tk-png.png)

O processo de tratamento de arquivos PNG é o seguinte:

1. Converte o gopher.png embutido em um tipo string contendo opções.
2. Converte para o tipo \*Img por meio da função NewPhoto.
3. Após passar pela função Image e ser convertido em uma string RAW, é criado como um widget de rótulo.

Arquivos ICO são processados do mesmo modo, e a única diferença em relação ao formato SVG está no modo como a função Data trata internamente os dados.

Vamos examinar a natureza da "string contendo opções" mencionada acima:

```go
type rawOption string
```

A string contendo opções mencionada anteriormente é simplesmente uma string formatada.

```go
func (w *Window) optionString(_ *Window) string {
    return w.String()
}
```

O método optionString é um método do ponteiro Window e retorna uma string.

Por fim, vamos examinar brevemente a estrutura interna da função Data:

```go
func Data(val any) Opt {
    switch x := val.(type) {
    case []byte:
        switch {
        case bytes.HasPrefix(x, pngSig):
            // ok
        case bytes.HasPrefix(x, icoSig):
            b := bytes.NewBuffer(x)
            img, err := ico.Decode(bytes.NewReader(x))
            if err != nil {
                fail(err)
                return rawOption("")
            }

            b.Reset()
            if err := png.Encode(b, img); err != nil {
                fail(err)
                return rawOption("")
            }

            val = b.Bytes()
        }
    }
    return rawOption(fmt.Sprintf(`-data %s`, optionString(val)))
}
```

Pelo código, nota-se que, no caso de arquivos ICO ou PNG, é necessário um processo de codificação/decodificação.
Nos demais casos, não há conversão especial: apenas a opção -data é acrescentada à string convertida em bytes, indicando que se trata do resultado da função Data.

### Carregando imagens com um widget de menu

Se adicionarmos um widget de menu aos exemplos de carregamento de PNG e ICO que praticamos anteriormente, poderemos criar um aplicativo que exibe a imagem desejada.

Primeiro, vamos examinar um exemplo simples de widget de menu.

```go
package main

import (
    "fmt"
    . "modernc.org/tk9.0"
    "runtime"
)

func main() {
    menubar := Menu()

    fileMenu := menubar.Menu()
    fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N"))
    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() }))
    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) }))
    fileMenu.AddCommand(Lbl("Save As..."), Underline(5))
    fileMenu.AddSeparator()
    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) }))
    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))

    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
    App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait()
}
```

![resultado da execução de go-tk-barra-de-menu](/assets/images/go-tk-helloworld-svg-etc/go-tk-menu-bar.png)

Neste exemplo, experimentamos uma barra de menus e a criação de menus, destaque de caracteres, a opção Command, a vinculação de atalhos de teclado e a definição do tamanho inicial da janela da aplicação.

Agora, vamos modificar a função Command especificada em GetOpenFile para criar um programa que carrega e exibe imagens.

O planejamento para escrever o programa é o seguinte:

1. Restringir a abertura apenas a arquivos PNG e ICO
2. Processar o arquivo selecionado na caixa de diálogo de arquivos
3. Implementar um widget para exibir a imagem

O código que reflete esse planejamento é o seguinte:

```go
package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "strings"

    . "modernc.org/tk9.0"
)

func handleFileOpen() {
    s := GetOpenFile()
    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))))
            Pack(imageLabel,
                TExit(),
                Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
        }
        picFile.Close()
    }
}

func main() {
    menubar := Menu()

    fileMenu := menubar.Menu()
    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen))
    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))

    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
    App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait()
}
```

![resultado da execução de go-tk-abrir-imagem](/assets/images/go-tk-helloworld-svg-etc/go-tk-image-open.png)

O código acima funciona da seguinte maneira:

1. Verifica a extensão do arquivo com as funções de comparação de strings do pacote strings
2. Abre, lê e fecha o arquivo usando o pacote os
3. A imagem lida é exibida em um widget de rótulo

## Conclusão

Neste texto, utilizando a biblioteca Tcl/Tk da linguagem Go, abordamos os seguintes conteúdos:

1. Estrutura básica de aplicações GUI
2. Processamento de diversos formatos de imagem, como SVG, PNG e ICO
3. Empacotamento de widgets e gerenciamento de layout
4. Estrutura de processamento de dados de imagem
5. Vinculação de atalhos de teclado e comandos de widgets

Ao usar a linguagem Go em conjunto com Tcl/Tk, é possível criar programas GUI simples e, ao mesmo tempo, práticos. Com base nisso, recomendo que você se desafie no desenvolvimento de aplicações GUI mais complexas.
