wkoikingのブログ

内容は主にhaskellやxyzzy関連です。

gtk2hsを使ってみる

Haskellで、gtkを(久しぶりに)使ってみました。 とりあえず、昔書いたコードが動くところまで行ったので、手順と注意点を記録に残しておきます。

gtkとgtk3というパッケージがあるのですが、gtk3は上手く行かなかったので、そこらへんの記録も。

開発環境

手順

gtkパッケージの場合

  1. Haskell Platformをインストール
  2. ここからGTK+ 2.xのall-in-one bundleをダウンロード
  3. 中身をパスにスペースを含まないディレクトリに解凍
    • 例:C:\gtk\
  4. その中のbinにパスを通す
    • 例:C:\gtk\bin
  5. 以下のコマンドを実行
cabal update
cabal install gtk2hs-buildtools
cabal install gtk
  1. glade3.8をここからインストール
    • 3.14はgtk3用なので注意
  2. ここチュートリアルにしたがって、.gladeファイルを作る(以下のコードと同じディレクトリに置く)

  3. 以下のコードをコンパイルして実行
    • ghciからは実行できなかったので注意
module Main where

import Graphics.UI.Gtk
import Control.Monad.Trans ( liftIO )

main = do
    initGUI
    xml    <- builderNew
    builderAddFromFile xml "hellogtk2hs.glade"

    window      <- builderGetObject xml castToWindow "window1"
    window `on` unrealize $ mainQuit

    closeButton <- builderGetObject xml castToButton "button2"
    closeButton `on` buttonPressEvent $ tryEvent $ do
        liftIO $ widgetDestroy window

    label       <- builderGetObject xml castToLabel "label1"
    entry       <- builderGetObject xml castToEntry "entry1"
    applyButton <- builderGetObject xml castToButton "button1"
    applyButton `on` buttonPressEvent $ tryEvent $ do
        name <- liftIO $ get entry entryText
        liftIO $ set label [ labelText := "Hello " ++ name ]

    widgetShowAll window
    mainGUI

gtk3パッケージの場合

  1. Haskell Platformをインストール
  2. ここからGTK+ 3.xのall-in-one bundleをダウンロード
  3. 中身をパスにスペースを含まないディレクトリに解凍
    • 例:C:\gtk3\
  4. その中のbinにパスを通す
    • 例:C:\gtk3\bin
  5. 以下のコマンドを実行
cabal update
cabal install gtk2hs-buildtools
cabal install gtk3
  1. glade3.14をここからインストール
    • 3.8はgtk2用なので注意
  2. 以下同様

注意点

<イベント>と<イベントモナド>

onClickedとかを使う以下の書き方はもう古いようです:

module Main where

import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade

main = do
    initGUI
    Just xml    <- xmlNew "hellogtk2hs.glade"
    window      <- xmlGetWidget xml castToWindow "window1"
    onDestroy window mainQuit
    closeButton <- xmlGetWidget xml castToButton "button2"
    onClicked closeButton $ do
        widgetDestroy window
    label       <- xmlGetWidget xml castToLabel "label1"
    entry       <- xmlGetWidget xml castToEntry "entry1"
    applyButton <- xmlGetWidget xml castToButton "button1"
    onClicked applyButton $ do
        name <- get entry entryText
        set label [ labelText := "Hello " ++ name ]
    widgetShowAll window
    mainGUI

今どきの書き方は最初のコードを参考にしてください。 新しい書き方の方が、多少書くのが面倒くさくなってくれている分、より厳しい型によるチェックをしてくれます。

基本的な書き方は、

<オブジェクト> `on` <イベント> $ tryEvent $ do <イベントモナド>

と言った感じです。

主要な<イベント>はここのEventsの節にまとめられています。

<イベントモナド>はパラメタライズされており、各種イベントに対応するイベントモナドを設けることで、 チェックを厳格にしているようです。

例えば、<イベント>buttonPressEvent :: WidgetClass self => Signal self (EventM EButton Bool)に対応する<イベントモナド>はEventM EButtonです。

このモナドの専用の関数としてeventButton :: EventM EButton MouseButtonなどがあり、型によって、このコンテキストで使える関数を限定しています。

hoogleでEventM EButton a +gtkなどと検索すると、そのモナドで使える関数の一覧が出てくるので、 その点でも非常に便利です。

ちなみに、<イベントモナド> EventM tはMonadIOのインスタンスなので、liftIOを使えば、任意のIOアクションが行えまます(行わざる負えない場合がほとんどです)。

gladeを別にインストールする必要がない件

現在はgtkにgladeがデフォルトで入っているので、gladeパッケージを入れる必要がありません。

gladeパッケージを使うときと使わない時の変更点はだいたいこんな感じです:

gladeパッケージを使うとき(以前の書き方)

import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade

main = do
    initGUI
    Just xml    <- xmlNew "hellogtk2hs.glade"
    window      <- xmlGetWidget xml castToWindow "window1"

gladeパッケージ使わないとき(今どきの書き方)

import Graphics.UI.Gtk

main = do
    initGUI
    xml    <- builderNew
    builderAddFromFile xml "hellogtk2hs.glade"
    window      <- builderGetObject xml castToWindow "window1"

gtk3

gtk3は試してみましたが、 どうやらexpose-event(gtk2hsではexposeEvent)がdeplicatedのようで、代用としてdraw eventというのがあるようです。

gtk3パッケージでexposeEvent :: WidgetClass self => Signal self (EventM EExpose Bool)を使うと実行時エラーがでるので、注意です。

今のところ、私は、

  • gtk2hsにおいてdraw eventに対応する関数を見つけることができない

  • hoogleのAPI searchにgtk3パッケージが対応していない

などの理由でgtkパッケージの方(つまりgtk2へのバインディング)を使っています。

glade

gladeも、

  • gtk2 -> glade3.8
  • gtk3 -> glade 3.14

と使い分けなければ、上手く行きませんでした。

gtk2ではVBoxとかHBoxとWidgetが分かれていましたが、gtk3ではBoxの属性として、VとHがあるようで(?)、gtk3でglade3.8で作ったVBoxとかHBoxを読み込むとエラーがでました。

おわりに

今後、diagramsパッケージを使用してgtkアプリケーションのDrawingArea上にお絵かきをすることに挑戦する予定なので、 次回の記事はそのレポートの予定です。