GUI-проиложение, написанное на Go с использованием GTK (гостевой пост Владимира Солонина)
25 марта 2015
Для написания десктопных приложений на языке Go существует несколько библиотек-привязок к графическим тулкитам, таким как Gtk и Qt. Есть еще пара-тройка написанных собственно на Go GUI-библиотек, однако они пока еще находятся на ранней стадии развития.
Сегодня я опробую библиотеку go-gtk (исходный код, документация), которая позволяет писать GTK+ 2 приложения. Прежде, чем устанавливать ее, убедитесь, что установлена переменная окружения GOPATH. Если нет, установите ее (например так: export GOPATH="$HOME/go"
). Также вам, конечно, понадобится сама библиотека Gtk2. Если все готово для установки go-gtk, выполните следующую команду в консоли:
В качестве примера напишем каркас программы для заметок. Создайте пустой каталог и файл main.go в нем со следующим содержимым:
import (
"os"
"github.com/mattn/go-gtk/gtk"
)
func main() {
gtk.Init(&os.Args)
gtk.Main()
}
Это минимальное Gtk приложение на Go. Здесь функция Init производит инициализацию тулкита, а Main запускает основной цикл, в котором Gtk ожидает и обрабатывает события по мере поступления.
В таком виде программа скомпилируется и запустится, но так и будет пребывать в состоянии ожидания пока ее не убьет пользователь. Чтобы наполнить ее существование смыслом добавим несколько виджетов и обработчиков событий. Но сначала для упрощения кода введем глобальную переменную, которая будет хранить номер последней вкладки:
Вставьте эту строку сразу после блока импорта.
Далее мы добавляем основное окно, задаем его заголовок и иконку, а также функцию выхода из программы в случае закрытия основного окна. Весь дальнейший код функции main будет расположен между gtk.Init(&os.Args) и gtk.Main().
window.SetTitle("Notes")
window.SetIconName("gtk-about")
window.Connect("destroy", func() {
gtk.MainQuit()
})
Теперь создадим виджет notebook, добавим возможность прокрутки вкладок и создадим вкладку с иконкой в виде плюса, которая будет работать привычным всем образом, а именно создавать новую вкладку. Для этого мы проверяем, является ли нажатая вкладка последней, и если да, вызываем функцию добавления новой вкладки addPage, которая будет описана ниже.
notebook.SetScrollable(true)
notebook.AppendPage(gtk.NewVBox(false, 1),
gtk.NewImageFromStock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU))
notebook.Connect("button-release-event", func() bool {
n := notebook.GetCurrentPage()
if n == last {
addPage(notebook)
}
return false
})
В следующих строках виджет notebook добавляется в основное окно, задаются его минимальные размеры, окно со всеми виджетами выводится на экран.
window.SetSizeRequest(500, 300)
window.ShowAll()
Теперь рассмотрим функцию добавления новой вкладки/страницы addPage.
dialog := gtk.NewDialog()
dialog.SetTitle("Title?")
dVbox := dialog.GetVBox()
input := gtk.NewEntry()
input.SetEditable(true)
dVbox.Add(input)
vbox := gtk.NewVBox(false, 1)
Первым делом запрашиваем у пользователя заголовок новой вкладки. Для этого создаем диалог, получаем вертикальный контейнер диалога и добавляем туда текстовое поле ввода input. Создаем еще один вертикальный контейнер для содержимого новой вкладки.
s := input.GetText()
if s != "" {
notebook.InsertPage(vbox, gtk.NewLabel(s), last)
last++
notebook.ShowAll()
}
notebook.PrevPage()
dialog.Destroy()
})
Чтобы отловить нажатие кнопки Enter по окончании ввода, подключаем обработчик события «activate». Функция обработки проверяет, не пустая ли строка, иначе вкладка получится слишком узкой, вставляет новую вкладку и увеличивает счетчик. Вызов метода ShowAll отобразит все изменения. Вызов PrevPage сделает активной новую вкладку, после чего диалог уничтожается.
button.Connect("clicked", func() {
input.Emit("activate")
})
dVbox.Add(button)
dialog.SetModal(true)
dialog.ShowAll()
Здесь мы добавляем в диалог кнопку «ОК», а поскольку она будет дублировать нажатие Enter в строке ввода, вместо непосредственной обработки вызываем это событие программно. Дабы заблокировать основное окно на время ввода заголовка, сделаем диалог модальным.
Так он будет выглядеть в действии.
swin := gtk.NewScrolledWindow(nil, nil)
swin.SetPolicy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
textview := gtk.NewTextView()
swin.Add(textview)
Этот код создает горизонтальный контейнер для кнопок, прокручиваемое окно и многострочный текстовый виджет.
tagBold := buf.CreateTag("bold", map[string]string{"weight": "700"})
tagItalic := buf.CreateTag("italic", map[string]string{"style": "2"})
Здесь мы получаем текстовый буфер и создаем пару тегов для изменения стиля текста.
butBold.Connect("clicked", func() {
var iter1, iter2 gtk.TextIter
if buf.GetSelectionBounds(&iter1, &iter2) {
buf.ApplyTag(tagBold, &iter1, &iter2)
}
})
butIta := gtk.NewToolButtonFromStock(gtk.STOCK_ITALIC)
butIta.Connect("clicked", func() {
var iter1, iter2 gtk.TextIter
if buf.GetSelectionBounds(&iter1, &iter2) {
buf.ApplyTag(tagItalic, &iter1, &iter2)
}
})
butFont := gtk.NewFontButton()
butFont.Connect("font-set", func() {
textview.ModifyFontEasy(butFont.GetFontName())
})
butClose := gtk.NewToolButtonFromStock(gtk.STOCK_DELETE)
butClose.Connect("clicked", func() {
n := notebook.GetCurrentPage()
notebook.RemovePage(notebook, n)
last--
notebook.PrevPage()
})
Еще пара кнопок из стандартного набора Gtk, кнопка смены шрифта для textview и закрытия вкладки с функциями обработки соответствующих сигналов. К сожалению, кнопки butBold и butIta мне не удалось заставить работать. Вместо изменения свойств выделенного текста они устанавливали их в дефолтное состояние.
hbox.PackStart(butIta, false, false, 0)
hbox.PackStart(butFont, false, false, 0)
hbox.PackEnd(butClose, false, false, 0)
vbox.PackStart(hbox, false, false, 0)
vbox.Add(swin)
notebook.ShowAll()
}
Методы PackStart и PackEnd позволяют добиться большей гибкости в установке контролов в контейнерах. Второй аргумент отвечает за то, будет ли под данный виджет отведено все свободное пространство, третий — будет ли он его заполнять, последний аргумент позволяет увеличить интервал между контролами помимо заданного контейнером.
Вот и все, выполните go build
в каталоге проекта и запустите полученный бинарник. После создания нескольких вкладок результат будет выглядеть примерно так:
Код примера одним куском можно найти здесь (зеркало).
В целом, по моим впечатлениям, данная библиотека вполне пригодна для написания GUI-приложений на Go при условии, что вам хватит реализованных авторами 45% функциональности Gtk+ 2.
От себя я хочу поблагодарить Владимира за интересный пост, а также призвать читателей задавать вопросы как по этой заметке, так и в целом по языку Go. Уверен, это вдохновит Владимира на написание новых статей!
Дополнение: Также вас могут заинтересовать посты Простой пример использования goroutines в языке Go и Пишем GUI-приложение при помощи Python, GTK и Glade.
Метки: Go, GUI, Кроссплатформенность.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.