LibreOffice Calc/Basic: セル内の数式で自作マクロの関数を使う

これは LibreOffice Technology Advent Calendar 2024 の10日目の記事です。


セル内の数式で自作マクロの関数を使いたくなることがたまにあります。 たまにやろうとすると「えーっと、どうやるんだっけ……」となって手間取ってしまうので、サッとできるようにメモしておきます。

それぞれの方法にメリット・デメリットがありますから、「Basic マクロを使うことで逆に大変になってしまった」とならないようにうまく使っていきたいものですね。

準備: セキュリティレベルの設定

デフォルトではセキュリティレベルが「高」になっていてマクロが実行できません。 まずはセキュリティレベルを「中」に変更します。

(セキュリティレベルを「高」のままにして署名を使う方法もあるようですがこの記事では扱いません)

  • メニュー>ツール>オプション
  • LibreOffice>セキュリティ>マクロセキュリティ
  • 「セキュリティレベル」タブ
    • 「中」に変更する

設定変更したら

  • メニュー>ファイル>再読み込み

でマクロを有効化します。

Basicマクロで関数を書く

  • メニュー>ツール>マクロ>マクロの管理>Basic...

または

  • メニュー>ツール>マクロ>マクロの編集

からマクロのエディタウィンドウを開きます。

参考: 【LibreOfficeマクロ】Editor起動 | 新人・若手SEスキルアップNavi


例として身長(cm)と体重(kg)を受け取って BMI を算出し、さらに BMI を元に適当にクラス分けする関数を書いてみました。

(注意: 以降のコード例では変数名などに snake case を使っていますが、単に私の好みでそうしているだけです。LibreOffice Basic では変数名などは camel case にするのが慣例のようです。念の為)

option explicit

function bmi(weight_kg, height_cm)
  ' 身長を cm から m に変換
  dim height_m : height_m = height_cm / 100

  bmi = weight_kg / (height_m * height_m)
end function

function bmi_class(weight_kg, height_cm)
  dim rv ' return value
  dim bmi_val : bmi_val = bmi(weight_kg, height_cm)

  ' このクラス分けは適当なもの
  if bmi_val < 20 then
    rv = "A"
  elseif bmi_val < 30 then
    rv = "B"
  elseif bmi_val < 40 then
    rv = "C"
  else
    rv = "?"
  end if

  bmi_class = rv
end function

マクロとして書いた関数は Calc のセルの数式内でも関数として使えるようになります。

引数としてセル参照を指定すると、関数にはセルの値が渡されます。このあたりは Calc の数式で使える普通の関数と同じ挙動ですね。


うまく動かないときの対処:

  • 再計算されない(数式の処理結果が変わらない)
    • メニュー>データ>計算>再計算(F9
  • 新しく追加した関数、リネームした関数が認識されていない場合など?
    • メニュー>ファイル>再読み込み

マクロから Calc の関数を呼ぶ

例として FLOOR 関数(床関数)を扱います。

Calc のセル内の数式であれば

= FLOOR( 4.9 )

と書けば計算してくれますが、この FLOOR は Calc の関数であって Basic マクロの組み込み関数ではなく、Basic マクロでは使えません。

たとえば Basic マクロで以下のように書いても(関数が未定義なため)エラーになってしまいます。

dim a : a = FLOOR( 4.9 )

そこでどうするかというと、以下のヘルプページに情報があります。

ヘルプページに書かれている通りですが、 Calc の FLOOR 関数を呼び出す場合は以下のようになります。

function my_floor(value) as integer
  dim oService as object
  oService = CreateUnoService("com.sun.star.sheet.FunctionAccess")
  my_floor = oService.callFunction("FLOOR", Array(value))
end function

テンプレート

option explicit

function my_func()
  my_func = ... ' {関数名} = ... で返り値を設定する
end function

' エラーハンドリング
function my_func2()
  on local error goto err__my_func2

  ' ... 処理 ...

err__my_func2:
  ' エラー発生時の処理
end function

' 途中で return する場合
function my_func3()
  ' ...

  my_func3 = ...
  exit function

  ' ...
end function


' Calc の関数を使うための共通処理
function call_calc_func(func_name as string, values as array)
  dim service as object
  service = CreateUnoService("com.sun.star.sheet.FunctionAccess")
  call_calc_func = service.callFunction(func_name, values)
end function

よく使うもの

自分がよく使いそうなもの。

整数を入力して時刻に変換

時刻の入力を何度も繰り返す場合、いちいちコロン : を打つのが面倒なんですよね……。 1234(整数または文字列)と入力したら自動的に 12:34(時刻)に変換してほしい。

option explicit

function hm_to_time(arg)
  on local error goto err__hm_to_time
  dim rv as double
  dim n as double
  dim h, m as integer

  select case typename(arg)
    case "String" : n = cint(arg)
    case else     : n = arg
  end select

  h = calc_floor(n / 100)
  m = n mod 100
  rv = ((h * 60) + m) / (60 * 24)

  hm_to_time = rv
  exit function
err__hm_to_time:
  hm_to_time = "ERROR!"
end function

function calc_floor(value) as integer
  calc_floor = call_calc_func("FLOOR", Array(value))
end function

function call_calc_func(func_name as string, values as array)
  dim service as object
  service = CreateUnoService("com.sun.star.sheet.FunctionAccess")
  call_calc_func = service.callFunction(func_name, values)
end function

まあこれは

=(
  FLOOR(A1 / 100) * 60 + MOD(A1, 100)
) / (60 * 24)

でもできるので例としてはちょっと微妙かもですね。

この記事を読んだ人は(ひょっとしたら)こちらも読んでいます

qiita.com

qiita.com

qiita.com