Julia - 簡単なGUIアプリを作ってみる

この記事は Qiita に 2018/01/02 に投稿したものです。2020/3/26 移行しました。

追記 (2018/9/27) Julia 1.0 で動くように修正。PackageCompiler.jl を使った例を追加。

概要

  • 人生初のGUIアプリ作成をJuliaでやってみました
  • GUIツールキットとしてGtkを使用しています
  • GUIプログラミング初心者なので、手始めに下図のようなボタンをクリックしたらクリックした回数が表示されるという簡単なアプリ作成から始めてみました

f:id:goropikarikun:20181110183443p:plain

作成までの流れは 1. 画面レイアウトの作成 2. ボタンを押したときに呼ばれる関数の定義 3. ボタンと関数を連携させる と言った感じです。

環境

OS: ArchLinux Julia 1.0.0 Gtk.jl v0.16.4

インストール

GUIを作るためにGtk.jlを使っているのでインストールしていない場合インストールしてください。

using Pkg
Pkg.add("Gtk.jl")

画面レイアウトの作成

初めに大枠のウィンドウを作り、そこにボタンやらスライダーやら何やらのWidgetを追加していきます。

using Gtk, Gtk.ShortNames

win = Window("Count Click") # 大枠のWindowをつくる
v = Box(:v) # widgetを並べて配置するためのボックス。 
            # :v でwidgetを縦に、:h で横並びになる
l = Label("You clicked 0 times.") # クリックした回数を表示する部分
b = Button("Click!") # ボタン
push!(win, v) # Window に Box を配置する
push!(v, l) # Box内に表示部分を配置
push!(v, b) # Box内にボタンを配置
set_gtk_property!(v, :expand, l, true) # Box内の余白をなくして表示部分を広くする

showall(win) # レイアウトに反映させる

f:id:goropikarikun:20181110183505p:plain

とりあえず、GUIアプリっぽくなってきました。しかし、このままではボタンを押したときの動作を定義していないのでボタンを押しても何も起こりません。

関数定義とボタンとの連携

次にボタンがクリックされたときに呼ばれる関数(callback function)を定義します。

nclick = 0
function click()
    global nclick += 1
    set_gtk_property!(l, :label, "You clicked $nclick times.") # Label Widgetのラベルを変更する

    return nothing
end

signal_connect(x -> click(), b, "clicked") # ボタンと関数をつなぐ

setproperty!を使うと各 Widget の property を変更することができます。

最後に signal_connect でボタンと関数をつなげます。上記の場合だとボタンbから"clicked"シグナルが出たら callback function を呼ぶという意味になるらしいです。このとき、callback function にはボタン b が引数として与えられます。今回の場合、引数もらっても使わないので無名関数使って素通りさせてます。

これで目標のGUIアプリを作ることができるはずです。 窓の外の野鳥や車でも見ながらポチポチしてください。

f:id:goropikarikun:20181110183443p:plain

まとめ

Juliaを使って簡単なGUIアプリを作ることができました。

私は他の言語でGUIアプリを作ったことがないので比較は出来ませんが、思っていたよりもGUIアプリって簡単に作れるものなんだなぁといった印象です。 まともな情報が公式ドキュメントくらいしかないのは辛いところですが。

他にも役に立たないものを練習がてら色々作ってみましたが、複雑なものでも 関数定義, setproperty!, signal_connect をひたすら繰り返せばそれっぽいものは作れるとわかりました。

ウィジェットが増えてくるとコマンドで画面レイアウトを作るのは大変なので Glade を使いましょう!

ソースコード

#!/usr/bin/env julia
using Gtk, Gtk.ShortNames

# 画面レイアウトの作成
win = Window("Count Click")
v = Box(:v)
l = Label("You clicked 0 times.")
b = Button("Click!")
push!(win, v)
push!(v, l)
push!(v, b)
set_gtk_property!(v, :expand, l, true)

showall(win)

# ボタンを押したときに呼ばれる関数
nclick = 0
function click()
    global nclick += 1
    set_gtk_property!(l, :label, "You clicked $nclick times.")

    return nothing
end

# ボタンと関数をつなぐ
signal_connect(x -> click(), b, "clicked")


if !isinteractive()
    c = Condition()
    signal_connect(win, :destroy) do widget
        notify(c)
    end
    wait(c)
end

最後の部分はREPL使わずにコマンドラインからスクリプトとして実行したときのために入れています。

click とでもスクリプト名つけて、実行権限もつけて、PATHの通っているところおけば普通のGUIのアプリの様に使えます。

$ chmod +x click
$ ./click

PackageCompiler.jl

PackageCompiler.jl を使うと Julia のプログラムを実行可能ファイルへと変換することが出来ます。 今回使った PackageCompiler.jl のバージョンは 0.5.0 です。

(v1.0) pkg> add PackageCompiler

上記のコードそのままでは動かなかったので多少修正したバージョンがこちらです。

module Hello

using Gtk

Base.@ccallable function julia_main(ARGS::Vector{String})::Cint
    win = GtkWindow("Count Click")
    v = GtkBox(:v)
    l = GtkLabel("You clicked 0 times.")
    b = GtkButton("Click!")
    push!(win, v)
    push!(v, l)
    push!(v, b)
    set_gtk_property!(v, :expand, l, true)

    # callback function
    global nclick = 0
    function click(x)
        nclick += 1
        set_gtk_property!(l, :label, "You clicked $(nclick) times.")
    end

    # connect button and function
    signal_connect(click, b, "clicked")
    showall(win)

    if !isinteractive()
        c = Condition()
        signal_connect(win, :destroy) do widget
            notify(c)
        end
        wait(c)
    end

    return 0
end

end

これを PackageCompiler を使ってコンパイル、その後実行します。

julia> using PackageCompiler

julia> build_executable("click.jl", "click")

julia> run(`builddir/click`)

機能自体がシンプルなのでレスポンスの違いを体感することはないと思いますが、起動スピードが純粋な Julia のスクリプトを使った場合よりも圧倒的に早くなりました。

参考

追記

もう少し実用的のものを作ってみました。GoogleにもWolfram Alphaにもグラフをプロットさせることが出来ない状況になったら有用かもしれません。 https://github.com/goropikari/GtkFunctionPlot.jl

f:id:goropikarikun:20181110183558p:plain