GoSuda

Implementacja przeglądarki obrazów opartej na wielofailowym Listboxie w Go i Tcl/Tk 9.0

By Yunjin Lee views ...

W poprzednim wpisie pokrótce przyjrzeliśmy się bibliotece CGo-Free Tcl/Tk. W tym odcinku rozwiniemy poprzedni przykład i stworzymy przeglądarkę obrazów.

Plan przeglądarki obrazów

  1. Wyświetlacz obrazów z poprzedniej lekcji nie miał funkcji usuwania obrazów, więc im więcej obrazów wczytywaliśmy, tym bardziej brakowało miejsca w oknie. Usuńmy nieużywane etykiety.
  2. Jeśli wyświetlacz obrazów ma prezentować wiele obrazów na liście, utwórzmy listę.
  3. Dobrze jest wczytywać wiele obrazów jednocześnie.
  4. Zaimplementujmy także usuwanie z listy obrazów, których nie chcemy oglądać.
  5. Stwórzmy funkcję wyboru i wyświetlania konkretnego obrazu spośród wielu.

Zmodyfikowana biblioteka Tcl/Tk 9.0

Istniejąca biblioteka ma niedostateczną implementację Listbox, przez co trudno wyświetlić listę obrazów. Pobierzmy zmodyfikowaną bibliotekę. Jeśli Git CLI nie jest zainstalowany, można pobrać archiwum tarball lub zip.

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

Najpierw przyjrzyjmy się kilku dodanym funkcjom.

Zanim przyjrzymy się nowym funkcjom, sprawdźmy na przykładzie funkcji Destroy w pliku tk.go, w wierszu 1017, jak zbudowane są funkcje istniejące, aby w prosty sposób poznać ich strukturę.

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

Ta funkcja jest zaimplementowana przez przekazanie działania do funkcji evalErr w formie skryptu Tcl. Oznacza to, że aby zaimplementować żądaną funkcjonalność, wystarczy przekazać polecenie w formie odpowiedniego skryptu Tcl.

Jako przykład zaimplementujmy metodę dodającą element do listboxa. Najpierw, na potrzeby skryptowania w Tcl, zapoznajmy się w oficjalnym podręczniku z poleceniem insert, dostępnym dla listbox.

insert 명령어

Z opisu polecenia insert wynika, że insert wstawia wymienione elementy pod określonym indeksem. Aby to zaimplementować, można napisać kod taki jak:

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

Skoro znamy już ogólną zasadę implementacji, omówmy dodatkowe funkcje Listbox.

Lista: wstawianie i usuwanie

 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

W powyższym programie AddItem wstawia kolejno elementy oddzielone spacjami, zaczynając od indeksu 0. Elementy item1-item3 otrzymują kolejno indeksy 0, 1 i 2. Sprawdźmy, jak działa usuwanie elementów, uruchamiając przykład.

Lista: pobieranie wybranych elementów

Teraz pobierzmy elementy wybrane w Listbox i je sprawdźmy.

 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 pobiera indeksy wszystkich elementów wybranych w bieżącym Listbox. Metoda GetOne pobiera wartość elementu o danym indeksie. Wynik widzimy na konsoli. Warto wiedzieć, że podobna metoda Get przyjmuje indeks początkowy i końcowy oraz pobiera wartości wszystkich elementów z zakresu.

Następnie zmieńmy kolory listboxa.

Najpierw przyjrzyjmy się poniższemu przykładowi.

 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 zapisano w kodzie powyżej, wysokość listboxa się zwiększyła. Widać też, że kolory zostały poprawnie zastosowane. Ponieważ opcja zmiany koloru została ustawiona dla konkretnego elementu, tylko pierwszy wiersz ma inne kolory.

Ponadto, choć różnica nie jest duża, metoda Relief pozwala zmienić styl obramowania widgetu spośród: flat, groove, raise, ridge, solid oraz sunken.

Przykład przeglądarki obrazów

Teraz, wykorzystując poznane widgety, stwórzmy przeglądarkę obrazów. Przykładowy program wygląda następująco.

  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

이미지 뷰어 실행 결과

W tym przykładzie, aby uprościć implementację, wszystkie widgety obrazów są tworzone z wyprzedzeniem, w momencie wczytywania, a duplikaty plików nie są sprawdzane. Można poprawić wspomniane wcześniej problemy, a także wypróbować zmianę motywu, korzystając z metody DefaultTheme, która znajduje się w zakomentowanym fragmencie. Zachęcam do ćwiczenia poprzez stworzenie nowego programu, w którym wprowadzisz te udoskonalenia.

Podsumowanie

W tym artykule przyjrzeliśmy się temu, jak działa wywoływanie poleceń w bibliotece Tcl/Tk dla języka Go, oraz stworzyliśmy przeglądarkę obrazów z listboxem.

  1. Sposób wywoływania poleceń Tcl/Tk
  2. Sposób korzystania z listboxa
  3. Zmiana właściwości widgetu listbox
  4. Tworzenie przeglądarki obrazów

Zachęcam do podjęcia podobnych prób z modyfikacją innych bibliotek oraz do napisania kompletnego programu z dodanymi funkcjami.