- PVSM.RU - https://www.pvsm.ru -
Решил я написать одно кроссплатформенное десктопное приложение на Go. Сделал CLI-версию, всё работает отлично. Да ещё и кросскомпиляция в Go поддерживается. Всё в общем отлично. Но понадобилась также и GUI-версия. И тут началось...
Приложение должно было быть кроссплатформенным.
Поэтому должно компилироваться под Windows, GNU/Linux и macOS.
Выбор пал на такие библиотеки:
Electron и прочие фреймворки, которые тянут с собой Chromium и node.js, я откинул так как они весят достаточно много, ещё и съедают много ресурсов операционной системы.
Теперь немного о каждой библиотеке.
Биндинг библиотеки GTK+ 3. Покрытие далеко не всех возможностей, но всё основное присутсвует.
Компилируется приложение с помощью стандартного go build
. Кроссплатформенная компиляция [4] возможна, за исключением macOS. Только с macOS можно скомпилировать под эту ОС, ну и с macOS можно будет скомпилировать и под Windows + GNU/Linux.
Интерфейс будет выглядить нативно для GNU/Linux, Windows (нужно будет указать специальную тему). Для macOS будет выглядеть не нативно. Выкрутиться можно только разве что страшненькой темой, которая будет эмулирувать нативные элементы macOS.
Биндинг библиотеки Qt 5. Поддержка QML, стандартных виджетов. Вообще этот биндинг многие советуют.
Компилируется с помощью специальной команды qtdeploy
. Кроме десктопных платформ есть также и мобильные. Кросскомпиляция происходит с помощью Docker [5]. Под операционные системы Apple можно скомпилировать только с macOS [6].
При желании на Qt можно добиться чтобы интерфейс выглядел нативно на десктопных ОС.
Библиотека, которая написана изначально на C, автор прикрутил её ко многим языкам, в том числе и к Go. Использывается нативный webview [7] для отображения: Windows — MSHTML, GNU/Linux — gtk-webkit2, macOS — Cocoa/WebKit. Кроме кода на Go нужно будет и на JS пописать, ну и HTML пригодится.
Компилируется при помощи go build
, кросскомпиляция возможна с помощью xgo [8].
Выглядеть нативно может настолько насколько позволит стандартный браузер.
Почему же я выбрал именно gotk3?
В therecipe/qt мне не понравилась слишком сложная система сборки приложения, даже специальную команду сделали.
zserge/webview вроде бы не плох, весить будет не много, но всё-таки это webview и могут быть стандартные проблемы, которые бывают в таких приложениях: может что-то где-то поехать. И это не Electron, где всегда в комплекте продвинутый Chromium, а в какой-нибудь старой Windows может всё поехать. Да и к тому же придётся ещё и на JS писать.
gotk3 я выбрал как что-то среднее. Можно собирать стандартным go build
, выглядит приемлемо, да и вообще я GTK+ 3 люблю!
В общем я думал, что всё будет просто. И что зря про Go говорят, что в нём проблема с GUI. Но как же я ошибался...
Устанавливаем всё из gotk3 (gtk, gdk, glib, cairo) себе:
go get github.com/gotk3/gotk3/...
Также у вас в системе должна быть установлена сама библиотека GTK+ 3 для разработки.
В Ubuntu:
sudo apt-get install libgtk-3-dev
В Arch Linux:
sudo pacman -S gtk3
Через Homebrew:
brew install gtk-mac-integration gtk+3
Здесь всё не так просто. В официальной инструкции [9] предлагают использовать MSYS2 и уже в ней всё делать. Лично я писал код на других операционных системах, а кросскомпиляцию для Windows делал в Arch Linux, о чём надеюсь скоро напишу.
Теперь пишем небольшой файл с кодом main.go
:
package main
import (
"log"
"github.com/gotk3/gotk3/gtk"
)
func main() {
// Инициализируем GTK.
gtk.Init(nil)
// Создаём окно верхнего уровня, устанавливаем заголовок
// И соединяем с сигналом "destroy" чтобы можно было закрыть
// приложение при закрытии окна
win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
if err != nil {
log.Fatal("Не удалось создать окно:", err)
}
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
После запуска получим окно такого вида:
Поздравляю! У вас получилось простое приложение из README [10] gotk3!
Больше примеров можно найти на Github gotk3 [11]. Их разбирать я не буду. Давайте лучше займёмся тем, чего нет в примерах!
Есть такая вещь для Gtk+ 3 — Glade. Это конструктор графических интерфейсов для GTK+. Выглядит примерно так:
Чтобы вручную не создавать каждый элемент и не помещать его потом где-то в окне с помощью программного кода, можно весь дизайн накидать в Glade. Потом сохранить всё в XML-подобный файл *.glade и загрузить его уже через наше приложение.
В дистрибутивах GNU/Linux установить glade не составит труда. В какой-нибудь Ubuntu это будет:
sudo apt-get install glade
В Arch Linux:
sudo pacman -S glade
В загрузках с официального сайта очень старая сборка. Поэтому устанавливать лучше через Homebrew:
brew install glade
А запускать потом:
glade
Скачать не самую последнюю версию можно здесь [12]. Я лично на Windows вообще не устанавливал, поэтому не знаю насчёт стабильность работы там 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>
То есть у нас получилось окно window_main
(GtkWindow
), в котором внутри контейнер (GtkBox
), который содержит поле ввода entry_1
(GtkEntry
), кнопку button_1
(GtkButton
) и метку label_1
(GtkLabel
). Кроме этого ещё имеются аттрибуты отсупов (я настроил немного), видимость и другие аттрибуты, которые Glade добавила автоматически.
Давайте теперь попробуем загрузить это представление в нашем 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
И получаем:
Ура! Теперь мы представление формы держим 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()
, для того, чтобы код был более простым. Но в реальном рабочем приложении их обязательно необходимо обработать.
Хорошо. С помощью кода выше мы получаем наши элементы формы. А теперь давайте обработаем сигнал clicked
кнопку (когда кнопка нажата). Сигнал GTK+ — это по сути реакция на событие. Добавим код:
// Сигнал по нажатию на кнопку
button1.Connect("clicked", func() {
text, err := entry1.GetText()
if err == nil {
// Устанавливаем текст из поля ввода метке
label1.SetText(text)
}
})
Теперь запускаем код:
go run main.go
После ввода какого-нибудь текста в поле и нажатию по кнопке Go мы увидем этот текст в метке:
Теперь у нас интерактивное приложение!
На данном этапе всё кажется простым и не вызывает трудностей. Но трудности у меня появились при кросскомпиляции (ведь gotk3 компилируется с CGO), интеграции с операционными системами и с диалогом выбора файла. Я даже добавил [13] в проект gotk нативный диалог. Также в моём проекте нужна была интернационализация. Там тоже есть некоторые особенности. Если вам интересно увидеть это всё сейчас в коде, то можно подсмотреть здесь [14].
Исходые коды примеров из статьи находятся здесь [15].
А если хотите почитать продолжение, то можете проголосовать. И в случае, если окажется это кому-нибудь интересным, я продолжу писать.
Автор: jhekasoft
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/289090
Ссылки в тексте:
[1] gotk3: https://github.com/gotk3/gotk3
[2] therecipe/qt: https://github.com/therecipe/qt
[3] zserge/webview: https://github.com/zserge/webview
[4] Кроссплатформенная компиляция: https://github.com/gotk3/gotk3/wiki/Cross-Compiling
[5] Docker: https://github.com/therecipe/qt/wiki/Deploying-Linux-to-Windows-64-bit-Static#using-docker-image
[6] macOS: https://github.com/therecipe/qt#deployment-targets
[7] webview: https://github.com/zserge/webview#webview
[8] xgo: https://github.com/karalabe/xgo
[9] официальной инструкции: https://www.gtk.org/download/windows.php
[10] README: https://github.com/gotk3/gotk3#sample-use
[11] Github gotk3: https://github.com/gotk3/gotk3-examples/
[12] здесь: http://ftp.gnome.org/pub/GNOME/binaries/win32/glade/
[13] добавил: https://github.com/gotk3/gotk3/pull/267
[14] здесь: https://github.com/jhekasoft/insteadman3/tree/master/gtk
[15] здесь: https://github.com/jhekasoft/articles/tree/master/01_golang_gtk3/example
[16] Источник: https://habr.com/post/420035/?utm_campaign=420035
Нажмите здесь для печати.