【DaVinci Resolve API】イベントを任意発生・抑制する方法(3/31更新)
まえがき
久しぶりの記事更新です。
もうすぐ2022年も終わりますが、皆さんはどんな1年でしたか?
私は年始に動画を出してから1度も動画を出すことはありませんでした。
ふふ。
ボイロ連携用のスクリプトも結局完成していませんし、そうこうしているうちにソフトウェアトーク連携用スクリプトを色んな人が作っているのでもう頑張る意味ないよね……ってなってます。
でももう少しで形になりそうなので弄ります。楽しいし。
来年はもうちょっと動画作りたいですね。
オノゴロ物語のスコアアタックとか、プレイ動画は撮ってるので解説動画は出したいところです。
イベントを意図的に発生させる方法
QueueEventを使う
さて、本題に入りましょう。
DavinciResolveのGUI処理ではイベントハンドラをよく用います。
例えば、各ウィジェットを操作して「ボタンを押したら」「値を変更したら」「文字を変更したら」→「こういう処理をする」という流れがありますよね。
ざっくり言うと「〇〇をしたら~」がイベントで、「イベントが起きたらどういう処理をするか?」がイベントハンドラです。
「テキストエディタの文字列変更」イベントを検知→「ラベルに表示される数値を+1する」という処理を実行みたいに使うわけですね。
で、まあ。
普通は各ウィジェットを操作しないとイベントは発生しないのですが、QueueEventメソッドによってある程度任意のタイミングで発生させることができます。
「そんなん何に使うの……?」って声が聞こえてきそうですが、私も知りません。
なんかそんなことが出来るらしいってわかったので記事にしただけですから…………ちゃんとした使い道は各自で探してください。
ただまあ、普通に呼び出された場合と違って、追加で引数を渡せるのがメリットでしょうか。
これを利用してイベントの一時抑制することも可能ではあります。(絶対に主要の使い方ではないけれど)
テストコード
とりあえず、今回テストに使ったコードは以下の通り。
言語はLuaです。Pythonで組む人はこう、上手い感じに適宜読み替えてください。
ui = fu.UIManager
disp = bmd.UIDispatcher(ui)
local width,height = 300,200 -- 初期ウィンドウサイズ
-- ウィンドウのUI配置設定
win = disp:AddWindow({
ID = 'MyWin',
WindowTitle = 'Voice入力ウィンドウ',
Geometry = { 100, 100, width, height },
Spacing = 10,
Events={Close=true, Show=true, KeyPress=true, KeyRelease=true, FocusIn=true, FocusOut=true, Enter=true, Leave=true, ContextMenu=true},
ui:VGroup{
ui:HGroup{
-- 普通にテキストを追加するボタン
ui:Button{
ID = "ButtonTestAppend",
Text = "文字列追加"
},
-- イベントを追加するボタン
ui:Button{
ID = "ButtonTestQueueEvent",
Text = "イベント追加"
}
},
ui:TextEdit{
ID = "TextEditTest",
TabStopWidth = 1, --Tab文字が半角スペース何文字分か設定(0だとデフォルトになる模様。よって最低値は1)
Text="",
PlaceholderText="ここに台詞を入力してください",
Events = {TextChanged = true}
},
ui:Label{
ID = "LabelTest",
Text= "0"
}
}
})
local itm = win:GetItems()
local chg_cnt = 0
-- イベントハンドラ:ボタンクリック時
function win.On.ButtonTestAppend.Clicked(ev)
itm.TextEditTest.PlainText = "ボタンで設定しました"
end
-- イベントハンドラ:ボタンクリック時
function win.On.ButtonTestQueueEvent.Clicked(ev)
itm.TextEditTest.PlainText = "ボタンで設定しました1" -- テキストエディタへの文字列設定1回目
itm.TextEditTest:QueueEvent("TextChanged",
{AppendArgment1=true, AppendArgment2="追加引数2"}) -- イベントキューへの追加(引数も追加)
itm.TextEditTest.PlainText = "ボタンで設定しました2" -- テキストエディタへの文字列設定2回目
end
-- イベントハンドラ:テキストエディタ内の文字列変更時
function win.On.TextEditTest.TextChanged(ev)
-- evの要素をコンソールへ出力して確認
print("■■■■■■■■■■■■■")
for k,v in pairs(ev) do
print("[" .. k .. "]:" .. tostring(v))
end
print("■■■■■■■■■■■■■")
chg_cnt = chg_cnt + 1 -- 呼び出し回数カウントアップ
itm.LabelTest.Text = "文字列変更回数:" .. chg_cnt --ラベル表示文字列変更
end
-- クローズハンドラ:ウィンドウ右上の✕ボタンクリック時
function win.On.MyWin.Close(ev)
disp:ExitLoop()
end
-- ウィンドウ表示・操作受付開始
win:Show()
disp:RunLoop()
win:Hide()
コードの実行結果
コードを実際に実行し、イベント追加ボタンを押した結果がこちら。
イベント追加ボタンを押すと
1.テキストエディタにテキスト設定
2.テキストエディタの「TextChangedイベント」をイベントキューに追加
3.テキストエディタにテキスト設定(2回目)
という順番に処理が実行されます。
テキストエディタはTextChangedイベントを有効にしているため、文字列が変更されるとイベントハンドラが起動します。
イベントハンドラの処理の中身は
1.イベントハンドラに渡される属性辞書の中身の表示
2.文字列変更回数のカウントアップと、ラベル表示更新
ですね。
実際に文字列を変更したのは2回だけですが、文字列変更回数は3になっているため、イベントの任意発動ができていることが確認できます。
ですが、「QueueEventを実行すれば即イベントが発動するんだね!」と勘違いしないようにお願いします。
QueueEventはあくまで「指定イベントをイベント処理順番の最後に追加する」だけです。
DavinciResolve GUIのイベント処理について
DavinciResolveのGUIイベントは割り込みがありません。
何かの関数内で複数のイベントが発生した場合、関数処理が終了した後、発生した順番にイベントが処理されます。イベント用のキュー(FIFO:First Input First Output、先に入れたものが先に出てくる方式)で管理されているわけですね。
イベントAの処理中に別のイベントBが発生した場合も、イベントBは処理待ち列の最後に追加されます。
なので、上記の例でイベント追加ボタンを押した場合は
・イベント追加ボタンを押した関数の処理終了
・1回目の文字列変更でTextChangedイベント追加(1番目)
・QueueEventでTextChangedイベント追加(2番目)
・2回目の文字列変更でTextChangedイベント追加(3番目)
という順番に処理されます。
繰り返しになりますが、QueueEventを実行した直後にそのイベント処理が始まるわけではない点には注意してください。
QueueEventの実行前に何かしらのイベントAが発生していれば、そのイベントAの処理が終わってからQueueEventで追加したイベントBの処理が始まります。
ひょっとしたら私が知らないだけでイベント処理はそれが常識なのかもしれませんけれど、まあ私は知らないので……一応、念のため。
QueueEventで任意発生させるメリット
QueueEventでイベントを任意発生させる明確なメリットとしては、イベントハンドラへ追加引数を渡せることでしょうか。
普通にイベントを検知した場合、渡される引数の属性は以下の通りです。
引数evの持つ属性:
who :(string)イベント発生したウィジェットID
what :(string)イベントの種類
when :(double)検知日時
window :(string)ウィジェットが配置されたウィンドウID
modifiers(table):(address)変更したかどうか? アドレスの変化を見ても規則性がわからず。
sender :(テーブル?)ウィジェットの種類?
その他、イベントの種類によって追加属性あり。
QueueEventは第1引数にイベント名、第2引数にイベントハンドラへ渡す追加属性のテーブルを渡して実行します。
QueueEvent(EventName, {info})
-- 実行例(テストコードより抜粋)
local itm = win:GetItems() -- ウィンドウからすべての子要素辞書を取得
-- イベントキューへの追加(第2引数は {} のように空のテーブルを渡せばデフォルト引数のみとなる)
itm.TextEditTest:QueueEvent("TextChanged",
{AppendArgment1=true, AppendArgment2="追加引数2"})
第1引数のイベント名については"TextChanged"みたいに書くだけなので特に困ることはありませんね。
第2引数については「{}」と空のテーブルを渡した場合、先に紹介したデフォルト属性のみがイベントハンドラへ渡ります。
何か要素を追加したテーブルを渡した場合、デフォルト属性+追加属性がイベントハンドラへ渡ります。
テストコードではQueueEventでイベントを追加した際、イベントハンドラへ渡す属性も追加しておきました。
改めて、テストコードでコンソールに出力された属性一覧を見てみます。
■■■■■■■■■■■■■
[when]:1008684.531
[sender]:UITextEdit (0x000002A1B50B3AD0) [App: 'Resolve' on 127.0.0.1, UUID: 84c6309c-4249-4b68-8e55-44ec31c30bb0]
[modifiers]:table: 0x031b9240
[window]:MyWin
[who]:TextEditTest
[what]:TextChanged
■■■■■■■■■■■■■
■■■■■■■■■■■■■
[AppendArgment1]:true
[AppendArgment2]:追加引数2
[when]:1008684.531
[sender]:UITextEdit (0x000002A1B50B3AD0) [App: 'Resolve' on 127.0.0.1, UUID: 84c6309c-4249-4b68-8e55-44ec31c30bb0]
[modifiers]:table: 0x031d68a8
[window]:MyWin
[who]:TextEditTest
[what]:TextChanged
■■■■■■■■■■■■■
■■■■■■■■■■■■■
[when]:1008684.531
[sender]:UITextEdit (0x000002A1B50B3AD0) [App: 'Resolve' on 127.0.0.1, UUID: 84c6309c-4249-4b68-8e55-44ec31c30bb0]
[modifiers]:table: 0x031a02b8
[window]:MyWin
[who]:TextEditTest
[what]:TextChanged
■■■■■■■■■■■■■
QueueEventで任意発生させた2回目だけ「AppendArgment1」「AppendArgment2」という属性が追加されています。
追加属性の有無を条件分岐に組み込めば、普通に発生したイベントなのか、QueueEventで任意発生させたイベントなのか区別をつけられるでしょう。
イベントの任意実行についての説明は以上となります。
イベントを一時的に抑制する方法(12/27更新)
ところで、私が知りたかったのはこれの逆……イベントの一時的な抑制だったりします。
TextEditウィジェットが持つTextChangedイベントって、キーボードからの手入力とスクリプトからの自動入力を区別してくれないんですよね。
なので、スクリプトからの文字列変更するときだけイベント処理させないようにしたいのですが、フラグ管理のまあ面倒なこと。
関数Aの中で発生したイベントBは、関数Aが終了してから処理されるため、関数Aの中でフラグ管理を完結させられないんですよね。
そしてイベントBが終了した時点では関数Aは実行されていないため、フラグを戻す処理を関数CとかイベントDとかに記述しなくてはならない。
要するに、イベントの一時抑制が出来ない場合、フラグの有効化と無効化の処理が複数の関数にばら撒かれるため、フラグ管理が凄く大変になるということです。ふぁっきん。
イベントの一時抑制方法はないかなぁと色々調べていたらQueueEventの機能がわかったため、DavinciResolveスクリプトの情報は多い方が良いよねということで記事を投稿した次第です。
で、記事を投稿した12/26の時点では一時的な抑制は無理だと思っていたのですが、その翌日にやり方がわかりました。
イベントは追加された順番に処理されます。
そして、QueueEventではイベントハンドラへ追加属性を渡すことができます。
そこで閃きました。
QueueEventでイベントの処理フラグ管理できるんじゃない?
ざっくり言うと、
1.QueueEventでTextChangedイベント追加しつつフラグ属性(False)を渡す
2.テキストエディタの文字列を変更(普通のTextChangedイベント追加)
3.QueueEventでTextChangedイベント追加しつつフラグ属性(True)を渡す
という感じで、抑制したいイベントが発生する前後をQueueEventで挟みます。
また、TextChangedイベント内ではフラグ属性がある場合はイベントの有効化・無効化を行い、そうでない場合はイベント処理をする。
ただし、フラグが無効化状態ならイベント処理を実行しない。
という風に記述を変更すればOKです。
テストコード
イベントを意図的に発生させるテストコードの内、イベント追加ボタンと、TextChangedイベントハンドラの内容を以下のように変更します。
また、フラグ管理用変数も追加しています。
local flg = true -- TextChangedイベントの有効化フラグ
-- イベントハンドラ:ボタンクリック時
function win.On.ButtonTestQueueEvent.Clicked(ev)
itm.TextEditTest.PlainText = "ボタンで設定しました1"
itm.TextEditTest:QueueEvent("TextChanged", {flg=false}) -- イベント処理を一時的に無効化
itm.TextEditTest.PlainText = "ボタンで設定しました2"
itm.TextEditTest.PlainText = "ボタンで設定しました3"
itm.TextEditTest:QueueEvent("TextChanged", {flg=true}) -- イベント処理を再度有効化
itm.TextEditTest.PlainText = "ボタンで設定しました4"
end
-- イベントハンドラ:テキストエディタ内の文字列変更時
function win.On.TextEditTest.TextChanged(ev)
-- QueueEventで任意発生してフラグ属性が追加されていた場合、TextChangedイベントの有効/無効を設定
if ev.flg ~= nil then
flg = ev.flg
print("イベントフラグを設定します:" .. tostring(flg))
return
end
-- TextChangedイベントのフラグが無効なら処理中断
if not flg then
print("イベントフラグが無効のため、イベント処理を中断します")
return
end
-- evの要素をコンソールへ出力して確認
print("■■■■■■■■■■■■■")
for k,v in pairs(ev) do
print("[" .. k .. "]:" .. tostring(v))
end
print("■■■■■■■■■■■■■")
chg_cnt = chg_cnt + 1 -- 呼び出し回数カウントアップ
itm.LabelTest.Text = "文字列変更回数:" .. chg_cnt --ラベル表示文字列変更
end
コードの実行結果
■■■■■■■■■■■■■
[what]:TextChanged
[when]:1048942.031
[sender]:UITextEdit (0x00000237ECB40D70) [App: 'Resolve' on 127.0.0.1, UUID: ad9bf916-2b0d-4e82-be57-90d34e52fcd5]
[modifiers]:table: 0x00989f30
[window]:MyWin
[who]:TextEditTest
■■■■■■■■■■■■■
イベントフラグを設定します:false
イベントフラグが無効のため、イベント処理を中断します
イベントフラグが無効のため、イベント処理を中断します
イベントフラグを設定します:true
■■■■■■■■■■■■■
[what]:TextChanged
[when]:1048942.031
[sender]:UITextEdit (0x00000237ECB40D70) [App: 'Resolve' on 127.0.0.1, UUID: ad9bf916-2b0d-4e82-be57-90d34e52fcd5]
[modifiers]:table: 0x014a0c60
[window]:MyWin
[who]:TextEditTest
■■■■■■■■■■■■■
テキストエディタの文字列は4回変更していますが、QueueEventでイベント処理を無効化した2回目、3回目のイベント処理では引数の属性が出力されていません。
イベント処理自体は関数の終了後に実行されますが、その後もフラグ無効化イベント→抑制対象のイベント→フラグ有効化イベントという順番で処理されてます。
フラグ管理用のイベントを予約するという形で、ちゃんと一つの関数の中でイベントの処理フラグ管理を完結させることができました。
やっっっっっっったぁ!!!!!!
イベントフラグ管理をもっと簡単にする(12/30更新)
ただ、まだ使い勝手が悪いです。
Luaはstaticのような静的変数の宣言がないため、イベント処理が終わってもフラグ情報が消えないように保持し続けるにはグローバル変数を利用する必要があるんですよね。
このままではウィジェット毎、イベント毎にフラグ管理をするために別名のグローバル変数をいくつも用意する羽目になります。
一応、Luaにはクロージャと言う概念があるようで、それを使えば静的変数みたいなものは実装できます。
クロージャ自体はオブジェクト指向でいうクラスに近いのかもしれませんが、私はLuaの知識あやふやなまま使ってるので厳密な定義とか触れないでおきましょう。そういう言語についての解説は専門サイトに任せます。
クロージャでフラグ管理してみる
さて、とりあえずクロージャを使ってフラグ管理をしてみます。
クロージャについては「お気楽 Lua プログラミング超入門」を参考にさせていただきました。
下記のコードはジェネレータ(呼び出すたびに結果が変わるタイプの関数)の例コードですが、実にシンプルでクロージャとしてもわかりやすいのでこちらを引用させていただきます。
-- 上記コードの実行結果
> a = make_gen_odd_num()
> a()
1
> a()
3
> a()
5
> a()
7
> b = make_gen_odd_num()
> b()
1
> b()
3
> b()
5
クロージャの説明は自信がありませんが、今回は関数処理が終わっても値を保持できる静的変数とセットの関数を作成できることだけわかればOK
OKでしょう。
イベントフラグ管理できるように以下の条件を満たすクロージャを作ります。
関数の中でフラグ管理用の変数をlocal宣言する
関数の返却値に、イベントハンドラ本体の処理を行う無名関数を定義する
以上を踏まえた上でイベントフラグ管理変数を持ったイベントハンドラの記述はこんな感じになります。
-- イベントハンドラ:テキストエディタ内の文字列変更時
tmpfunction = function() -- クロージャを変数に代入
local flg = true -- イベント有効化フラグ(初期値:有効)
return function(ev) -- イベントハンドラとして設定する処理関数
-- QueueEventで任意発生、かつフラグ属性があった場合、TextChangedイベントの有効/無効を設定
if ev.flg ~= nil then
flg = ev.flg
print("イベントフラグを設定します:" .. tostring(flg))
return
end
-- TextChangedイベントのフラグが無効なら処理中断
if not flg then
print("イベントフラグが無効のため、イベント処理を中断します")
return
end
-- evの要素をコンソールへ出力して確認
print("■■■■■■■■■■■■■")
for k,v in pairs(ev) do
print("[" .. k .. "]:" .. tostring(v))
end
print("■■■■■■■■■■■■■")
chg_cnt = chg_cnt + 1 -- 呼び出し回数カウントアップ
itm.LabelTest.Text = "文字列変更回数:" .. chg_cnt --ラベル表示文字列変更
end
end
win.On.TextEditTest.TextChanged = tmpfunction() -- クロージャを実行し、イベントハンドラに処理関数設定
気を付けておきたいこととして、イベントハンドラに設定するのはクロージャ本体ではなく、クロージャを実行した結果返却される値(無名関数)の方です。
コードの最後でイベントハンドラに関数を設定していますが、ちゃんと()を付けて関数として実行することを忘れないようにしてください。
で、これでグローバル変数がうぞうぞと増える自体は回避した……のですけれど。
これはこれで、
イベントハンドラ1つに付きクロージャ宣言+クロージャ実行によるイベントハンドラへの処理設定
という記述が常に必要になります。
イベントハンドラが少ないうちは良いでしょうが、GUIが複雑になればなるほどイベントハンドラは増えていきます。
クロージャ宣言と代入部分も1~2行の追記とはいえ、最終的に何度書くことになるかは変わってきますし、記述頻度が増えればミスも増えます。
最初に設定だけ済ませたら、後はなるべく不要な処理を書かずに済ませたいところです。
テーブルでフラグ管理してみる
幸いというべきか、イベントハンドラのフラグだけならウィジェット名とイベント名さえわかれば一括管理が可能です。
なので、もう少し楽になるようにフラグ管理用のテーブルを考えます。
フラグ管理用テーブルに求められる機能は以下の通りです。
ウィジェット名とイベント名を指定してフラグを取得・設定できる
テーブルに未登録のウィジェット名、イベント名のフラグを取得する場合はデフォルトでTrueを返す(事前に一々有効化しておく手間を省くため)
これらの条件を満たしたテーブルはこんな感じになりました。
テーブル内に変数とgetter/setterメソッドを持つのでほぼオブジェクトですね。
-- イベントフラグ管理用テーブル
local EventFlags = {
flg = {}, -- フラグ管理用テーブル
-- イベントの有効化フラグの返却メソッド
getEnabled = function(self, element, event)
if self.flg[element] == nil then -- 指定のウィジェット名のフラグが登録されていない場合、デフォルト値としてtrueを返す
return true
elseif self.flg[element][event] == nil then -- 指定のイベント名のフラグが登録されていない場合、デフォルト値としてtrueを返す
return true
else
return self.flg[element][event] --指定のウィジェット名、イベント名のフラグを返却する
end
end,
-- イベントの有効化フラグテーブルの設定メソッド
setEnabled = function(self, element, event, bool)
-- 設定値が真偽値以外なら中断
if type(bool) ~= "boolean" then
print("EventFlag:setEnabled():" .. "設定値がboolean型ではありません。イベント有効化フラグの設定を中断します")
return false
end
-- 設定値が真偽値なら設定
if self.flg[element] == nil then -- ウィジェット名が未登録の場合
self.flg[element] = {} -- 指定ウィジェット名のキーにイベント用の空のテーブルを代入
end
self.flg[element][event] = bool -- フラグを設定
return true
end
}
getEnabledでフラグ状態を取得、setEnabledでフラグ状態を設定します。
さらに、TextChangedイベントハンドラで参照していたフラグ用変数をフラグ管理用テーブルに置き換えます。
-- イベントハンドラ:テキストエディタ内の文字列変更時
function win.On.TextEditTest.TextChanged(ev)
-- QueueEventで任意発生、かつフラグ属性があった場合、イベントの有効/無効を設定
if ev.flg ~= nil then
EventFlags:setEnabled(ev.who, ev.what, ev.flg)
print("イベントフラグを設定します:" .. tostring(EventFlags:getEnabled(ev.who, ev.what)))
return
end
-- TextChangedイベントのフラグが無効なら処理中断
if not EventFlags:getEnabled(ev.who, ev.what) then
print("イベントフラグが無効のため、イベント処理を中断します")
return
end
-- evの要素をコンソールへ出力して確認
print("■■■■■■■■■■■■■")
for k,v in pairs(ev) do
print("[" .. k .. "]:" .. tostring(v))
end
print("■■■■■■■■■■■■■")
chg_cnt = chg_cnt + 1 -- 呼び出し回数カウントアップ
itm.LabelTest.Text = "文字列変更回数:" .. chg_cnt --ラベル表示文字列変更
end
フラグの取得・設定処理の記述が少し長くなってしまいましたが、
イベントハンドラに渡されるデフォルト属性「ev.who」がウィジェット名、「ev.what」がイベント名としてフラグ取得・設定用メソッドの引数へそのまま渡せるため、どのイベントハンドラでも全く同じ記述でフラグ管理ができるようになりました。
やったね。
Luaのメタテーブル機能を使えばメソッドなしのもっとシンプルなテーブルとして使えるのかもしれませんが、残念ながら今の私が書ける限界はこんなところです。メタテーブル難しい。
クラスでフラグ管理してみる(2023/3/31追記)
しかし、同じ記述で済むようになったと言っても、イベントハンドラ全てに記述していくとなると短い方が確認しやすくて嬉しいですよね。
できれば1~3行程度で済むのが望ましいです。
Luaのメタテーブルやらクラスやらについて理解が深まったのでさらにさらに効率化してみます。
クラス名
WidgetEventFlags
メソッド一覧(4つ)
1.コンストラクタ:new(GUIウィンドウ)
引数に渡したGUIウィンドウ用のイベントフラグ管理インスタンスを作成して返却します。
2.イベント有効化:enabled(ウィジェット名、イベント名)
指定したウィジェット・イベントのイベント発生フラグを有効にします。
3.イベント無効化:disabled(ウィジェット名、イベント名)
指定したウィジェット・イベントのイベント発生フラグを無効にします。
4.イベントフラグの参照・更新:check(ウィジェット名、イベント名、フラグ)
イベントハンドラの冒頭で呼んで使います。
フラグ(bool値)がない場合は設定されているイベントフラグを取得します。特に設定されていない場合はデフォルトでtrueを返却します。
フラグ(bool値)がある場合はイベントフラグの設定を行います。この場合、イベントフラグの設定のみが目的だと判断し、設定値に関わらずメソッドはFalseを返します。
イベント管理用クラスのコード全体は下記の通り。
-- WidgetEventFlags.lua
-- ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-- DavinciResolve GUI ウィジェットのイベントフラグ管理クラス
-- クラス宣言
local WidgetEventFlags = {}
--[[
コンストラクタ
name: WidgetEventFlags.new
@param _window(window):イベントフラグを管理したいウィンドウ
@return インスタンス
--]]
WidgetEventFlags.new = function(_window)
-- 引数チェック
if _window == nil then
error("引数不足:イベントフラグを管理するウィンドウが指定されていません")
end
local obj= {} -- 返却用インスタンス
-- インスタンスのメンバ変数初期化
obj.flg = {} -- フラグ管理用テーブル
obj.win = _window -- イベントフラグを管理したいウィンドウ
--インスタンスに設定されていないメンバ、メソッドはクラスを参照するように設定
setmetatable(obj, {__index=WidgetEventFlags})
return obj
end
--[[
イベントの発生フラグの有効化予約メソッド
name: WidgetEventFlags.enabled
@param _element_name(string):ウィジェット名
_event_name(string):イベント名
@return
--]]
WidgetEventFlags.enabled = function(self, _element_name, _event_name)
local itm = self.win:GetItems() -- ウィンドウ内のウィジェット一覧取得
itm[_element_name]:QueueEvent(_event_name, {flg = true}) -- 指定したウィジェットのイベント有効化操作を予約
end
--[[
イベントの発生フラグの無効化予約メソッド
name: WidgetEventFlags.disabled
@param _element_name(string):ウィジェット名
_event_name(string):イベント名
@return
--]]
WidgetEventFlags.disabled = function(self, _element_name, _event_name)
local itm = self.win:GetItems() -- ウィンドウ内のウィジェット一覧取得
itm[_element_name]:QueueEvent(_event_name, {flg = false}) -- 指定したウィジェットのイベント無効化操作を予約
end
--[[
イベント発生フラグの設定・参照メソッド
name: WidgetEventFlags.check
@param _element_name(string):ウィジェット名
_event_name(string):イベント名
_bool(boolean):イベントの有効化フラグ(trueで有効化、falseで無効化)
@return _boolの値によって変化
_boolがnil(未指定)の場合:現在の設定値を返却(true:有効, false:無効)
_boolがtrue, falseの場合:falseを返却
特に設定していないイベントの場合はデフォルトでtrueを返却する。
--]]
WidgetEventFlags.check = function(self, _element_name, _event_name, _bool)
-- ウィジェット名が未登録の場合、イベント用の空テーブルを設定
if self.flg[_element_name] == nil then
self.flg[_element_name] = setmetatable( {}, {__index = function() return true end})
end
-- flgがないなら設定ではないと判断し、イベントフラグの有効無効状態を取得して返す
if _bool == nil then
return self.flg[_element_name][_event_name]
end
-- flgがある場合はフラグ設定と判断し、フラグ設定後にfalseを返却する
-- 設定値が真偽値以外なら中断
if type(_bool) ~= "boolean" then
error("WidgetEventFlags:check():" .. "第3引数がboolean型ではありません。イベント有効化フラグの設定を中断します")
return false
end
-- 設定値が真偽値なら設定
self.flg[_element_name][_event_name] = _bool --フラグ設定
if _bool then
print("イベント発生を有効化:" .. _element_name .. "." .. _event_name)
else
print("イベント発生を無効化:" .. _element_name .. "." .. _event_name)
end
return false
end
return WidgetEventFlags
いや、Luaのメタテーブル便利ですね。
__indexにtrue返す無名関数を設定しておくだけで、テーブルにイベント登録してるかどうかを検索しなくて済むのが楽。
それからフラグ管理テーブルと違ってフラグ有効化・無効化のメソッドを追加しました。
やってることはQueueEventでフラグを渡しているだけですが、こちらの方がわかりやすいですからね。。
また、クラス作成のついでにモジュールとして本体のコードとは別に分けました。
WidgetEventFlagsクラスを使いたい場合、使用したいコードの中でrequie関数で読み込んで使用します。
DaVinci Resolve スクリプトでrequie関数を用いてLuaモジュールを読み込みたい場合、デフォルトの読み込み先は「C:\ProgramData\Blackmagic Design\DaVinci Resolve\Fusion\Modules\Lua\」フォルダとなっています。
モジュールファイルはここに置いておきましょう。
フラグ管理改善版テストコード(2023/3/31更新)
イベントフラグ管理クラスのモジュールを専用フォルダに置いて読み込み、イベントの抑制をする本体コードは最終的に下記の通り。
-- ◆ウィジェットイベントフラグ管理用クラスを読み込み
-- Luaモジュールは[[C:\ProgramData\Blackmagic Design\DaVinci Resolve\Fusion\Modules\Lua\]]に置いておくこと
local WidgetEventFlags = require("WidgetEventFlags")
-- ウィンドウのUI配置設定
ui = fu.UIManager
disp = bmd.UIDispatcher(ui)
local width,height = 300,200 -- 初期ウィンドウサイズ
win = disp:AddWindow({
ID = 'MyWin',
WindowTitle = 'Voice入力ウィンドウ',
Geometry = { 100, 100, width, height },
Spacing = 10,
Events={Close=true, Show=true, KeyPress=true, KeyRelease=true, FocusIn=true, FocusOut=true, Enter=true, Leave=true, ContextMenu=true},
ui:VGroup{
ui:HGroup{
-- 普通にテキストを追加するボタン
ui:Button{
ID = "ButtonTestAppend",
Text = "文字列追加"
},
-- イベントを抑制しながらテキストを追加するボタン
ui:Button{
ID = "ButtonTestQueueEvent",
Text = "イベント追加"
}
},
ui:TextEdit{
ID = "TextEditTest",
TabStopWidth = 1, --Tab文字が半角スペース何文字分か設定(0だとデフォルトになる模様。よって最低値は1)
Text="",
PlaceholderText="ここに台詞を入力してください",
Events = {TextChanged = true}
},
ui:Label{
ID = "LabelTest",
Text= "0"
}
}
)
local itm = win:GetItems()
local chg_cnt = 0
-- ◆イベントフラグ管理初期化(インスタンス作成)
local event_flags = WidgetEventFlags.new(win)
-- イベントハンドラ:ボタンクリック時
function win.On.ButtonTestAppend.Clicked(ev)
itm.TextEditTest.PlainText = "ボタンで設定しました"
end
-- イベントハンドラ:ボタンクリック時
function win.On.ButtonTestQueueEvent.Clicked(ev)
itm.TextEditTest.PlainText = "ボタンで設定しました1"
event_flags:disabled("TextEditTest", "TextChanged") -- イベント処理を一時的に無効化
itm.TextEditTest.PlainText = "ボタンで設定しました2"
itm.TextEditTest.PlainText = "ボタンで設定しました3"
event_flags:enabled("TextEditTest", "TextChanged") -- イベント処理を再度有効化
itm.TextEditTest.PlainText = "ボタンで設定しました4"
end
-- イベントハンドラ:テキストエディタ内の文字列変更時
function win.On.TextEditTest.TextChanged(ev)
-- ◆イベント発生フラグの設定参照(フラグ設定、もしくは参照して無効化されてた場合は中断)
if not event_flags:check(ev.who, ev.what, ev.flg) then return end
-- evの要素をコンソールへ出力して確認
print("■■■■■■■■■■■■■")
for k,v in pairs(ev) do
print("[" .. k .. "]:" .. tostring(v))
end
print("■■■■■■■■■■■■■")
chg_cnt = chg_cnt + 1 -- 呼び出し回数カウントアップ
itm.LabelTest.Text = "文字列変更回数:" .. chg_cnt --ラベル表示文字列変更
end
-- クローズハンドラ:ウィンドウ右上の✕ボタンクリック時
function win.On.MyWin.Close(ev)
disp:ExitLoop()
end
-- ウィンドウ表示・操作受付開始
win:Show()
disp:RunLoop()
win:Hide()
イベントハンドラ冒頭に記述するイベント発生フラグの参照・設定が1行で済むようになりました。
また、フラグの有効化・無効化も専用メソッドでわかりやすくなったのも可読性が上がって良かった……はず。上がってますよね?
理想としてはイベントハンドラ内部にif文を記述する必要なく、イベントハンドラの外部から管理(ON/OFF)できればそれが一番良い(要するに登録したイベントハンドラを検知するイベントハンドラ的なのが実装できると良い)のですが、流石にそこまでは無理そう。
いやLuaの公式リファレンスマニュアルを読む限りではdebugライブラリを使えば実装できなくはないようですが、debugライブラリは処理が重いらしいのであまり気は進みません。
イベント発生フラグ管理クラスのモジュールファイル
一応、イベント発生フラグ管理クラスはモジュールファイルを上げておきます。
使いたい方はご自由にどうぞ。
あとがき
QueueEventを利用してイベントを任意発生、一時的に抑制する方法については以上となります。
イベントキューの処理はどのプログラムにもあるため、この記事の内容も結局は車輪の再発明なのでしょう。
私もDavinciResolveに拘らず、イベントキュー処理の面で検索をかければもっと早く解決策を見つけられたかも。
ちゃんとプログラミングの勉強をしている人ならあっさり解決できたかもしれません。やっぱり勉強は大事ですね。
知識を調べるためには、それを調べるための前提知識がいる。基礎を知らずに応用を調べることは叶わないと。うーむ。
では、ここまで読んでいただき、ありがとうございました。
この情報が少しでもお役に立てば幸いです。