GoSuda

Реализация просмотрщика изображений на основе многофайлового списка (listbox) с использованием Go и Tcl/Tk 9.0

By Yunjin Lee views ...

В прошлой статье мы вкратце рассмотрели библиотеку CGo-Free Tcl/Tk. В этот раз мы возьмём пример из прошлой статьи и на его основе создадим просмотрщик изображений.

План работы над просмотрщиком изображений

  1. В предыдущем средстве отображения изображений не было функции удаления изображений, поэтому по мере загрузки нескольких изображений размер окна становился недостаточным. Давайте удалим неиспользуемые метки.
  2. Если изображения будут перечисляться в средстве отображения, создадим для них список.
  3. Желательно загружать несколько изображений за один раз.
  4. Реализуем также возможность удалять из списка изображения, которые не нужно просматривать.
  5. Создадим функцию выбора и просмотра отдельного изображения из нескольких.

Измененная библиотека Tcl/Tk 9.0

В существующей библиотеке реализация Listbox недостаточна, поэтому трудно отображать список изображений. Давайте загрузим измененную библиотеку. Если Git CLI не установлен, можно загрузить архив tarball или zip.

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

Сначала рассмотрим некоторые из добавленных функций.

Прежде чем изучать новые функции, давайте кратко разберём структуру существующих функций на примере функции Destroy из строки 1017 файла tk.go.

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

Эта функция реализована путём передачи действия в функцию evalErr в виде скрипта Tcl. Другими словами, чтобы реализовать нужную функцию, достаточно передать команду в формате соответствующего скрипта Tcl.

В качестве примера реализуем метод добавления элемента в список. Сначала, для написания скриптов Tcl, посмотрим в официальном руководстве команду insert из доступных для 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 последовательно помещает элементы, разделённые пробелами, начиная с индекса 0. item1-item3 получают индексы 0, 1 и 2 соответственно. Чтобы понять, как работает удаление элементов, запустим пример и посмотрим.

Список: получение выбранных элементов

Теперь получим выбранные в Listbox элементы и проверим результат.

 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 получает индексы всех элементов, выбранных в текущем Listbox. Метод GetOne получает значение элемента по соответствующему индексу. Результат виден в выводе консоли. Для справки: похожий метод Get принимает начальный и конечный индексы и возвращает значения всех элементов в этом диапазоне.

Далее изменим цвета списка.

Сначала рассмотрим следующий пример.

 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}

Результат применения цветов

Как и было задано в коде, высота увеличилась. Кроме того, видно, что цвета применились корректно. Здесь заданы параметры, меняющие цвет только для отдельного элемента, поэтому видно, что отличается только первая строка.

Также, хотя разница небольшая, с помощью метода Relief можно изменить стиль рамки виджета: flat, groove, raise, ridge, solid или sunken.

Пример просмотрщика изображений

Теперь создадим просмотрщик изображений с использованием изученных виджетов. Пример программы выглядит так:

  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

Результат запуска просмотрщика изображений

В этом примере, чтобы упростить реализацию, все виджеts изображений создаются заранее при загрузке, и проверка на дубликаты файлов не выполняется. Вы можете устранить упомянутые выше недостатки, а также попробовать изменить тему с помощью метода DefaultTheme, который сейчас закомментирован. Рекомендуем попрактиковаться, написав новую программу с такими улучшениями.

Заключение

В этой статье мы разобрали, как работают вызовы команд в библиотеке Tcl/Tk для Go, и создали просмотрщик изображений с добавленным списком Listbox.

  1. Способ вызова команд Tcl/Tk
  2. Использование Listbox
  3. Изменение свойств виджета Listbox
  4. Создание просмотрщика изображений

Попробуйте таким же образом поработать над модификацией других библиотек и создайте законченную программу с добавленными функциями.