RSS

タグ別アーカイブ: Blink

Julia の勉強:Julia+Blink で GUI アプリを作る

Julia 単体では基本的にコンソールアプリケーションしか作れませんが、サードパーティのパッケージを使用することにより GUI アプリ (ウィンドウを持つアプリ) を作ることができます。

そのひとつとして WebIO + Blink を使う方法があります。(Gtk や Tk もある)

Blink は WebIO のフロントエンドで Electron を Julia でサポートします。

Electron は Node.js (JavaScript) + HTML + CSS で GUI アプリを構築するためのプラットフォームです。

次のコードを実行すると、ウィンドウが開きそこに 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)" />&nbsp;
            <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>&nbsp;</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)

 
コメントする

投稿者: : 2021/12/15 投稿先 Julia

 

タグ: , , ,