---
id: e55cbf5b5116abf3a5fc62c1af99e19b
author: Yunjin Lee
title: Go ve Tcl/Tk 9.0 ile Çoklu Dosya Liste Kutusu Tabanlı Görüntü Görüntüleyici Uygulaması
description: modernc.org/tk9.0 üzerine Tcl listbox komut bağlamalarını ve sanal olay işleyicilerini ekleyerek çoklu görüntü dosyası açma, seçim değiştirme ve liste öğesi silme işlemlerini destekleyen bir GUI görüntü görüntüleyicisi uygulanmaktadır.
language: tr
date: 2024-11-19T11:51:05.279238471Z
path: /blog/posts/creating-a-image-viewer-with-tk-in-go-z279238471Z
category: gui
---

Önceki yazıda CGo-Free Tcl/Tk kütüphanesini kısaca inceledik.
Bu sefer önceki örneği geliştirerek bir görüntü görüntüleyici oluşturmaya çalışacağız.

## Görüntü Görüntüleyici Planı

1. Önceki görüntü gösterici, görüntü silme özelliğine sahip olmadığı için birden fazla görüntü yüklendikçe
   pencere boyutu yetersiz kalıyordu.
   Kullanılmayan etiketleri silelim.
2. Görüntü göstericide birden fazla görüntüyü listeleyeceksek bir liste oluşturalım.
3. Birden fazla görüntüyü tek seferde yüklemek iyi olacaktır.
4. Görmek istemediğimiz görüntüleri listeden çıkarma işlevini de uygulayalım.
5. Çoklu görüntüler arasından belirli bir görüntüyü seçip görüntüleme özelliği oluşturalım.

## Değiştirilmiş Tcl/Tk 9.0 Kütüphanesi

Mevcut kütüphanede Listbox uygulaması yetersiz olduğu için görüntü listesini göstermek zordur.
Değiştirilmiş kütüphaneyi indirelim.
Git CLI yüklü değilse tarball veya zip dosyası olarak da indirebilirsiniz.

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

Öncelikle eklenen özelliklerden bazılarına bakalım.

Yeni fonksiyonları incelemeden önce, mevcut fonksiyonların nasıl yapılandırıldığını
tk.go dosyasının 1017. satırındaki Destroy fonksiyonu üzerinden kısaca inceleyelim.

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

Bu fonksiyon, işlemi Tcl betik biçiminde evalErr fonksiyonunaileterek uygulanmıştır.
Bu, istenen işlevi gerçekleştirmek için yalnızca ilgili Tcl betik biçiminde komutu iletmenin yeterli olduğu anlamına gelir.

Örnek olarak, bir liste kutusuna öğe ekleyen bir metot uygulayalım.
Öncelikle Tcl betiği için resmi kılavuzda listbox'ta kullanılabilen komutlardan insert'e bakalım.

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

insert komutunun açıklama sayfasına bakıldığında, insert belirtilen dizine sıralanmış öğeleri ekler.
Bunu uygulamak için

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

gibi bir kod yazabiliriz.

Artık genel uygulama mantığını anladığımıza göre, Listbox için eklenen ek işlevleri açıklayalım.

### Liste: Ekleme/Silme

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

```

Yukarıdaki programda AddItem, boşlukla ayrılmış farklı öğeleri 0 indeksinden başlayarak sırayla ekler.
item1-item3 öğeleri sırasıyla 0, 1, 2 indekslerini alır.
Öğe silmenin nasıl çalıştığını örneği çalıştırarak görelim.

### Liste: Seçili Öğeleri Alma

Şimdi Listbox'ta seçilen öğeleri alıp kontrol edelim.

```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 metodu, mevcut Listbox'ta seçili tüm öğelerin indekslerini alır.
GetOne metodu ise ilgili indeksteki öğenin değerini alır.
Bu, konsola yazdırılan sonuçtan anlaşılabilir.
Not olarak, benzer bir metot olan Get, başlangıç ve bitiş indekslerini alarak aralıktaki tüm öğelerin değerlerini alır.

Sırada liste kutusunun renklerini değiştirmek var.

Öncelikle aşağıdaki örneğe bakalım.

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

Kodda yazıldığı gibi yükseklik arttı.
Ayrıca renklerin doğru uygulandığını görebilirsiniz.
Burada yalnızca belirli bir öğeye farklı renk uygulayan bir seçenek belirtildiği için
yalnızca ilk satırın renginin farklı uygulandığı görülmektedir.

Ayrıca büyük bir fark yaratmasa da, Relief metodu ile flat, groove, raise, ridge, solid ve sunken seçenekleri arasından
widget kenarlığının stilini değiştirebilirsiniz.

## Görüntü Görüntüleyici Örneği

Şimdi öğrendiğimiz widget'ları kullanarak bir görüntü görüntüleyici oluşturalım.
Örnek program aşağıdaki gibidir.

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

Bu örnekte uygulamayı basitleştirmek için tüm görüntü widget'ları yüklenirken önceden oluşturulmakta ve
yinelenen dosyalar kontrol edilmemektedir.
Yukarıda bahsedilen sorunlar iyileştirilebilir;
ayrıca yorum satırına alınmış DefaultTheme metodunu kullanarak temayı değiştirmeyi de deneyebilirsiniz.
Bu noktaları geliştirdiğiniz yeni bir program yazarak pratik yapmanızı öneririz.

## Sonuç

Bu yazıda, Go dilindeki Tcl/Tk kütüphanesinin komut çağrılarının nasıl çalıştığını inceledik
ve liste kutusu eklenmiş bir görüntü görüntüleyici oluşturduk.

1. Tcl/Tk kütüphanesinde komut çağırma yöntemi
2. Liste kutusu kullanım yöntemi
3. Liste kutusu widget'ının özelliklerinin değiştirilmesi
4. Görüntü görüntüleyici geliştirme

Bu yöntemle diğer kütüphanelerin değiştirilmesine de girişebilir ve eklediğiniz özelliklerle tamamlanmış bir program yazmayı deneyebilirsiniz.
