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

#Go言語でGUIしたいかー？！　おー！！

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

##Go言語でGUIするには？

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

  * __[go-ui](https://github.com/visualfc/go-ui)__
    「Go言語 GUI」でググった時に、おそらく一番情報の多いライブラリ。ただ情報はあるけど大して気合の入ったライブラリではない模様。
    __go-ui__なんて大層な名前をしてるけど、その実態はQtバインディングだったりする。
    開発はもう終わったみたいで、 __[GoQt](https://github.com/visualfc/goqt)__ というのが後継らしいけど、 __GoQt__ の方はまだリポジトリを作っただけのような状態だった。
    _Windowsで導入するのがほぼ無理ゲー_。（最重要事案）

  * __[go-gtk](https://github.com/mattn/go-gtk/)__
    mattn氏が開発の中心となっているGo言語のGtk+バインディング。多分Go言語のGUIライブラリで一番開発が活発。
    _[Windowsでも動くらしい](http://mattn.kaoriya.net/software/lang/go/20110201010655.htm)_けれど、Win64な環境ではしったこっちゃないし、そもそもGtkベースだからLGPLなわけで、嫌LGPLな現代っ子の僕には向いてない。

  * __[wxGo](https://github.com/JeroenD/wxGo)__
    名前の通りwxWidgetsのGoバインディング。SWIGを使っている。
    既に二年近くメンテされてないし、どうしてもwxWidgets使いたい人向け。

他にもいくつかある。[ここ](http://go-lang.cat-v.org/library-bindings)ら辺を参照。

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

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

__[WALK](https://github.com/lxn/walk)__という、Windows向けのGUIライブラリがあります。これは、何かのGo言語バインディングというわけではなくて、Go言語から直接Win32APIを叩くことでGUIを作るライブラリです。
開発は盛んな方だと思います。600コミット以上されていて、最近もコミットされ続けていますし。 __go-gtk__ ほどじゃないのが残念ですが。

どれだけの人に伝わるかわかりませんが、Rubyで言えば[VisualRuby](http://www.osk.3web.ne.jp/~nyasu/software/vrproject.html)みたいなものです、多分。

ぶっちゃけたことを言ってしまえばただ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言語](http://code.google.com/p/go/downloads/list)をインストールしてきてください。

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

##それでは使ってみよう

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

まずは、`mkdir searchbox && cd searchbox`で、新しいディレクトリを作って、移動してください。ここに必要なファイルを置いていきます。
（なぜわざわざディレクトリを掘る必要があるの？　って感じですが、あとあと`go build`して実行可能ファイルを作ることになるので、ディレクトリを分けておいたほうが都合がいいのです）

次に、プログラムの主役ソースコードを書きます。
とりあえず僕が書いたコードを載せておきます。（このコードの[Gist](https://gist.github.com/alucky0707/6199515)）

```go: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関連のパッケージを読み込み、

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

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

```go:
	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メソッドが「検索」ボタンを押した時に呼ばれるものです。

```go:
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`で問題ないでしょう。

```xml: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
```

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

![searchbox1.png](https://qiita-image-store.s3.amazonaws.com/0/11219/55082e25-02ea-25f0-4a6d-5dbc8fe4b608.png)

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

![searchbox2.png](https://qiita-image-store.s3.amazonaws.com/0/11219/a9305ebb-f649-743e-da34-46f86ed0e3ab.png)

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

##最後に

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

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

それではっ。

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

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