Edited at

WindowsでGo言語でGUIするにはWALKがいいかもしれない

More than 3 years have passed since last update.


Go言語でGUIしたいかー?! おー!!

とまあ、そんなノリで始まります。


Go言語でGUIするには?

Go言語でGUIするライブラリは意外と色々あります。


  • go-ui

    「Go言語 GUI」でググった時に、おそらく一番情報の多いライブラリ。ただ情報はあるけど大して気合の入ったライブラリではない模様。

    go-uiなんて大層な名前をしてるけど、その実態はQtバインディングだったりする。

    開発はもう終わったみたいで、 GoQt というのが後継らしいけど、 GoQt の方はまだリポジトリを作っただけのような状態だった。

    Windowsで導入するのがほぼ無理ゲー。(最重要事案)


  • go-gtk

    mattn氏が開発の中心となっているGo言語のGtk+バインディング。多分Go言語のGUIライブラリで一番開発が活発。

    Windowsでも動くらしいけれど、Win64な環境ではしったこっちゃないし、そもそもGtkベースだからLGPLなわけで、嫌LGPLな現代っ子の僕には向いてない。


  • wxGo

    名前の通りwxWidgetsのGoバインディング。SWIGを使っている。

    既に二年近くメンテされてないし、どうしてもwxWidgets使いたい人向け。


他にもいくつかある。ここら辺を参照。

ここまで見た感じだと、まともに開発されているライブラリはgo-gtkしかない気がします。でもなぁ、Gtkなんだよなぁ…。


そんなあなたに、WALKはいかが?

WALKという、Windows向けのGUIライブラリがあります。これは、何かのGo言語バインディングというわけではなくて、Go言語から直接Win32APIを叩くことでGUIを作るライブラリです。

開発は盛んな方だと思います。600コミット以上されていて、最近もコミットされ続けていますし。 go-gtk ほどじゃないのが残念ですが。

どれだけの人に伝わるかわかりませんが、Rubyで言えばVisualRubyみたいなものです、多分。

ぶっちゃけたことを言ってしまえばただWin32APIのラッパなのですが、うまい具合に抽象化されていて、結構使いやすいです。

あと、完全にWindows向けかつGo言語単体で完結しているので、Windowsならインストールが楽ちんです。


なにはともあれインストール

僕の環境はWindows 8上のgo version 1.1.1 windows/amd64ですが、Windowsならおそらくはうまくいくと思います。

インストール方法は、コンソール上で、

go get github.com/lxn/walk

を叩くだけ。簡単ですよね?

何も出力されなければ成功です。Go言語は怖いぐらいに 「沈黙は成功なり」 の法則を順守している気がします。せめて依存関係ぐらい出力してくれてもいいのに…。

ちなみに、何か出力されていたら十中八九エラーだと思います。恐らく一番多いエラーは、バージョン1.1.x未満のGo言語を使っていることによるので。とりあえず最新版のGo言語をインストールしてきてください。

他にも、環境変数GOPATHが設定されていなかったり、Gitがインストールされていなかったりするとエラーが出ます。注意してください。


それでは使ってみよう

テキストエリアの文字列を検索する、というプログラムを作ることにします。名前はsearchboxです。

まずは、mkdir searchbox && cd searchboxで、新しいディレクトリを作って、移動してください。ここに必要なファイルを置いていきます。

(なぜわざわざディレクトリを掘る必要があるの? って感じですが、あとあとgo buildして実行可能ファイルを作ることになるので、ディレクトリを分けておいたほうが都合がいいのです)

次に、プログラムの主役ソースコードを書きます。

とりあえず僕が書いたコードを載せておきます。(このコードのGist


searchbox.go

package main

//WALK関連のライブラリ
import (
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
)

//その他標準ライブラリ
import (
"fmt"
"log"
"strings"
)

func main() {
mw := &MyMainWindow {}

if _, err := (MainWindow {
AssignTo: &mw.MainWindow,
Title: "SearchBox",
MinSize: Size {300, 400},
Layout: VBox {},
Children: []Widget {
GroupBox {
Layout: HBox {},
Children: []Widget {
LineEdit {
AssignTo: &mw.searchBox,
},
PushButton {
Text: "検索",
OnClicked: mw.clicked,
},
},
},
TextEdit {
AssignTo: &mw.textArea,
},
ListBox {
AssignTo: &mw.results,
Row: 5,
},
},
}.Run()); err != nil {
log.Fatal(err)
}

}

//structにまとめることで、グローバル変数を作らない
type MyMainWindow struct {
*walk.MainWindow
searchBox *walk.LineEdit
textArea *walk.TextEdit
results *walk.ListBox
}

func (mw *MyMainWindow) clicked() {
word := mw.searchBox.Text()
text := mw.textArea.Text()
model := []string {}
for _, i := range search(text, word) {
model = append(model, fmt.Sprintf("%d文字目に発見", i))
}
log.Print(model)
mw.results.SetModel(model)
}

//textからwordを検索して、位置をUnicode単位で返す
func search(text ,word string) (result []int) {
result = []int {}
i := 0
for j, _ := range text {
if strings.HasPrefix(text[j:], word) {
log.Print(i)
result = append(result, i)
}
i += 1
}
return
}


簡単にこのコードを説明すると、最初のimportでWALK関連のパッケージを読み込み、

//WALK関連のライブラリ

import (
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
)

main関数の中でWindowの構造を作り(この部分がかなり宣言的でイケてると思います)、Runメソッドを呼びGUIを初めています。

    if _, err := (MainWindow {

AssignTo: &mw.MainWindow,
Title: "SearchBox",
MinSize: Size {300, 400},
Layout: VBox {},
Children: []Widget {
GroupBox {
Layout: HBox {},
Children: []Widget {
LineEdit {
AssignTo: &mw.searchBox,
},
PushButton {
Text: "検索",
OnClicked: mw.clicked,
},
},
},
TextEdit {
AssignTo: &mw.textArea,
},
ListBox {
AssignTo: &mw.results,
Row: 5,
},
},
}.Run()); err != nil {

AssignToで実施のWidgetとの結びつけをするのですね。

で、その後のclickedメソッドが「検索」ボタンを押した時に呼ばれるものです。

func (mw *MyMainWindow) clicked() {

word := mw.searchBox.Text()
text := mw.textArea.Text()
model := []string {}
for _, i := range search(text, word) {
model = append(model, fmt.Sprintf("%d文字目に発見", i))
}
log.Print(model)
mw.results.SetModel(model)
}

search関数は、本当はindex/suffixarrayパッケージを使いたかったのですが、suffixarrayがbyteにしか対応していなかったため、byte位置を教えられてもなあ、という気がしたので定義したものです。

あとはまあ、雰囲気で察してください。

ソースコードができたら、マニフェストファイルを書きます。書かないと起動しないので注意してください。

マニフェストファイルは実行可能ファイル名.manifestという名前で保存します。

この場合はsearchbox.exe.manifestで問題ないでしょう。


searchbox.exe.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
</assembly>

ここまできたら、あとはbuildしてコンパイルするだけです。

go build

(起動時にコマンドプロンプトが表示されないようにするには、

go build -ldflags="-H windowsgui"

として下さい)

でオーケーです。

早速、起動してみましょう。

searchbox.exe

こんな画面が立ち上がれば、無事成功です。

青空文庫から適当に文章を引っ張ってきて「検索」をしてみると、こんな風になります。

良い感じじゃないでしょうか?


最後に

説明をかなり簡略化したため、わかりにくいところ(コードの意味とか)は多いと思います。そこはWALKのexamplesやgodocを見て補完してください。(他力本願ですみません)

Go言語でGUIをする、という記事はあまり無くて、しかも大抵は環境がOS XかLinuxという状況だったので、この記事がWindowsでGo言語やろうとしてる人の支えに、またGo言語を始める切っ掛けになったらいいな、なんて思ってみたりもしています。

それではっ。


イヤッッホォォォオオォオウ!Go言語最高ー!!

この前AIRをやったばかりなんです。叫びたい年頃なんです。