GoSuda

Go:n ja Tk:n avulla luotavan, tiedostolistalla täydennetyn kuvankatseluohjelman kehittäminen

By Yunjin Lee
views ...

Edellisessä julkaisussa tarkastelimme lyhyesti CGo-Free Tcl/Tk-kirjastoa. Tällä kertaa pyrimme luomaan kuvankatseluohjelman soveltamalla edellisen kerran esimerkkiä.

Kuvankatseluohjelman suunnitelma

  1. 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.
  2. Mikäli kuvankatseluohjelmaan aiotaan listata useita kuvia, luodaan niille lista.
  3. On suositeltavaa ladata useita kuvia kerralla.
  4. Toteutetaan myös toiminto, jolla katseltavaksi kelpaamattomat kuvat voidaan poistaa listalta.
  5. 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-komento

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}

Värien soveltamisen tulos

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

Kuvankatseluohjelman suoritustulos

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.

  1. Tcl/Tk-kirjaston komennonkutsumistapa
  2. Listaboksin käyttötapa
  3. Listaboksitoteutuksen (widget) attribuuttien muuttaminen
  4. Kuvankatseluohjelman laatiminen

Pyydämme teitä tällä tavoin haastamaan itsenne myös muiden kirjastojen muokkaamisessa ja luomaan valmiin ohjelman lisättyjen toimintojen avulla.