Kuvankatseluohjelman luominen Go:lla ja Tk:lla, tiedostoluettelon lisäyksellä
지난 게시물에서는 CGo-Free Tcl/Tk 라이브러리에 대해 간단하게 살펴 봤습니다. 이번 시간에는 지난 번의 예제를 응용하여 이미지 뷰어를 만들어 보도록 하겠습니다.
이미지 뷰어 계획
- 지난 시간의 이미지 표시기는 이미지 삭제 기능이 없어 여러 이미지들을 불러올 수록창의 크기가 모자랐습니다. 사용하지 않는 라벨을 삭제해 줍시다.
- 이미지 표시기에 여러 이미지를 목록화할 것이라면 리스트를 만들어 줍시다.
- 한 번에 여러 이미지를 불러오는 것이 좋습니다.
- 보지 않을 이미지를 목록에서 빼는 것도 구현해 줍니다.
- 다중 이미지 중 특정 이미지를 선택해서 보는 기능을 만들어 줍시다.
수정된 Tcl/Tk 9.0 라이브러리
기존 라이브러리에는 Listbox 구현이 미흡해서 이미지 목록을 보여주기 힘듭니다. 수정된 라이브러리를 다운로드해 줍시다. Git CLI가 설치되어 있지 않다면 tarball이나 zip 파일로 다운로드받아도 좋습니다.
1git clone https://github.com/gg582/tk9.0
먼저 추가된 기능들의 몇 가지를 살펴 봅시다.
일단 새로운 함수를 살펴보기에 앞서서, 기존 함수들은 어떻게 되어 있는지 tk.go의 1017행의 Destroy 함수를 통해 구조를 간단하게 파악해보도록 하겠습니다.
1func Destroy(options ...Opt) {
2 evalErr(fmt.Sprintf("destroy %s", collect(options...)))
3}
이 함수는 evalErr라는 함수에 Tcl 스크립트 형식으로 동작을 전달해 구현되어 있습니다. 그 말은, 원하는 기능을 구현하기 위해서는 해당하는 Tcl 스크립트의 형식으로 명령을 전달하기만 된다는 뜻입니다.
예시로 리스트박스에 항목을 추가하는 메서드를 구현해 봅시다. 먼저 Tcl 스크립팅을 위해 공식 매뉴얼에서 listbox에 사용 가능한 명령어 중 insert를 살펴 봅시다.
insert 명령어의 설명 페이지를 보면, insert는 특정 인덱스에 나열된 항목들을 삽입합니다. 그렇다면 이를 구현하기 위해서
1func (l *ListboxWidget) AddItem(index int, items string) {
2 evalErr(fmt.Sprintf("%s insert %d %s", l.fpath, index, items))
3}
와 같은 코드를 작성할 수 있습니다.
이제 대략적인 구현 원리를 알았으니, Listbox를 위한 추가 기능들부터 설명하겠습니다.
리스트: 삽입/삭제
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
위 프로그램에서 AddItem on erottimena välilyönnin sisältäviä kohteita ja lisää ne peräkkäin indeksistä 0 alkaen. item1-item3 saa indeksit 0, 1, 2 järjestyksessä. Suorita esimerkki ja tarkastele, miten kohteen poistaminen toimii.
리스트: 선택된 항목 가져오기
Tarkastellaan nyt Listboxista valittujen kohteiden hakemista.
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}
Selected-metodi hakee kaikkien Listboxissa valittujen kohteiden indeksit. GetOne-metodi hakee kyseistä indeksiä vastaavan kohteen arvon. Tämä on nähtävissä konsoliin tulostetusta tuloksesta. Huomautuksena, vastaava Get-metodi ottaa vastaan alku- ja loppuindeksit ja hakee kaikki kyseisen alueen kohteiden arvot.
Seuraavaksi muutetaan Listboxin väriä.
Tarkastellaan ensin alla olevaa esimerkkiä.
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}
Kuten yllä olevassa koodissa on kirjoitettu, korkeus on kasvanut. Lisäksi värien on havaittavissa soveltuvan hyvin. Koska tietyille kohteille on määritetty erilaiset värivaihtoehdot, on havaittavissa, että vain ensimmäisellä rivillä on erilainen väri.
Lisäksi, vaikka suurta eroa ei olekaan, Relief-metodia käyttämällä voi muuttaa widgetin reunan tyyliä valinnoista flat, groove, raise, ridge, solid, sunken.
이미지 뷰어 예제
Käytämme nyt edellä opittua widgetiä kuvankatseluohjelman luomiseen. Esimerkkiohjelma on seuraava:
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
Tässä esimerkissä kaikkia kuvawidgetejä ladataan etukäteen toteutuksen yksinkertaistamiseksi, eikä kaksoiskappaleita tarkisteta. Edellä mainittuja ongelmia voidaan parantaa, ja teemaa voidaan muuttaa käyttämällä kommentoitua DefaultTheme-metodia. Harjoitelkaa luomalla uusi ohjelma, joka parantaa näitä kohtia.
정리
Tässä artikkelissa olemme tutkineet, miten Go-kielen Tcl/Tk-kirjaston komentokutsut toimivat, ja olemme luoneet kuvankatseluohjelman, johon on lisätty Listbox.
- Tcl/Tk-kirjaston komentokutsujen toimintatapa
- Listboxin käyttö
- Listbox-widgetin ominaisuuksien muuttaminen
- Kuvankatseluohjelman luominen
Tällä tavoin voitte myös yrittää muokata muita kirjastoja ja luoda valmiita ohjelmia lisäämillänne toiminnoilla.