GoSuda

Implementace prohlížeče obrázků založeného na seznamovém poli pro více souborů v Go a Tcl/Tk 9.0

By Yunjin Lee views ...

Minulý příspěvek stručně představil knihovnu CGo-Free Tcl/Tk. Tentokrát na předchozí příklad navážeme a vytvoříme prohlížeč obrázků.

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

  1. Dřívější zobrazovač obrázků neměl funkci odstranění obrázků, takže se s rostoucím počtem načtených obrázků velikost okna stávala nedostatečnou. Pojďme odstranit nepoužívané štítky.
  2. Pokud chceme obrázky v zobrazovači uvádět jako seznam, vytvořme seznam.
  3. Je vhodné načítat více obrázků najednou.
  4. Implementujme také odebrání obrázků, které nechceme zobrazit, ze seznamu.
  5. Vytvořme funkci, která umožní vybrat a zobrazit konkrétní obrázek z více obrázků.

Upravená knihovna Tcl/Tk 9.0

Stávající knihovna má nedostatečnou implementaci Listboxu, a proto je obtížné zobrazit seznam obrázků. Stáhněme si upravenou knihovnu. Pokud nemáte nainstalované Git CLI, můžete knihovnu stáhnout také 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, zjistěme strukturu stávajících funkcí na příkladu funkce Destroy v souboru tk.go na řádku 1017.

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

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

Jako příklad implementujme metodu pro přidání položky do listboxu. Nejprve si v oficiální příručce prohlédněme příkaz insert, který lze u listboxu použít.

insert 명령어

Z popisu příkazu insert na stránce dokumentace vyplývá, že insert vkládá seznam položek na zadaný index. Pro jeho implementaci tedy můžeme napsat 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, popíšeme další funkce pro Listbox.

Seznam: vkládání a mazá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

Ve výše uvedeném programu AddItem vkládá položky oddělené mezerou postupně od indexu 0. Položky item1 až item3 tak získají indexy 0, 1 a 2. Jak mazání položek funguje, si ověříme spuštěním příkladu.

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

Nyní získáme položky vybrané v Listboxu a zkontrolujeme je.

 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 vrací indexy všech položek vybraných v aktuálním Listboxu. Metoda GetOne vrací hodnotu položky odpovídající danému indexu. Výsledek je vidět ve výstupu konzole. Pro úplnost, podobná metoda Get přijímá počáteční a koncový index a vrací hodnoty všech položek v daném rozsahu.

Dále změníme barvy 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}

색상 적용 결과

Jak je vidět z kódu, výška se zvětšila. Barvy se rovněž správně uplatnily. Protože jsme nastavili volbu barvy pouze pro konkrétní položku, změnila se barva pouze u prvního řádku.

Metoda Relief umožňuje změnit styl okraje widgetu na jednu z možností: flat, groove, raise, ridge, solid nebo sunken. Rozdíl není příliš velký.

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

Nyní vytvoříme prohlížeč obrázků s použitím widgetů, které jsme se naučili. Příkladový program vypadá takto:

  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)) //다중 선택을 활성화하고 필터를 켭니다.
 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    //테마를 사용하고 싶을 때에는 테마 명과 경로를 지정해 줍니다.
139    fileMenu := menubar.Menu()
140    extensions = make([]FileType,0,1)
141    extensions = append(extensions, FileType{ "Supported Images", []string {".png",".ico"}, "" } )
142    //필터에 png와 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 tomto příkladu jsou kvůli jednoduchosti implementace všechny widgety obrázků vytvořeny předem při jejich načítání a duplicitní soubory se nekontrolují. Výše zmíněné nedostatky lze vylepšit a také lze pomocí metody DefaultTheme, která je v komentáři, změnit téma. Doporučujeme si procvičit tvorbu vylepšeného programu s těmito úpravami.

Shrnutí

V tomto článku jsme si ukázali, jak funguje volání příkazů v knihovně Tcl/Tk pro jazyk Go, a vytvořili jsme prohlížeč obrázků s přidaným seznamem (Listbox).

  1. Způsob volání příkazů knihovny Tcl/Tk
  2. Použití Listboxu
  3. Změna vlastností widgetu Listbox
  4. Tvorba prohlížeče obrázků

Doporučujeme stejným způsobem zkusit upravit i jiné knihovny a dokončit program s přidanými funkcemi.