この記事は、Wano Group Advent Calendar 2021 の記事です。
背景
何年かに一回WindowsのGUIアプリを作ることがありまして、その最初のバージョンをexerbを使ってRubyで作っていました。
長年それを使いまわしていたのですが、いい加減古すぎるので、新しいやり方で作ろうと探したら、libuiが見つかりましたので、それを使ってみた記録の記事です。
Rubyのコードをexeにするのには、ocraを使いました。
ちなみに筆者はRubyはほとんど書いたことありませんので、コードに変なところがあるかもしれませんが、ご容赦を。
libui
libui はポータブルなUIライブラリということで、Goにもバインディングがあります。
じゃぁ、Goで作ればいいやんという話もありますが、以前のコードを使いまわしたいところもあったので、Rubyのバインディングを使いました。ただ、ドキュメントは整備されていないので、結局Goのドキュメントを見るということにはなるのですが。
これの良いところは、普段どおり、Linuxで開発して、exeを作るときだけ、Windowsでやれば良いというところですね(ちょっとだけはまりどころがありましたが)。
作り方
Rubyのlibuiのドキュメントにもありますが、オブジェクト指向ではないので、基本的には、LibUIから生えているメソッドで各コンポーネントを作っていく感じで、それに対する操作も、LibUIのメソッドで行うと言った感じです。Goのドキュメントでは、型からメソッドが生えているので、若干読み替えが必要になります。
- Goでは、
type Entry
にSetText
があるe := ui.NewEntry()
e.SetText("text")
- Rubyでは、
UI.entry_set_text(entry, 'text')
となるe = UI.new_entry
UI.entry_set_text(e, "text")
といった感じです。
GUIアプリ作るの久しぶりすぎて、何も覚えてなくいんですが、コンポーネントの大きさは勝手に調整される感じのようです。widthやheightと言った指定がないです(Gridを使えば自由に配置できるのかもしれません)。
Boxを入れ子にしていって、なんとなく配置する感じなのかなと思いました。
コード
試しに、テキストを正規表現で置換するものを作ってみます。
# coding: utf-8
require 'libui'
UI = LibUI
def main()
UI.init
# ウィンドウを作成
main_window = UI.new_window('Sipmle Text Replace', 1000, 500, 0)
# ×ボタンでウィンドウを消せるようにする
UI.window_on_closing(main_window) do
puts 'Close window'
UI.control_destroy(main_window)
UI.quit
0
end
# 全体のbox
box = UI.new_vertical_box
# 入力/結果用のbox
box_inout = UI.new_vertical_box
# 正規表現とボタン入れる用のbox
box_regexp_button = UI.new_horizontal_box
# 全体のboxに追加
UI.box_append(box, box_regexp_button, 0)
UI.box_append(box, box_inout, 1)
# 全体のboxをウィンドウに追加
UI.window_set_child(main_window, box)
# 入力欄の作成
entry_label = UI.new_label("テキスト")
input_entry = UI.new_multiline_entry
# 結果出力の領域
result_label = UI.new_label("結果")
result_entry = UI.new_multiline_entry
## 結果なので入力できないようにする
UI.multiline_entry_set_read_only(result_entry, 1)
# 正規表現と置換文字列
regexp_label = UI.new_label("正規表現")
regexp_entry = UI.new_entry
replace_label = UI.new_label("置換文字列")
replace_entry = UI.new_entry
# 実行ボタン
button = UI.new_button('実行')
# クリアボタン
clear_button = UI.new_button('クリア')
# ボックスに追加
UI.box_append(box_inout, entry_label, 0)
UI.box_append(box_inout, input_entry, 1)
UI.box_append(box_inout, result_label, 0)
UI.box_append(box_inout, result_entry, 1)
UI.box_append(box_regexp_button, regexp_label, 0)
UI.box_append(box_regexp_button, regexp_entry, 1)
UI.box_append(box_regexp_button, replace_label, 0)
UI.box_append(box_regexp_button, replace_entry, 1)
UI.box_append(box_regexp_button, button, 0)
UI.box_append(box_regexp_button, clear_button, 0)
# 実行ボタン押された
UI.button_on_clicked(button) do
text = UI.multiline_entry_text(input_entry).to_s
regexp = UI.entry_text(regexp_entry).to_s
replace = UI.entry_text(replace_entry).to_s
if (text.empty? || regexp.empty? || replace.empty?)
UI.msg_box(main_window, 'Error', "テキスト/正規表現/置換文字列のいずれかが入力されていません")
else
result = replace(main_window, regexp, replace, text)
UI.multiline_entry_set_text(result_entry, result)
end
0
end
# クリアボタン押された
UI.button_on_clicked(clear_button) do
UI.multiline_entry_set_text(input_entry, "")
UI.entry_set_text(regexp_entry, "")
UI.entry_set_text(replace_entry, "")
end
# ウインドウを表示
UI.control_show(main_window)
UI.main
UI.quit
end
def replace(main_window, regexp, replace, text)
result = ''
begin
result = text.gsub(/#{regexp}/, replace)
rescue => e
UI.msg_box(main_window, 'Error', e)
end
return result
end
main()
見た目
このコードを、ruby sample.rb
のように実行すると、下記のように表示されます。
exeファイル化
ocra を使って、exe ファイルにします。WindowsにRubyを入れるのは、RubyInstallerを使います。
- Ruby 3.0.3-1(x64) を入れる
- コマンドプロンプトで聞かれる選択肢には、
1,2,3
を入力
- コマンドプロンプトで聞かれる選択肢には、
- インストール終わったら、コマンドプロンプトを立ち上げる
gem install libui
gem install ocra
- 下記を実行するとGUIが立ち上がるので、x ボタンで終了させる。その後、exeができあがります。
ocra sample.rb ^
--dll ruby_builtin_dlls/libssp-0.dll ^
--dll ruby_builtin_dlls/libgmp-10.dll ^
--dll ruby_builtin_dlls/libffi-7.dll ^
--gem-all=fiddle ^
--windows ^
--icon sample_icon.ico
※ ^
はLinuxの \
と同じです。
sample_icon.ico は、Windowsのiconファイルです。
こちらの記事を参考にGimpで作成しました。
はまったところ
labelの使い方
Ubuntuで動かしていたときには、動いたのに、Windowsで実行すると動かない、というのがありました。
Labelなんですが、下記のような使い方なのかなと思っていたら、
label = UI.new_label
UI.label_set_text(label, 'text')
単純に、下記で良かったです。上で書くとWindowsではエラーになりました。
label = UI.new_label('text')
というか、Go のNewLabel
もstring
引数に取るので、むしろ、Ubuntuで動いていたのがおかしいのかな...?
ocra で "openssl.so"が読み込めないというエラーが出る
今回あげたコードには含まれていませんが、実際作っていたものでは、opensslをrequireしていました。
その場合に、ocraでexeを作ることはできるのですが、実行時にエラーが発生します。
ちょっとぐぐってみると、twitterやブログにそういった記事が見つかったりました。
Goで書き直すかなーと思っていたのですが、下記のオプションを追加することでちゃんと動きました。
--dll ruby_builtin_dlls/libcrypto-1_1-x64.dll
--dll ruby_builtin_dlls/libssl-1_1-x64.dll
後、証明書関係でもエラーが出ることがあるらしいのですが、下記でいけるそうです(試してません)
--gem-file C:\Ruby30-x64\ssl
終わり
若干はまりましたが、お手軽に作れてよかったです。