Blog o programowaniu, nowych technologiach i życiu programisty w wielkim mieście.

Jak dodać graficzny interfejs użytkownika GUI dla Golang

Ostatnio 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.

Udostępnij