← На главную

GUI-проиложение на Go с использованием GTK (гостевой пост Владимира Солонина)

Для написания десктопных приложений на языке Go существует несколько библиотек-привязок к графическим тулкитам, таким как Gtk и Qt. Есть еще пара-тройка написанных собственно на Go GUI-библиотек, однако они пока еще находятся на ранней стадии развития.

Сегодня я опробую библиотеку go-gtk (исходный код, документация), которая позволяет писать GTK+ 2 приложения. Прежде, чем устанавливать ее, убедитесь, что установлена переменная окружения GOPATH. Если нет, установите ее (например так: export GOPATH="$HOME/go"). Также вам, конечно, понадобится сама библиотека Gtk2. Если все готово для установки go-gtk, выполните следующую команду в консоли:

go get github.com/mattn/go-gtk/gtk

В качестве примера напишем каркас программы для заметок. Создайте пустой каталог и файл main.go в нем со следующим содержимым:

package main import ( "os" "github.com/mattn/go-gtk/gtk" ) func main() { gtk.Init(&os.Args) gtk.Main() }

Это минимальное Gtk приложение на Go. Здесь функция Init производит инициализацию тулкита, а Main запускает основной цикл, в котором Gtk ожидает и обрабатывает события по мере поступления.

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

var last int

Вставьте эту строку сразу после блока импорта.

Далее мы добавляем основное окно, задаем его заголовок и иконку, а также функцию выхода из программы в случае закрытия основного окна. Весь дальнейший код функции main будет расположен между gtk.Init(&os.Args) и gtk.Main().

window := gtk.NewWindow(gtk.WINDOW_TOPLEVEL) window.SetTitle("Notes") window.SetIconName("gtk-about") window.Connect("destroy", func() { gtk.MainQuit() })

Теперь создадим виджет notebook, добавим возможность прокрутки вкладок и создадим вкладку с иконкой в виде плюса, которая будет работать привычным всем образом, а именно создавать новую вкладку. Для этого мы проверяем, является ли нажатая вкладка последней, и если да, вызываем функцию добавления новой вкладки addPage, которая будет описана ниже.

notebook := gtk.NewNotebook() 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.Add(notebook) window.SetSizeRequest(500, 300) window.ShowAll()

Теперь рассмотрим функцию добавления новой вкладки/страницы addPage.

func addPage(notebook *gtk.Notebook) { dialog := gtk.NewDialog() dialog.SetTitle("Title?") dVbox := dialog.GetVBox() input := gtk.NewEntry() input.SetEditable(true) dVbox.Add(input) vbox := gtk.NewVBox(false, 1)

Первым делом запрашиваем у пользователя заголовок новой вкладки. Для этого создаем диалог, получаем вертикальный контейнер диалога и добавляем туда текстовое поле ввода input. Создаем еще один вертикальный контейнер для содержимого новой вкладки.

input.Connect("activate", func() { s := input.GetText() if s != "" { notebook.InsertPage(vbox, gtk.NewLabel(s), last) last++ notebook.ShowAll() } notebook.PrevPage() dialog.Destroy() })

Чтобы отловить нажатие кнопки Enter по окончании ввода, подключаем обработчик события «activate». Функция обработки проверяет, не пустая ли строка, иначе вкладка получится слишком узкой, вставляет новую вкладку и увеличивает счетчик. Вызов метода ShowAll отобразит все изменения. Вызов PrevPage сделает активной новую вкладку, после чего диалог уничтожается.

button := gtk.NewButtonWithLabel("OK") button.Connect("clicked", func() { input.Emit("activate") }) dVbox.Add(button) dialog.SetModal(true) dialog.ShowAll()

Здесь мы добавляем в диалог кнопку «ОК», а поскольку она будет дублировать нажатие Enter в строке ввода, вместо непосредственной обработки вызываем это событие программно. Дабы заблокировать основное окно на время ввода заголовка, сделаем диалог модальным.

Модальный диалог на Go

Так он будет выглядеть в действии.

hbox := gtk.NewHBox(false, 1) swin := gtk.NewScrolledWindow(nil, nil) swin.SetPolicy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) textview := gtk.NewTextView() swin.Add(textview)

Этот код создает горизонтальный контейнер для кнопок, прокручиваемое окно и многострочный текстовый виджет.

buf := textview.GetBuffer() tagBold := buf.CreateTag("bold", map[string]string{"weight": "700"}) tagItalic := buf.CreateTag("italic", map[string]string{"style": "2"})

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

butBold := gtk.NewToolButtonFromStock(gtk.STOCK_BOLD) 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(butBold, false, false, 0) 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 с использованием GTK

Код примера одним куском можно найти здесь (зеркало).

В целом, по моим впечатлениям, данная библиотека вполне пригодна для написания GUI-приложений на Go при условии, что вам хватит реализованных авторами 45% функциональности Gtk+ 2.

От себя я хочу поблагодарить Владимира за интересный пост, а также призвать читателей задавать вопросы как по этой заметке, так и в целом по языку Go. Уверен, это вдохновит Владимира на написание новых статей!

Дополнение: Также вас могут заинтересовать посты Простой пример использования goroutines в языке Go и Пишем GUI-приложение при помощи Python, GTK и Glade.