Jak dodać graficzny interfejs użytkownika GUI dla Golang
Wtorek, 20 Sie, 2019Ostatnio zacząłem pracować nad nową aplikacją i w ramach rozwoju umiejętności programowania w Golang postanowiłem spróbować napisać ją w Go.
Zgodnie z założeniami tej aplikacji, użytkownika powinien mieć możliwość wyboru pliku z okna dialogowego, wybranie docelowej lokalizacji zapisu plików, podanie linku do strony, z której będzie miał możliwość ściągnięcia treści itd.
Docelowymi użytkownikami oprogramowania będą doświadczeni edytorzy, jednak wymaganiem jest użycie prostego i przejrzystego interfejsu użytkownika.
Z założenia język Go nie został zaprojektowany z GUI przez co nie ma wbudowanych bibliotek z graficznym interfejsem użytkownika. Ze względu na moje nikłe doświadczenie z GUI dla Golang musiałem trochę poeksperymentować.
Oczywistym wyborem mógłby okazać się Electron (Astilectron) ze względu na moje doświadczenie z HTML, CSS i JavaScript. Electron jest połączeniem dwóch technologii: Chromium i Node.js, przez co w mojej opinii jest dość “ciężkim rozwiązaniem”. Postanowiłem poszukać czegoś “lżejszego”.
Pierwszym GUI, z którym zacząłem eksperymentować był Fyne.
Fyne jest zbudowany wyłącznie z grafiki wektorowej co gwarantuje zachowanie jakości przy skalowaniu aplikacji, projekt jest również rozwijany bez większych przestojów więc warto zwrócić na niego uwagę.
Jak prosto stworzyć GUI z Fyne?
Aby zacząć należy dodać bibliotekę:
go get fyne.io/fyne
Później już wystarczy tylko kilka linii kodu:
package main
import (
"fyne.io/fyne/widget"
"fyne.io/fyne/app"
)
func main() {
app := app.New()
w := app.NewWindow("Hello")
w.SetContent(widget.NewVBox(
widget.NewLabel("Hello Fyne!"),
widget.NewButton("Quit", func() {
app.Quit()
}),
))
w.ShowAndRun()
}
Następnie odpalamy program:
go run main.go
I w ten oto prosty sposób stworzyliśmy pierwszą aplikację! Proste, prawda? No dobrze, spróbujmy teraz zmienić rozmiar okna.
Co zrobić aby ustawić rozmiar okna?
Wystarczy dodać bibliotekę:
fyne.io/fyne
i później definiujemy rozmiar okna:
w.Resize(fyne.NewSize(640, 480))
Tworzenie aplikacji przy wykorzystaniu Fyne wydaje się naprawdę proste. Niestety w chwili tworzenia tego tekstu, nie mogłem znaleźć informacji jak w prosty sposób zarządzać oknami dialogowymi typu: otwórz plik, zapisz plik itd. Zacząłem więc przeszukiwać internet dalej i znalazłem rozwiązanie - webview.
Biblioteka webview używa gtk-webkit, Cocoa/Webkit i MSHTML jako silnik przeglądatki i wspiera zarówno system Linux, MacOS jak i Windows.
Jak zbudować prostą aplikację z Webview i Go?
Najpierw instalujemy bibliotekę webview:
go get github.com/zserge/webview
A później odpalamy przykładowy program:
package main
import (
"github.com/zserge/webview"
)
func main() {
webview.Open("Minimal webview example",
"https://en.m.wikipedia.org/wiki/Main_Page", 800, 600, true)
}
Kilka linii kodu i mamy aplikację gotową :)
A co z obsługą przycisków?
Stwórzmy aplikację zawierająca dwa przyciski. Jeden będzie otwierał okno, w którym użytkownik będzie mógł wybrać plik a drugi przycisk otworzy okno wyboru katalogu. Interfejs użytkownika umieścimy w osobnym pliku index.html w katalogu public. Do dzieła!
Tworzymy plik index.html wraz z podstawowym stylowaniem przycisków.
<!doctype html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<style>
body {
background-color: #2e3238;
margin: 0;
padding: 0;
font-size: 16px;
}
button {
min-height: 60px;
margin: 10px;
width: 45%;
background-color: #2ab2e6;
color: #ffffff;
font-size: 24px;
cursor: pointer;
}
</style>
</head>
<body>
<button onclick="external.invoke('open')">Open file</button>
<button onclick="external.invoke('opendir')">Open directory</button>
</body>
</html>
Świetnie, pozostaje nam jeszcze napisanie głównej aplikacji w Go.
Plik main.go:
package main
import (
"io/ioutil"
"log"
"net"
"net/http"
"github.com/zserge/webview"
)
const (
windowWidth = 450
windowHeight = 600
)
func main() {
url := startServer()
w := webview.New(webview.Settings{
Width: windowWidth,
Height: windowHeight,
Title: "Nasza apka",
URL: url,
ExternalInvokeCallback: handleButtons,
})
defer w.Exit()
w.Run()
}
func startServer() string {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
go func() {
defer l.Close()
UItemplate, err := ioutil.ReadFile("public/index.html")
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(UItemplate))
})
log.Fatal(http.Serve(l, nil))
}()
return "http://" + l.Addr().String()
}
func handleButtons(w webview.WebView, data string) {
switch {
case data == "close":
w.Terminate()
case data == "open":
log.Println("open", w.Dialog(webview.DialogTypeOpen, 0, "Open file", ""))
case data == "opendir":
log.Println("open", w.Dialog(webview.DialogTypeOpen, webview.DialogFlagDirectory, "Open directory", ""))
}
}
Program jest naprawdę prosty i nie wymaga większych komentarzy.
Myślę, że jest to rozwiązanie, które pozwoli mi na zbudowanie w prosty sposób wymaganej aplikacji. Na pewno podzielę się z Tobą resztą kodu.