おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

GTK -- 少し待ってから次の作業をする方法

少し待たなければいけないケース

GTK 4 は、マルチスレッドであるので、他のオブジェクトの作業完了まで待たなければいけないことがあります。

そういうときは、作業完了を知らせるシグナルを捕まえれば良いのです。 たいてい、これで解決できます。 具体的にはシグナルに、「他のオブジェクトの作業完了後にこちらで行いたいプログラム」をハンドラーとして結合します。

ところが、そのようなシグナルがない場合もあります。 自分が困ったのは、GtkColumnViewで、リストを表示するプログラム(Tcsv)でした。 私の想像では、次のようなことが行われると考えています(確証なし)。

  1. リストにアイテムを追加する
  2. リスト(GListModel)が、"items-changed"シグナルを発行する
  3. そのシグナルを受けてGtkColumnView(または関連オブジェクト)が、「GtkScrollWindowの縦方向のスクロールに関連するGtkAdjustment」のupperプロパティを1行分増やす。
  4. GtkAdjustmentは、その変更完了時に"changed"シグナルを発行する。
  5. そのシグナルにもとづき、GtkScrolledWindowの縦方向のスクロールバー(GtkScrollBar)を調整する

行の追加と同時に、新たな行(最終行)を表示するために末尾までスクロール(GtkAdjustmentのvalueプロパティをupperから1ページ分(page-sizeプロパティ)前にセットする)したかったので、次のようなことをしました。

行追加直後にGtkAdjustmentのvalueプロパティを更新する

この方法は、GtkAdjustmentのupperプロパティが更新されていないのでうまくいきません。 つまり、行の追加がGtkAdjustmentやGtkScrollWindowに反映されていないのです。 そこで、次のように方法を変えました。

GtkAdjustmentの"changed"シグナルを捕らえてvalueプロパティを更新する

この方法は、GtkAdjustmentの変更とGtkScrollBarの変更はできるのですが、画面がスクロールされませんでした。 私の想像では、GtkAdjustmentのvalueプロパティ変更がGtkScrolledWindowに伝わっていないと思われます。

GtkAdjustmentは2回シグナルを発行します。

  • upperが変更されたときに"changed"シグナルを発行する
  • valueが変更されたときに"value-changed"シグナルを発行する

GtkAdjustmentのドキュメントによると、これらのシグナルが続けて発行されるとき、それらはリカーシブ(再発行)ではなくリスタート(再スタート)されるとのことです。 つまり、シグナルは2回発行されずに、1回めの途中でそれが(キャンセルされ)再度スタートから発行手続きが始まる、ということです。 このせいかどうか分かりませんが(そうだとするとおかしな点もある)GtkScrolledWindowはupperの変更に対応しているだけで、valueの変更に対してスクロールしていません。

このあたりで行き詰まりました。

少し待ってから作業する

GtkScrolledWindowがリストの1行追加野処理を完了するまで、GtkAdjustmentのvalueを更新するのを待つ必要がありそうです。 しかし、それに適するシグナルがGtkScrolledWindowにはありません。 そうなると、「一定時間待ち、その間にGtkScrollWindowに作業してもらう」ことになります。 それを探していてg_timeout_add_onceを見つけました。

guint
g_timeout_add_once (
  guint interval,
  GSourceOnceFunc function,
  gpointer data
)
  • interval: 待ち時間。単位はミリ秒。この時間だけ待った後にfunctionが呼び出される
  • function: 待ち時間後に呼び出される関数。関数の形式は後述。
  • data: 関数に渡す引数

関数の形式は次のとおりです。

void
(* GSourceOnceFunc) (
  gpointer user_data
)

関数の引数はさきほどのdata(第3引数)です。

これを使って、リストに1行追加し、0.1秒待ってvalueプロパティを更新したら、期待した結果を得ることができました。 0.1秒というのは、経験値であり、理論的にそれが妥当かどうかはわかりません。 経験的には0.1秒あれば1行追加に対してGtkColumnViewもGtkScrolledWindowも対応が完了していました。

この方法が最適解とはいえないと思います。 しかし、現実には有効な方法でしたので、ここに記録しておくことにしました。

この関数を使ったtcsvwindow.cが次のレポジトリに収められています。 昨日のアップデートです。 scroll関数が上記のfunctionに相当します。