GoSuda

Vytvoření prohlížeče obrázků s přidaným seznamem souborů v Tk z Go

By Yunjin Lee
views ...

V minulém příspěvku jsme se krátce seznámili s knihovnou CGo-Free Tcl/Tk. Dnes se pokusíme vytvořit prohlížeč obrázků, aplikováním příkladu z minulé lekce.

Plán prohlížeče obrázků

  1. Dříve použitý zobrazovač obrázků postrádal funkci pro mazání obrázků, což vedlo k tomu, že se okno stalo příliš malé, pokud bylo načteno mnoho obrázků. Odstraníme nepoužívané labely.
  2. Pokud chceme zobrazit více obrázků v zobrazovači obrázků, vytvoříme seznam.
  3. Je vhodné načíst více obrázků najednou.
  4. Implementujeme také funkci pro odstranění obrázků, které nechceme zobrazovat, ze seznamu.
  5. Vytvoříme funkci pro výběr a zobrazení konkrétního obrázku z více obrázků.

Upravená knihovna Tcl/Tk 9.0

Stávající knihovna má nedostatečnou implementaci Listboxu, což ztěžuje zobrazení seznamu obrázků. Prosím, stáhněte si upravenou knihovnu. Pokud nemáte nainstalovaný Git CLI, můžete si ji stáhnout jako tarball nebo zip soubor.

1git clone https://github.com/gg582/tk9.0

Nejprve se podívejme na některé z přidaných funkcí.

Než se podíváme na nové funkce, nejprve si stručně objasníme strukturu stávajících funkcí prostřednictvím funkce Destroy na řádku 1017 souboru tk.go.

1func Destroy(options ...Opt) {
2	evalErr(fmt.Sprintf("destroy %s", collect(options...)))
3}

Tato funkce je implementována předáním operace ve formátu Tcl skriptu funkci evalErr. To znamená, že pro implementaci požadované funkce stačí předat příkaz v odpovídajícím formátu Tcl skriptu.

Jako příklad implementujme metodu pro přidání položky do Listboxu. Nejprve se v oficiálním manuálu podívejme na příkaz insert, který je k dispozici pro Listbox, pro Tcl skriptování.

Příkaz insert

Na stránce s popisem příkazu insert je uvedeno, že insert vkládá položky uvedené na konkrétním indexu. Pro implementaci tedy můžeme napsat následující kód:

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

Nyní, když známe přibližný princip implementace, vysvětlíme si další funkce pro Listbox.

Seznam: Vložení/Odstranění

 1package main
 2import . "modernc.org/tk9.0"
 3
 4func main() {
 5    length := 3
 6    l := Listbox()
 7    l.AddItem(0, "item1 item2 item3")
 8    b1 := TButton(Txt("Delete Multiple Items, index (0-1)"), Command( func(){
 9        if length >= 2 {
10            l.DeleteItems(0,1)
11            length-=2
12        }
13    }))
14    b2 := TButton(Txt("Delete One Item, index (0)"), Command( func () {
15        if length > 0 {
16            l.DeleteOne(0) 
17            length-=1
18        }
19    }))
20    Pack(TExit(),l,b1,b2)
21    App.Wait()
22}
23

V programu výše AddItem postupně vkládá různé položky oddělené mezerami, počínaje indexem 0. item1-item3 budou mít postupně indexy 0, 1, 2. Spuštěním příkladu zjistíte, jak funguje mazání položek.

Seznam: Získání vybraných položek

Nyní se podíváme na získání a ověření vybraných položek z Listboxu.

 1package main
 2
 3import . "modernc.org/tk9.0"
 4
 5func main() {
 6    l := Listbox()
 7    l.SelectMode("multiple")
 8    l.AddItem(0, "item1 item2 item3")
 9    btn := TButton(Txt("Print Selected"), Command( func() {
10        sel := l.Selected()
11        for _, i := range sel {
12            println(l.GetOne(i))
13        }
14    }))
15
16    Pack(TExit(), l, btn)
17    App.Wait()
18}

Metoda Selected získá indexy všech aktuálně vybraných položek v Listboxu. Metoda GetOne získá hodnotu položky odpovídající danému indexu. Výsledek je patrný z výstupu do konzole. Mimochodem, podobná metoda Get přijímá počáteční a koncový index a získá všechny hodnoty položek v daném rozsahu.

Dále změníme barvu Listboxu.

Nejprve se podívejme na následující příklad.

 1package main
 2
 3import . "modernc.org/tk9.0"
 4
 5func main() {
 6    l := Listbox()
 7    l.Background("blue")
 8    l.Foreground("yellow")
 9    l.SelectBackground("black")
10    l.SelectForeground("white")
11    l.Height(20)
12    l.Width(6)
13    l.AddItem(0, "item1 item2 item3")
14    l.ItemForeground(0,"red")
15    l.ItemBackground(0,"green")
16    l.ItemSelectBackground(0,"white")
17    l.ItemSelectForeground(0,"black")
18    l.Relief("ridged")
19    Pack(TExit(),l)
20    App.Wait()
21}

Výsledek aplikace barev

Jak je uvedeno v kódu výše, výška se zvětšila. Také je vidět, že barvy byly úspěšně aplikovány. Zde je nastavena možnost pro odlišnou barvu pouze pro konkrétní položku, takže je vidět, že pouze první řádek má odlišnou barvu.

Kromě toho, i když s malým rozdílem, metoda Relief umožňuje změnit styl okraje widgetu z flat, groove, raise, ridge, solid, sunken.

Příklad prohlížeče obrázků

Nyní vytvoříme prohlížeč obrázků pomocí widgetů, které jsme se naučili dříve. Příklad programu je následující:

  1package main
  2
  3import (
  4    "fmt"
  5    "log"
  6    "os"
  7    "runtime"
  8    "strings"
  9    "path"
 10
 11    . "modernc.org/tk9.0"
 12)
 13
 14var pbuttons []*TButtonWidget
 15var extensions []FileType
 16var pbutton *TButtonWidget = nil
 17var listbox, listbox2 *ListboxWidget
 18var cur *LabelWidget = nil
 19var imagesLoaded []*LabelWidget
 20func PhotoName(fileName string) string {
 21        fileName = path.Base(fileName)
 22        return fileName[:len(fileName)-len(path.Ext(fileName))]
 23}
 24
 25func handleFileOpen() {
 26    res := GetOpenFile(Multiple(true),Filetypes(extensions)) // Aktivuje vícenásobný výběr a zapne filtry.
 27    s := make([]string,0,1000)
 28    for _, itm := range res {
 29        if itm != "" {
 30            tmp := strings.Split(itm," ")
 31            s = append(s,tmp...)
 32        }
 33    }
 34
 35    for _, photo := range s {
 36        formatCheck := strings.Split(photo, ".")
 37        format := formatCheck[len(formatCheck)-1]
 38
 39        if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
 40            picFile, err := os.Open(photo)
 41            if err != nil {
 42                log.Println("Error while opening photo, error is: ", err)
 43            }
 44
 45            pictureRawData := make([]byte, 10000*10000)
 46            picFile.Read(pictureRawData)
 47
 48            imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
 49                        imagesLoaded = append(imagesLoaded,imageLabel)
 50            var deleteTestButton *TButtonWidget
 51            deleteTestButton = TButton(
 52                Txt("Unshow Image"),
 53            Command(func() {
 54                GridForget(imageLabel.Window)
 55                GridForget(deleteTestButton.Window)
 56            }))
 57
 58            pbuttons = append(pbuttons,deleteTestButton)
 59
 60                        listbox.AddItem(len(imagesLoaded)-1,PhotoName(photo))
 61                        listbox2.AddItem(len(imagesLoaded)-1,PhotoName(photo))
 62            picFile.Close()
 63        }
 64    }
 65}
 66
 67func DeleteSelected () {
 68    s:=listbox.Selected()
 69    if len(s) == 0 {
 70        return
 71        }
 72    for _, i := range s {
 73        listbox.DeleteOne(i)
 74        listbox2.DeleteOne(i)
 75        if len(imagesLoaded)-1>i {
 76            continue
 77        }
 78        if cur == imagesLoaded[i] {
 79            pbutton = nil
 80            cur = nil
 81        }
 82        Destroy(imagesLoaded[i])
 83        Destroy(pbuttons[i])
 84                imagesLoaded = append(imagesLoaded[:i],imagesLoaded[i+1:]...)
 85        pbuttons = append(pbuttons[:i], pbuttons[i+1:]...)
 86    }
 87}
 88
 89func SelectImage() {
 90        s:=listbox2.Selected()
 91        if len(s) == 0 {
 92                return
 93        }
 94
 95    if len(imagesLoaded) -1 < s[0] {
 96        return
 97    }
 98    if imagesLoaded[s[0]] == nil {
 99        return
100    }
101    if cur != nil {
102            GridForget(cur.Window)
103    }
104    if pbutton != nil {
105        GridForget(pbutton.Window)
106    }
107
108        Grid(imagesLoaded[s[0]], Row(0), Column(2))
109    Grid(pbuttons[s[0]], Row(0), Column(3))
110    cur = imagesLoaded[s[0]]
111    pbutton = pbuttons[s[0]]
112}
113
114func SelectIndex(index int) {
115
116    if len(imagesLoaded) -1 <index {
117        return
118    }
119    if imagesLoaded[index] == nil {
120        return
121    }
122    if cur != nil {
123            GridForget(cur.Window)
124    }
125    if pbutton != nil {
126        GridForget(pbutton.Window)
127    }
128
129        Grid(imagesLoaded[index], Row(0), Column(2))
130    Grid(pbuttons[index], Row(0), Column(3))
131    cur = imagesLoaded[index]
132    pbutton = pbuttons[index]
133}
134
135func main() {
136    menubar := Menu()
137    //DefaultTheme("awdark","themes/awthemes-10.4.0")
138    // Pokud chcete použít téma, zadejte název a cestu k tématu.
139    fileMenu := menubar.Menu()
140    extensions = make([]FileType,0,1)
141    extensions = append(extensions, FileType{ "Supported Images", []string {".png",".ico"}, "" } )
142    // Do filtru přidá png a ico.
143    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func () {
144        handleFileOpen()
145        SelectIndex(len(imagesLoaded)-1)
146    } ))
147    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
148    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
149    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
150    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
151        imagesLoaded = make([]*LabelWidget, 0, 10000)
152    pbuttons = make([]*TButtonWidget,0,10000)
153    var scrollx, scroll, scroll2, scrollx2 *TScrollbarWidget
154        listbox = Listbox(Yscrollcommand(func(e *Event) { e.ScrollSet(scroll)}) , Xscrollcommand( func(e *Event) { e.ScrollSet(scrollx)}))
155        listbox2 = Listbox(Yscrollcommand(func(e *Event) { e.ScrollSet(scroll2)}), Xscrollcommand(func(e *Event) { e.ScrollSet(scrollx2)}))
156        listbox.SelectMode("multiple")
157        listbox2 = Listbox()
158        listbox.Background("white")
159        listbox.SelectBackground("blue")
160        listbox.SelectForeground("yellow")
161        listbox2.Background("grey")
162        listbox2.SelectBackground("green")
163    listbox2.SelectForeground("blue")
164    listbox2.SelectBackground("brown")
165        listbox.Height(5)
166        listbox.Width(4)
167        listbox2.Height(5)
168        listbox2.Width(4)
169        delBtn := Button(Txt("Delete"), Command(func () { DeleteSelected() }))
170        selBtn := Button(Txt("Select"), Command(func () { SelectImage() }))
171        scroll = TScrollbar(Command(func(e *Event) { e.Yview(listbox) }))
172        scrollx = TScrollbar(Orient("horizontal"),Command(func(e *Event) { e.Xview(listbox) }))
173    scroll2 = TScrollbar(Command(func(e *Event) { e.Yview(listbox2) }))
174        scrollx2 = TScrollbar(Orient("horizontal"),Command(func(e *Event) { e.Xview(listbox2) }))
175        Grid(listbox,Row(1),Column(0), Sticky("nes"))
176        Grid(scroll,Row(1),Column(1), Sticky("nes"))
177    Grid(scrollx,Row(2),Column(0),  Sticky("nes"))
178        Grid(delBtn,Row(3),Column(0), Sticky("nes"))
179        Grid(listbox2,Row(1),Column(2), Sticky("nes"))
180        Grid(scroll2,Row(1),Column(3), Sticky("nes"))
181    Grid(scrollx2,Row(2),Column(2), Sticky("nes"))
182        Grid(selBtn,Row(3),Column(2), Sticky("nes"))
183    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
184    App.Configure(Mnu(menubar), Width("80c"), Height("60c")).Wait()
185}
186

Výsledek spuštění prohlížeče obrázků

V tomto příkladu, pro zjednodušení implementace, jsou všechny widgety obrázků předem vytvořeny při načítání, a nejsou kontrolovány duplicitní soubory. Můžete se pokusit vylepšit výše zmíněné problémy, nebo změnit téma pomocí metody DefaultTheme, která je zakomentována. Doporučujeme procvičit si vytvoření nového programu s těmito vylepšeními.

Shrnutí

V tomto článku jsme se seznámili s tím, jak fungují volání příkazů knihovny Tcl/Tk v jazyce Go, a vytvořili jsme prohlížeč obrázků s přidaným Listboxem.

  1. Způsob volání příkazů knihovny Tcl/Tk
  2. Jak používat Listbox
  3. Změna vlastností widgetu Listbox
  4. Vytvoření prohlížeče obrázků

Doporučujeme vám pokusit se modifikovat i jiné knihovny podobným způsobem a vytvořit kompletní program s přidanými funkcemi.