Julia 単体では基本的にコンソールアプリケーションしか作れませんが、サードパーティのパッケージを使用することにより GUI アプリ (ウィンドウを持つアプリ) を作ることができます。
そのひとつとして WebIO + Blink を使う方法があります。(Gtk や Tk もある)
Blink は WebIO のフロントエンドで Electron を Julia でサポートします。
Electron は Node.js (JavaScript) + HTML + CSS で GUI アプリを構築するためのプラットフォームです。
Hello Blink!
次のコードを実行すると、ウィンドウが開きそこに Hello Blink! と表示されます。
using Blink w = Window() body!(w, "<p style='text-align:center;color:red;'>Hello, Blink!</p>") println("Started Blink >") read(stdin, Char)
HTML 表示の確認
次に、ちゃんとした HTML を作ってウィンドウに表示してみます。この例では HTML テーブルを表示してみます。
using Blink function createTable() return """ <br /> <table style="margin-top:30px; margin-left: 100px;"> <thead> <tr><th>Row</th><th>A</th><th>B</th><th>C</th><th>D</th></tr> </thead> <tbody> <tr><td>1</td><td>1</td><td>2</td><td>3</td><td>0</td></tr> <tr><td>2</td><td>4</td><td>5</td><td>6</td><td>0</td></tr> <tr><td>3</td><td>7</td><td>8</td><td>9</td><td>0</td></tr> </tbody> </table> """ end head = """ <html> <head> <meta name="viewport" content="width=800,initial-scale=1"> <meta charset="utf-8" /> <title>Table</title> <style> table, th, td { border-collapse: collapse; border: solid thin gray; } th { background-color: azure; padding: 3px; font-weight: bold;} td { padding: 3px; width: 50px; text-align: right;} </style> </head> <body> """ foot = """ <body> </html> """ html = head * createTable() * foot w = Window() body!(w, html) println("Started Blink >") read(stdin, Char)
次のようにコードを追加すると、ウィンドウの大きさや位置の指定、ウィンドウの最上位に表示ができるようになります。
size(w, 480, 320) position(w, 100, 100) floating(w, true) body!(w, html)
フォーム
単に計算結果を表示するだけなら、前のサンプルプログラムでもいいですが、データを入力する場合はフォームが必要です。
ここでは、次のようなフォームを持つ Blink アプリを作ってみました。
行列の行数と列数、要素の値の範囲 (整数) を指定して「作成する」ボタンをクリックするとその下にJulia の行列を表示します。
「閉じる」ボタンをクリックすると、このアプリを終了します。
このプログラムは次のようになっています。
using Blink function html_body() htm = """ <h1>Form test</h1> <br /> <form id="form1"> <h3>テスト用 行列作成</h3> <fieldset><div>行数</div><div><input type="number" id="rows" size="8" value="3" /></div></fieldset> <fieldset><div>列数</div><div><input type="number" id="cols" size="8" value="3" /></div></fieldset> <fieldset><div>値の範囲</div><div><input type="text" id="range" size="8" value="0:9" /></div></fieldset> <fieldset><input type="button" id="button1" value=" 作成する " onclick="Blink.msg('button1Click', form1.rows.value + ',' + form1.cols.value + ',' + form1.range.value)" /> <input type='button' id="closeButton" value=' 閉じる ' onclick="Blink.msg('closeClick', 'close')" /> </fieldset> <br /> <p id="result" style="color:magenta;font-size:14pt;"></p> </form> """ return htm end head = """ <html> <head> <meta name="viewport" content="width=800,initial-scale=1"> <meta charset="utf-8" /> <title>Form</title> <style> table, th, td { border-collapse: collapse; border: solid thin gray; } th { background-color: azure; padding: 3px; font-weight: bold;} td { padding: 3px; width: 50px; text-align: right;} h1 { text-align:center; color:crimson; padding:10px; font-size:1.5em; } form { margin-left:10%; } fieldset { border-width: 0px; margin: 4px; } </style> </head> <body> """ foot = """ <p> </p> <body> </html> """ function generateMatrix(args) ns = split(args, ",") nr = parse(Int32, ns[1]) nc = parse(Int32, ns[2]) srg = split(ns[3], ":") rg = parse(Int32, srg[1]):parse(Int32,srg[2]) mat = rand(rg, nr, nc) s = "[" for i = 1:nr for j = 1:nc s *= string(mat[i, j]) if j < nc s *= " " end end if i < nr s *= "; " end end s *= "]" return s end html = head * html_body() * foot w = Window(async=false) body!(w, html, async=false) handle(w, "button1Click") do arg @async begin @show arg matrix = generateMatrix(arg) js(w, Blink.JSString("""document.getElementById('result').innerText = '$matrix';""")) end end handle(w, "closeClick") do arg exit() end println("Started Blink >") #read(stdin, Char) while true yield() end
画面に表示する HTML は変数 head, 関数 html_body(), foot で定義しています。
JavaScript 側で発生したイベント (ボタンクリック) は下の部分です。
<input type="button" id="button1" value=" 作成する " onclick="Blink.msg('button1Click', form1.rows.value + ',' + form1.cols.value + ',' + form1.range.value)" /> <input type='button' id="closeButton" value=' 閉じる ' onclick="Blink.msg('closeClick', 'close')" />
イベントは JavaScript 側からは Blink.msg(event, message) で送ります。
Julia 側では handle(window, event) で受けます。これを呼び出す前には Window オブジェクトを構築しておかなくてはなりません。ここでパラメータに async=false を指定していますが、これがないと構築動作は非同期になり、構築が完了する前に Window() が終了してしまうそうです。そのため、async = false がないと動作が不安定になる可能性があります。(試しにこの指定なしでやってみましたが、一応動きました)
w = Window(async=false)
次に body!(window, html, async) でウィンドウに HTML を貼り付けます。ここでも async=false を指定していますが、これがないと Window() と同じくウィンドウボディが構築される前に関数が終了してしまうためです。
body!(w, html, async=false)
次の handle(window, event) do arg … が「作成」ボタンのイベントハンドラです。
handle(w, "button1Click") do arg @async begin @show arg matrix = generateMatrix(arg) js(w, Blink.JSString("""document.getElementById('result').innerText = '$matrix';""")) end end
ここで @async を使って別のタスクとして実行します。そうしないと、イベントが連続して発生したときの動作が不安定になるし、それ以前にメインタスクに「再入」することになり 1 回しかイベントが動作しません。
「閉じる」ボタンのイベントハンドラには @async がありませんが、この場合、アプリが終了し、1回しかこのイベントが発生しないためです。
handle(w, "closeClick") do arg exit() end
最後には次のような while 文がありますが、これがないとアプリがウィンドウが開くと同時に終了してしまいます。
while ブロックで yield() を実行していますが、これは OS のメッセージポンプを動作させ、待機中に他の処理が行えるようにしています。
while true yield() end
この部分はキー入力待ちにしても同様に動作します。
read(stdin, Char)