Хабрахабр

GUI на Golang: GTK+ 3

Сделал CLI-версию, всё работает отлично. Решил я написать одно кроссплатформенное десктопное приложение на Go. Всё в общем отлично. Да ещё и кросскомпиляция в Go поддерживается. И тут началось... Но понадобилась также и GUI-версия.

Golang gotk3

Выбор библиотеки (биндинга) для GUI

Приложение должно было быть кроссплатформенным.
Поэтому должно компилироваться под Windows, GNU/Linux и macOS.
Выбор пал на такие библиотеки:

Electron и прочие фреймворки, которые тянут с собой Chromium и node.js, я откинул так как они весят достаточно много, ещё и съедают много ресурсов операционной системы.

Теперь немного о каждой библиотеке.

gotk3

Покрытие далеко не всех возможностей, но всё основное присутсвует. Биндинг библиотеки GTK+ 3.

Кроссплатформенная компиляция возможна, за исключением macOS. Компилируется приложение с помощью стандартного go build. Только с macOS можно скомпилировать под эту ОС, ну и с macOS можно будет скомпилировать и под Windows + GNU/Linux.

Для macOS будет выглядеть не нативно. Интерфейс будет выглядить нативно для GNU/Linux, Windows (нужно будет указать специальную тему). Выкрутиться можно только разве что страшненькой темой, которая будет эмулирувать нативные элементы macOS.

therecipe/qt

Поддержка QML, стандартных виджетов. Биндинг библиотеки Qt 5. Вообще этот биндинг многие советуют.

Кроме десктопных платформ есть также и мобильные. Компилируется с помощью специальной команды qtdeploy. Под операционные системы Apple можно скомпилировать только с macOS. Кросскомпиляция происходит с помощью Docker.

При желании на Qt можно добиться чтобы интерфейс выглядел нативно на десктопных ОС.

zserge/webview

Использывается нативный webview для отображения: WindowsMSHTML, GNU/Linuxgtk-webkit2, macOSCocoa/WebKit. Библиотека, которая написана изначально на C, автор прикрутил её ко многим языкам, в том числе и к Go. Кроме кода на Go нужно будет и на JS пописать, ну и HTML пригодится.

Компилируется при помощи go build, кросскомпиляция возможна с помощью xgo.

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

Выбор

Почему же я выбрал именно gotk3?

В therecipe/qt мне не понравилась слишком сложная система сборки приложения, даже специальную команду сделали.

И это не Electron, где всегда в комплекте продвинутый Chromium, а в какой-нибудь старой Windows может всё поехать. zserge/webview вроде бы не плох, весить будет не много, но всё-таки это webview и могут быть стандартные проблемы, которые бывают в таких приложениях: может что-то где-то поехать. Да и к тому же придётся ещё и на JS писать.

Можно собирать стандартным go build, выглядит приемлемо, да и вообще я GTK+ 3 люблю! gotk3 я выбрал как что-то среднее.

И что зря про Go говорят, что в нём проблема с GUI. В общем я думал, что всё будет просто. Но как же я ошибался...

Начинаем

Устанавливаем всё из gotk3 (gtk, gdk, glib, cairo) себе:

go get github.com/gotk3/gotk3/...

Также у вас в системе должна быть установлена сама библиотека GTK+ 3 для разработки.

GNU/Linux

В Ubuntu:

sudo apt-get install libgtk-3-dev

В Arch Linux:

sudo pacman -S gtk3

macOS

Через Homebrew:

brew install gtk-mac-integration gtk+3

Windows

В официальной инструкции предлагают использовать MSYS2 и уже в ней всё делать. Здесь всё не так просто. Лично я писал код на других операционных системах, а кросскомпиляцию для Windows делал в Arch Linux, о чём надеюсь скоро напишу.

Простой пример

Теперь пишем небольшой файл с кодом main.go:

package main import ( "log" "github.com/gotk3/gotk3/gtk"
) func main() win.SetTitle("Простой пример") win.Connect("destroy", func() { gtk.MainQuit() }) // Создаём новую метку чтобы показать её в окне l, err := gtk.LabelNew("Привет, gotk3!") if err != nil { log.Fatal("Не удалось создать метку:", err) } // Добавляем метку в окно win.Add(l) // Устанавливаем размер окна по умолчанию win.SetDefaultSize(800, 600) // Отображаем все виджеты в окне win.ShowAll() // Выполняем главный цикл GTK (для отрисовки). Он остановится когда // выполнится gtk.MainQuit() gtk.Main()
}

Но мы просто запустим его: Спомпилировать можно с помощью команды go build, а потом запустить бинарник.

go run main.go

После запуска получим окно такого вида:

Простой пример на Golang gotk3

У вас получилось простое приложение из README gotk3! Поздравляю!

Их разбирать я не буду. Больше примеров можно найти на Github gotk3. Давайте лучше займёмся тем, чего нет в примерах!

Glade

Это конструктор графических интерфейсов для GTK+. Есть такая вещь для Gtk+ 3Glade. Выглядит примерно так:

Glade

Потом сохранить всё в XML-подобный файл *.glade и загрузить его уже через наше приложение. Чтобы вручную не создавать каждый элемент и не помещать его потом где-то в окне с помощью программного кода, можно весь дизайн накидать в Glade.

Установка Glade

GNU/Linux

В какой-нибудь Ubuntu это будет: В дистрибутивах GNU/Linux установить glade не составит труда.

sudo apt-get install glade

В Arch Linux:

sudo pacman -S glade

macOS

Поэтому устанавливать лучше через Homebrew: В загрузках с официального сайта очень старая сборка.

brew install glade

А запускать потом:

glade

Windows

Я лично на Windows вообще не устанавливал, поэтому не знаю насчёт стабильность работы там Glade. Скачать не самую последнюю версию можно здесь.

Простое приложение с использованием Glade

В общем надизайнил я примерно такое окно:

Glade

Сохранил и получил файл main.glade:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface> <requires lib="gtk+" version="3.20"/> <object class="GtkWindow" id="window_main"> <property name="title" translatable="yes">Пример Glade</property> <property name="can_focus">False</property> <child> <placeholder/> </child> <child> <object class="GtkBox"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="margin_left">10</property> <property name="margin_right">10</property> <property name="margin_top">10</property> <property name="margin_bottom">10</property> <property name="orientation">vertical</property> <property name="spacing">10</property> <child> <object class="GtkEntry" id="entry_1"> <property name="visible">True</property> <property name="can_focus">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkButton" id="button_1"> <property name="label" translatable="yes">Go</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkLabel" id="label_1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">This is label</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> </object> </child> </object>
</interface>

Кроме этого ещё имеются аттрибуты отсупов (я настроил немного), видимость и другие аттрибуты, которые Glade добавила автоматически. То есть у нас получилось окно window_main (GtkWindow), в котором внутри контейнер (GtkBox), который содержит поле ввода entry_1 (GtkEntry), кнопку button_1 (GtkButton) и метку label_1 (GtkLabel).

Давайте теперь попробуем загрузить это представление в нашем main.go:

package main import ( "log" "github.com/gotk3/gotk3/gtk"
) func main() { // Инициализируем GTK. gtk.Init(nil) // Создаём билдер b, err := gtk.BuilderNew() if err != nil { log.Fatal("Ошибка:", err) } // Загружаем в билдер окно из файла Glade err = b.AddFromFile("main.glade") if err != nil { log.Fatal("Ошибка:", err) } // Получаем объект главного окна по ID obj, err := b.GetObject("window_main") if err != nil { log.Fatal("Ошибка:", err) } // Преобразуем из объекта именно окно типа gtk.Window // и соединяем с сигналом "destroy" чтобы можно было закрыть // приложение при закрытии окна win := obj.(*gtk.Window) win.Connect("destroy", func() { gtk.MainQuit() }) // Отображаем все виджеты в окне win.ShowAll() // Выполняем главный цикл GTK (для отрисовки). Он остановится когда // выполнится gtk.MainQuit() gtk.Main()
}

Снова запускаем:

go run main.go

И получаем:

Golang Glade gotk3

Теперь мы представление формы держим XML-подобном main.glade файле, а код в main.go! Ура!

Сигналы

Пусть текст из поля ввода при нажатии на кнопку попадёт в метку. Окно запускается, но давайте добавим интерактивности.

Для этого для начала получим элементы поля ввода, кнопки и метке в коде:

// Получаем поле ввода
obj, _ = b.GetObject("entry_1")
entry1 := obj.(*gtk.Entry) // Получаем кнопку
obj, _ = b.GetObject("button_1")
button1 := obj.(*gtk.Button) // Получаем метку
obj, _ = b.GetObject("label_1")
label1 := obj.(*gtk.Label)

Но в реальном рабочем приложении их обязательно необходимо обработать. Я не обрабатываю ошибки, которые возвращает функция GetObject(), для того, чтобы код был более простым.

С помощью кода выше мы получаем наши элементы формы. Хорошо. Сигнал GTK+ — это по сути реакция на событие. А теперь давайте обработаем сигнал clicked кнопку (когда кнопка нажата). Добавим код:

// Сигнал по нажатию на кнопку
button1.Connect("clicked", func() { text, err := entry1.GetText() if err == nil { // Устанавливаем текст из поля ввода метке label1.SetText(text) }
})

Теперь запускаем код:

go run main.go

После ввода какого-нибудь текста в поле и нажатию по кнопке Go мы увидем этот текст в метке:

Golang Glade gotk3 signal

Теперь у нас интерактивное приложение!

Заключение

Но трудности у меня появились при кросскомпиляции (ведь gotk3 компилируется с CGO), интеграции с операционными системами и с диалогом выбора файла. На данном этапе всё кажется простым и не вызывает трудностей. Также в моём проекте нужна была интернационализация. Я даже добавил в проект gotk нативный диалог. Если вам интересно увидеть это всё сейчас в коде, то можно подсмотреть здесь. Там тоже есть некоторые особенности.

Исходые коды примеров из статьи находятся здесь.

И в случае, если окажется это кому-нибудь интересным, я продолжу писать. А если хотите почитать продолжение, то можете проголосовать.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть