Go:n ja Tk:n avulla luotavan, tiedostolistalla täydennetyn kuvankatseluohjelman kehittäminen
Edellisessä julkaisussa tarkastelimme lyhyesti CGo-Free Tcl/Tk-kirjastoa. Tällä kertaa pyrimme luomaan kuvankatseluohjelman soveltamalla edellisen kerran esimerkkiä.
Kuvankatseluohjelman suunnitelma
- Edellisen kerran kuvannäyttöohjelmasta puuttui kuvien poistotoiminto, minkä vuoksi ikkunan koko kävi riittämättömäksi useampia kuvia ladattaessa. Poistetaan käyttämättömät elementit.
- Mikäli kuvankatseluohjelmaan aiotaan listata useita kuvia, luodaan niille lista.
- On suositeltavaa ladata useita kuvia kerralla.
- Toteutetaan myös toiminto, jolla katseltavaksi kelpaamattomat kuvat voidaan poistaa listalta.
- Luodaan toiminnallisuus, jolla tietty kuva voidaan valita ja tarkastella useiden kuvien joukosta.
Muokattu Tcl/Tk 9.0 -kirjasto
Olemassa olevan kirjaston Listbox-toteutus on puutteellinen, mikä vaikeuttaa kuvien listan näyttämistä. Ladataan muokattu kirjasto. Jos Git CLI ei ole asennettu, on myös hyväksyttävää ladata se tarball- tai zip-tiedostona.
1git clone https://github.com/gg582/tk9.0
Tarkastellaan ensin muutamia lisättyjä toimintoja.
Ennen uusien funktioiden tarkastelua, hahmotellaan olemassa olevien funktioiden rakenne yksinkertaisesti tk.go-tiedoston 1017 rivillä sijaitsevan Destroy-funktion avulla.
1func Destroy(options ...Opt) {
2 evalErr(fmt.Sprintf("destroy %s", collect(options...)))
3}
Tämä funktio on toteutettu välittämällä toiminto Tcl-skriptimuodossa evalErr-nimiselle funktiolle. Tämä tarkoittaa sitä, että halutun toiminnallisuuden toteuttamiseksi riittää, että komento välitetään vastaavan Tcl-skriptin muodossa.
Toteutetaan esimerkiksi metodi, jolla lisätään kohde Listboxiin. Tarkastellaan ensin Tcl-skriptausta varten virallisesta käsikirjasta listboxille saatavilla olevista komennoista komentoa insert.
insert-komennon kuvaussivun mukaan insert sijoittaa luetellut kohteet tiettyyn indeksiin. Täten, tämän toteuttamiseksi,
1func (l *ListboxWidget) AddItem(index int, items string) {
2 evalErr(fmt.Sprintf("%s insert %d %s", l.fpath, index, items))
3}
voimme laatia seuraavanlaisen koodin.
Nyt kun toteutusperiaate on alustavasti ymmärretty, selitämme Listboxiin liittyvät lisäominaisuudet.
Lista: Lisäys/Poisto
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
Yllä olevassa ohjelmassa AddItem lisää välilyönnillä erotellut eri kohteet järjestyksessä alkaen indeksistä 0. item1–item3 saavat vastaavasti indeksit 0, 1 ja 2. Selvitämme kohteen poiston toiminnan suorittamalla esimerkin.
Lista: Valittujen kohteiden noutaminen
Nyt noudamme Listboxista valitut kohteet ja tarkistamme ne.
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 tällä hetkellä Listboxissa valittujen kohteiden indeksit. GetOne-metodi hakee kyseistä indeksiä vastaavan kohteen arvon. Tämä voidaan todeta konsoliin tulostetusta tuloksesta. Vertailun vuoksi samankaltainen Get-metodi vastaanottaa alku- ja loppuindeksit ja hakee kaikki kohteet kyseiseltä alueelta.
Seuraavaksi muokataan listaboksin väriasetuksia.
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}
Yllä olevan koodin mukaisesti korkeus on kasvanut. Lisäksi voidaan havaita, että värit ovat soveltuneet asianmukaisesti. Koska tässä on määritelty asetus, joka antaa eri värin vain tietylle kohteelle, havaitaan, että vain ensimmäinen rivi on saanut eri värit.
Lisäksi, vaikka merkittävää eroa ei ole, Relief-metodia käyttämällä voidaan muuttaa widgetin reunuksen tyyliä arvojen flat, groove, raise, ridge, solid tai sunken joukosta.
Kuvankatseluohjelmaesimerkki
Luomme seuraavaksi kuvankatseluohjelman hyödyntäen aiemmin opittuja widgetejä. Esimerkkiohjelma on seuraavanlainen.
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)) //Aktivoidaan monivalinta ja kytketään suodatin päälle.
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 //Kun teemaa halutaan käyttää, määritellään teeman nimi ja polku.
139 fileMenu := menubar.Menu()
140 extensions = make([]FileType,0,1)
141 extensions = append(extensions, FileType{ "Supported Images", []string {".png",".ico"}, "" } )
142 //Lisätään png ja ico suodattimeen.
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ä toteutuksen yksinkertaistamiseksi kaikki kuvawidgetit luodaan valmiiksi latausvaiheessa, eikä päällekkäisiä tiedostoja tarkisteta. Aiemmin mainittuja ongelmia voidaan parantaa, ja lisäksi teemaa voi kokeilla vaihtaa kommentoituun DefaultTheme-metodiin turvautuen. Kehotamme harjoittelemaan luomalla uuden ohjelman, jossa näitä kohtia on parannettu.
Yhteenveto
Tässä kirjoituksessa perehdyimme Go-kielen Tcl/Tk-kirjaston komennonkutsun toimintatapaan ja loimme listaboksilla varustetun kuvankatseluohjelman.
- Tcl/Tk-kirjaston komennonkutsumistapa
- Listaboksin käyttötapa
- Listaboksitoteutuksen (widget) attribuuttien muuttaminen
- Kuvankatseluohjelman laatiminen
Pyydämme teitä tällä tavoin haastamaan itsenne myös muiden kirjastojen muokkaamisessa ja luomaan valmiin ohjelman lisättyjen toimintojen avulla.