Implementatie van een op meerdere bestanden gebaseerde afbeeldingsviewer met een listbox in Go en Tcl/Tk 9.0

In het vorige bericht hebben we kort gekeken naar de CGo-vrije Tcl/Tk-bibliotheek. In dit bericht gaan we het vorige voorbeeld uitbreiden om een beeldviewer te maken.
Plan voor de beeldviewer
- De beeldweergave van de vorige keer had geen functie om afbeeldingen te verwijderen, dus naarmate er meer afbeeldingen werden geladen, was het venster te klein. Laten we labels die niet meer worden gebruikt, verwijderen.
- Als we de geladen afbeeldingen in de beeldweergave als lijst willen tonen, laten we dan een lijst maken.
- Het is handig om meerdere afbeeldingen tegelijk te kunnen laden.
- Laten we ook de functie implementeren om afbeeldingen die we niet meer willen bekijken uit de lijst te verwijderen.
- Laten we een functie maken waarmee we een specifieke afbeelding uit meerdere afbeeldingen kunnen selecteren en bekijken.
Gewijzigde Tcl/Tk 9.0-bibliotheek
In de oorspronkelijke bibliotheek is de implementatie van Listbox onvoldoende, waardoor het moeilijk is om een lijst met afbeeldingen weer te geven. Laten we de gewijzigde bibliotheek downloaden. Als Git CLI niet is geïnstalleerd, kun je ook het tarball- of zip-bestand downloaden.
1git clone https://github.com/gg582/tk9.0
Laten we eerst enkele van de toegevoegde functies bekijken.
Voordat we naar de nieuwe functies kijken, laten we eerst aan de hand van de functie Destroy in regel 1017 van tk.go kort de structuur van de bestaande functies doorgronden.
1func Destroy(options ...Opt) {
2 evalErr(fmt.Sprintf("destroy %s", collect(options...)))
3}
Deze functie is geïmplementeerd door de actie als Tcl-script door te geven aan de functie evalErr. Dat betekent dat we, om de gewenste functionaliteit te implementeren, alleen het overeenkomstige commando in de vorm van een Tcl-script hoeven door te geven.
Als voorbeeld implementeren we een methode die items aan een listbox toevoegt. Laten we eerst in de officiële handleiding kijken naar insert, een van de commando's die voor listbox beschikbaar zijn, voor het Tcl-scripting.
Op de beschrijvingspagina van het insert-commando zien we dat insert de opgegeven items invoegt op een bepaalde index. Om dit te implementeren kunnen we dan code schrijven zoals:
1func (l *ListboxWidget) AddItem(index int, items string) {
2 evalErr(fmt.Sprintf("%s insert %d %s", l.fpath, index, items))
3}
Nu we het globale implementatieprincipe begrijpen, lichten we eerst de extra functionaliteit voor Listbox toe.
Lijst: invoegen/verwijderen
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
In het bovenstaande programma voegt AddItem de items, gescheiden door spaties, in vanaf index 0. item1 tot en met item3 krijgen respectievelijk de indexen 0, 1 en 2. Laten we het voorbeeld uitvoeren om te zien hoe het verwijderen van items werkt.
Lijst: geselecteerde items ophalen
Laten we nu de geselecteerde items uit de Listbox ophalen en controleren.
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}
De methode Selected haalt de indexen op van alle items die momenteel in de Listbox zijn geselecteerd. De methode GetOne haalt de waarde op van het item met de overeenkomstige index. Dit is te zien aan de resultaten die in de console worden afgedrukt. Ter informatie: de vergelijkbare methode Get haalt, met een start- en eindindex, de waarden van alle items binnen dat bereik op.
Vervolgens gaan we de kleuren van de listbox aanpassen.
Laten we eerst het onderstaande voorbeeld bekijken.
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}

Zoals in de bovenstaande code is opgegeven, is de hoogte toegenomen. Ook is te zien dat de kleuren correct zijn toegepast. Doordat er een optie is ingesteld die alleen voor een specifiek item een andere kleur bepaalt, is te zien dat alleen de eerste regel een andere kleur heeft gekregen.
Hoewel het verschil niet groot is, kun je met de methode Relief de stijl van de widgetrand wijzigen in een van de volgende: flat, groove, raise, ridge, solid of sunken.
Voorbeeld van een beeldviewer
Laten we nu met de zojuist geleerde widgets een beeldviewer maken. Het voorbeeldprogramma ziet er als volgt uit.
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

Om de implementatie eenvoudig te houden, worden in dit voorbeeld alle afbeeldingswidgets vooraf aangemaakt wanneer de afbeeldingen worden geladen, en wordt er niet gecontroleerd op dubbele bestanden. Je kunt de eerder genoemde problemen verbeteren, en ook het thema wijzigen door de als commentaar gemarkeerde DefaultTheme-methode te gebruiken. Oefen door een programma te maken waarin deze onderdelen zijn verbeterd.
Samenvatting
In dit artikel hebben we bekeken hoe de commando-aanroepen van de Tcl/Tk-bibliotheek voor Go werken, en hebben we een beeldviewer gemaakt met een toegevoegde listbox.
- Methode voor het aanroepen van commando's in de Tcl/Tk-bibliotheek
- Gebruik van de listbox
- Wijzigen van eigenschappen van de listbox-widget
- Het maken van een beeldviewer
Probeer op dezelfde manier ook andere bibliotheken aan te passen, en schrijf een voltooid programma met de toegevoegde functionaliteit.