C言語GUI入門|イベント駆動型プログラミングとは

GUIアプリは、ウィンドウを表示した後、ボタンのクリックやキーボードからの入力など、ユーザーの操作を待ちながら動作します。このような出来事をきっかけとして処理を実行する方法を、イベント駆動型プログラミングといいます。

コンソールアプリでは、プログラムに書かれた処理が上から順番に進み、最後まで到達すると終了する構成がよく使われます。一方、GUIアプリでは、ユーザーが次にどのボタンを押すのか、いつ文字を入力するのかを、プログラム側であらかじめ決めることができません。

そこでGUIアプリは、ユーザーからの操作を待ち、イベントが発生したら対応する関数を実行します。このときに呼び出される関数がコールバック関数です。

この記事では、イベント、メインループ、シグナル、コールバック関数の関係を、ボタンのクリックとキーボード入力を例に解説します。第1章では具体的なプログラムをまだ作成せず、イベント駆動型プログラミングの動作をイメージできるようになりましょう。

イベント駆動型プログラミングとは

イベント駆動型プログラミングとは、ユーザーの操作やシステム上の出来事をきっかけとして、必要な処理を実行するプログラムの作り方です。

GUIアプリでは、ユーザーがどのような順番で操作するか分かりません。

たとえば、GUI電卓を起動したユーザーは、次のような操作を行う可能性があります。

  • 1つ目の入力欄に数値を入力する
  • 2つ目の入力欄を先に選択する
  • 足し算ボタンをクリックする
  • 入力内容を消去する
  • キーボードのEnterキーを押す
  • ウィンドウの大きさを変える
  • 何も入力せずにウィンドウを閉じる

プログラムが操作の順番を一方的に決めるのではなく、ユーザーの操作に応じて実行する処理を選ぶ必要があります。

そのため、GUIアプリには、どのような操作が行われたかを受け取り、操作ごとに適切な処理を実行する仕組みが用意されています。

GUIアプリは操作を待ち続ける

GUIアプリを起動すると、最初にウィンドウやボタンなどの画面が作成されます。

画面の準備が終わった後、GUIアプリはすぐには終了しません。ユーザーが何かを操作するまで待ちます。

ユーザーがボタンをクリックすると、クリックに対応する処理を実行します。その処理が終わると、再び次の操作を待ちます。

大まかな流れは次のとおりです。

  1. GUIアプリを起動する
  2. ウィンドウやウィジェットを作成する
  3. ウィンドウを表示する
  4. ユーザーの操作を待つ
  5. 操作が行われたら対応する処理を実行する
  6. 必要に応じて画面を更新する
  7. 再びユーザーの操作を待つ
  8. 終了操作が行われたらアプリを終了する

待機、処理、画面更新という流れは、アプリケーションが終了するまで繰り返されます。

待っている間に終了しているわけではない

ユーザーが何も操作していない間も、GUIアプリは終了していません。

ウィンドウを表示したまま、マウスやキーボードからの操作、ウィンドウの状態変化などを受け取れる状態になっています。

ただし、何も起きていないのに同じ処理を高速で繰り返しているわけではありません。処理するものがなければ待機し、イベントが届いたときに必要な処理を再開します。

メインループ

GUIアプリが操作を待ち続けるための中心的な仕組みを、メインループと呼びます。

GTK 4の公式ドキュメントでも、操作がない間はメインループで入力を待ち、クリックなどの操作が発生すると、イベントを対応するウィジェットへ渡すと説明されています。

メインループは、次の作業を繰り返します。

  • イベントが届くのを待つ
  • 届いたイベントの種類を確認する
  • イベントに関係するウィジェットを確認する
  • 必要なシグナルを発生させる
  • 対応するコールバック関数を呼び出す
  • 画面の更新が必要か確認する
  • 次のイベントを待つ

GtkApplicationを利用すると、GTKの初期化やメインループの管理をまとめて行えます。具体的なGtkApplicationの使い方は、第4章で学習します。

図1:GUIアプリが操作を待ち続ける流れ

イラスト作成プロンプト

カラーイラスト、16:9横長、親しみやすいアニメ調。C言語初心者向けの教育イラスト。日本のソフトウェア会社のコードレビュー会議で、若い日本人男性C言語プログラマーと若い日本人女性C言語プログラマーが、1台の大型モニターを囲んでGUIアプリの動作を確認している。男性は短い黒髪、白いシャツ、紺色のカーディガン、女性は肩までの黒髪、細い黒縁眼鏡、白いブラウスを着用している。

大型モニターを正面から描き、中央に循環する5段階の流れを配置する。「ウィンドウを表示」「操作を待つ」「イベントが発生」「処理を実行」「画面を更新」という5つのカードを太い循環矢印でつなぐ。

「画面を更新」から「操作を待つ」へ戻る矢印を強調する。「操作を待つ」から分岐し、「閉じる操作」「アプリを終了」という2つのカードへ進む矢印も配置する。

左側にボタンをクリックするマウス、右側に文字を入力するキーボードを描き、どちらからも「イベントが発生」へ矢印を向ける。下部に「待機と処理を繰り返す」と表示する。

説明文はすべて日本語で正確に表示する。図の中には図全体のタイトルや見出しを入れない。人物や背景と文字を重ねない。モニターの裏には文字を表示しない。ノートパソコンのカバーには文字やプログラムを表示しない。
この図から分かること

GUIアプリは、ウィンドウを表示した後、ユーザーの操作を待ちます。操作が行われると対応する処理を実行し、必要に応じて画面を更新します。

処理が終わると、アプリケーションは終了せず、再び次の操作を待ちます。この流れが、イベント駆動型GUIアプリの基本です。

ウィンドウを閉じるなどの終了操作が行われたときに、メインループを終了し、アプリケーションも終了します。

イベントとは

イベントとは、プログラムが処理する対象となる出来事です。

GUIアプリで発生するイベントは、マウスのクリックやキーボード入力だけではありません。ユーザーの操作、ウィンドウの状態変化、アプリケーション内部の処理など、さまざまな出来事がイベントになります。

イベントの種類具体例
マウス操作クリック、ダブルクリック、移動、ドラッグ、スクロール
キーボード操作キーを押す、キーを離す、文字を入力する
ウィンドウ操作開く、閉じる、移動する、大きさを変更する
入力欄の操作文字列が変更される、Enterキーで確定する
ウィジェットの操作ボタンを押す、項目を選ぶ、チェック状態を変える
時間に関する処理一定時間が経過する、定期処理の時刻になる
ファイルや通信読み込みが完了する、データを受信する
アプリ内部の変化計算が終わる、ゲームの状態が変わる

ユーザーの目に見える操作だけでなく、一定時間が経過したことや、データの受信が完了したこともイベントとして扱えます。

イベントが処理されるまで

Windows上でマウスやキーボードを操作すると、その情報は最初にオペレーティングシステムが受け取ります。

GTK 4を使ったGUIアプリでは、大まかに次の流れでイベントが処理されます。

  1. ユーザーがマウスやキーボードを操作する
  2. Windowsが入力を受け取る
  3. GTK 4が操作に関する情報を受け取る
  4. GTK 4が操作対象のウィジェットを調べる
  5. ウィジェットが必要なシグナルを発生させる
  6. シグナルに接続されたコールバック関数が呼び出される
  7. コールバック関数が処理を行う
  8. 必要に応じて画面が更新される
  9. GTK 4が次のイベントを待つ

プログラマーがWindowsから届くすべての入力情報を直接処理する必要はありません。GTK 4が入力を整理し、ウィジェットやシグナルという扱いやすい形でプログラムへ伝えます。

イベントとシグナルの違い

GTK 4を学ぶときは、イベントとシグナルという言葉が登場します。この2つは関連していますが、同じ意味ではありません。

用語意味具体例
イベントユーザー操作やシステム上の出来事マウスボタンが押された
ウィジェットイベントを受け取る画面上の部品GtkButton
シグナルウィジェットで意味のある変化が起きたことを知らせる仕組みボタンがクリックされた
コールバック関数シグナルに応じて呼び出される関数計算処理を行う関数

たとえば、マウスボタンが押されて離されたという入力イベントをGtkButtonが受け取ると、ボタンがクリックされたことを表すシグナルが発生します。

プログラムは、そのシグナルにコールバック関数を接続しておくことで、クリックに対応する処理を実行できます。

GTK 4では、低い段階の入力処理にイベントコントローラーが使われます。GTK 4の公式資料でも、入力イベントの処理にはイベントコントローラーを利用する構成が示されています。GTK 4公式ドキュメント:Input and Event Handling

初めのうちは、次のように考えると分かりやすいでしょう。

  • イベントは、起きた出来事
  • シグナルは、ウィジェットから届く通知
  • コールバック関数は、通知に応じて実行する処理

ボタンのクリック

ボタンのクリックは、GUIアプリで特によく使われる操作です。

たとえば、GUI電卓では次のようなボタンを使用します。

  • 足し算ボタン
  • 引き算ボタン
  • 掛け算ボタン
  • 割り算ボタン
  • クリアボタン

それぞれのボタンには、クリックされたときに実行する処理を用意します。

クリックは押した瞬間だけではない

一般的なボタンのクリックは、マウスボタンを押して離す一連の操作として扱われます。

GTK 4のGtkButtonでは、ボタンが押されて離され、ボタンが有効化されたときにclickedシグナルが発生します。

ボタンを押したままポインターを外へ移動するなど、操作の仕方によってはクリックとして扱われない場合があります。このような細かな判定は、基本的にGTK 4のウィジェットが担当します。

ボタンごとに異なる処理を接続する

複数のボタンがある場合は、それぞれに異なる処理を接続できます。

ボタンクリックされたときの処理
足し算2つの数値を足す
引き算1つ目の数値から2つ目の数値を引く
掛け算2つの数値を掛ける
割り算1つ目の数値を2つ目の数値で割る
クリア入力欄と計算結果を空にする

また、複数のボタンを同じコールバック関数へ接続し、どのボタンから呼び出されたかによって処理を切り替える方法もあります。

ボタンはキーボードでも操作できる

ボタンはマウスだけで操作するとは限りません。フォーカスがボタンにある場合、EnterキーやSpaceキーでボタンを有効化できることがあります。

GTK 4の入力処理でも、ボタンなどのウィジェットは、マウスクリックだけでなくEnterキーやSpaceキーから有効化できると説明されています。

そのため、プログラムではマウスが押されたという低い段階の出来事だけを見るよりも、ボタンが有効化されたことを表すclickedシグナルへ処理を接続する方が扱いやすくなります。

図2:ボタンのクリックから処理が実行されるまで

この図から分かること

ユーザーがボタンをクリックすると、GTK 4が操作を受け取り、対象となるボタンを確認します。ボタンからclickedシグナルが発生すると、接続されているコールバック関数が呼び出されます。

コールバック関数が計算などの処理を行い、その結果を画面へ表示します。処理が終わると、GTK 4は次の操作を待つ状態へ戻ります。

プログラマーはマウス入力の細かな判定をすべて作るのではなく、ボタンのclickedシグナルに必要な処理を接続します。

キーボードからの入力

GUIアプリでは、文字や数値の入力、操作対象の切り替え、機能の実行などにキーボードを使用します。

キーボードに関係する代表的な操作は次のとおりです。

  • 入力欄に文字を入力する
  • Backspaceキーで文字を削除する
  • Enterキーで入力を確定する
  • Tabキーでフォーカスを移動する
  • 矢印キーで項目を選択する
  • Spaceキーでボタンを操作する
  • ショートカットキーで機能を実行する

フォーカスとは

キーボードから入力された内容をどのウィジェットが受け取るかを決める状態を、フォーカスと呼びます。

画面上に入力欄が複数あっても、通常はフォーカスを持つ入力欄がキーボード入力を受け取ります。

ユーザーは次のような操作でフォーカスを移動できます。

  • 入力欄をマウスでクリックする
  • Tabキーを押す
  • Shift+Tabキーを押す
  • プログラムから特定のウィジェットへフォーカスを移す

GTK 4では、Tabキーで次の操作対象へ、Shift+Tabキーで前の操作対象へフォーカスを移動できます。

フォーカスを適切に移動できる画面は、マウスを使わないユーザーにとっても操作しやすくなります。

入力欄へ文字が入る仕組み

ユーザーがキーを押すと、その入力情報がWindowsからGTK 4へ伝えられます。

GTK 4の入力欄は、キー入力や文字入力の仕組みを使い、入力された内容を画面へ反映します。

大まかな流れは次のとおりです。

  1. ユーザーが入力欄を選択する
  2. 入力欄がフォーカスを受け取る
  3. ユーザーがキーボードのキーを押す
  4. GTK 4がキーボード入力を受け取る
  5. フォーカスを持つ入力欄へ入力を伝える
  6. 入力欄の文字列が更新される
  7. 画面に新しい文字列が表示される

日本語を入力する場合は、1回のキー操作がそのまま1文字になるとは限りません。複数のキー入力を使って文字を変換し、確定した日本語が入力欄へ渡されます。

通常の文字入力では、押されたキーを1つずつ自分で文字へ変換する必要はありません。GTK 4の入力欄が、入力方式と連携して文字列を扱います。

Enterキーによる処理

入力欄へ文字を入力した後、Enterキーを押して処理を開始するGUIアプリがあります。

たとえば、次のような操作です。

  • 検索欄でEnterキーを押して検索する
  • 名前を入力してEnterキーで確定する
  • GUI電卓でEnterキーを押して計算する
  • メッセージ入力欄でEnterキーを押して送信する

GTK 4のGtkEntryでは、Enterキーによって入力欄が有効化されるとactivateシグナルが発生します。

計算ボタンのclickedシグナルと、入力欄のactivateシグナルを同じ計算処理へ接続すれば、マウスでもキーボードでも同じ機能を実行できる画面を作れます。

キーそのものを処理する場合

通常の入力欄では、GTK 4が文字入力を扱います。一方、ゲームや独自の操作画面では、特定のキーが押されたことを直接調べたい場合があります。

たとえば、次のような処理です。

  • 矢印キーでゲームの選択位置を移動する
  • Escキーで画面を閉じる
  • 特定のキーをショートカットとして使う
  • キーを押している間だけ処理を続ける

GTK 4では、キー入力を扱うためのGtkEventControllerKeyが用意されています。

ただし、文字入力欄へ日本語を入力するような場面では、低い段階のキーイベントだけで処理しようとせず、GtkEntryなどの入力用ウィジェットを利用することが大切です。

図3:キーボード入力とフォーカスの関係

この図から分かること

キーボードから入力された内容は、基本的にフォーカスを持つウィジェットへ渡されます。入力欄にフォーカスがあれば、入力した文字や数値がその入力欄へ表示されます。

TabキーやShift+Tabキーを使うと、フォーカスを前後のウィジェットへ移動できます。入力欄でEnterキーを押すとactivateシグナルが発生し、登録されているコールバック関数を呼び出せます。

コールバック関数とは

コールバック関数とは、イベントやシグナルが発生したときに、GTK 4から呼び出される関数です。

通常のC言語プログラムでは、ある関数から別の関数を直接呼び出す流れをよく使います。

イベント駆動型プログラミングでは、プログラム側が今すぐ関数を呼び出すのではなく、どのシグナルが発生したときに呼び出すのかをGTK 4へ登録します。

その後、実際にシグナルが発生すると、GTK 4が登録された関数を呼び出します。

関数を実行するのではなく登録する

コールバック関数を理解するときは、今すぐ実行することと、後で呼び出してもらうために登録することを分けて考えましょう。

操作意味
関数を直接呼び出すその場所ですぐに処理を実行する
コールバック関数を登録するイベントが発生したときにGTK 4から呼び出してもらう
シグナルが発生するウィジェットで意味のある出来事が起きたことを知らせる
GTK 4が関数を呼ぶ登録されていた処理を実行する

たとえば、計算用のコールバック関数を登録しても、登録した瞬間には計算されません。ユーザーが計算ボタンをクリックし、clickedシグナルが発生したときに計算処理が呼び出されます。

コールバック関数が担当する処理

コールバック関数には、ユーザー操作に対応する処理を書きます。

GUI電卓の計算ボタンでは、次のような処理が必要になります。

  • 入力欄から文字列を取得する
  • 入力欄が空でないか確認する
  • 文字列を数値へ変換する
  • 指定された計算を行う
  • 0で割ろうとしていないか確認する
  • 計算結果を文字列へ変換する
  • 結果表示ラベルを更新する

クリアボタンのコールバック関数では、次のような処理を行います。

  • 2つの入力欄を空にする
  • 計算結果を消去する
  • エラーメッセージを消去する
  • 最初の入力欄へフォーカスを戻す

○×ゲームのマスをクリックした場合は、次のような処理になります。

  • どのマスがクリックされたか調べる
  • そのマスが空いているか確認する
  • プレイヤーの記号を表示する
  • ゲームの状態を更新する
  • 勝敗を判定する
  • ゲームが続く場合はコンピューターの手を決める

このように、コールバック関数の内容は、接続するウィジェットとシグナルによって変わります。

コールバック関数へ渡される情報

コールバック関数には、処理に必要な情報が渡されます。

代表的な情報には、次のようなものがあります。

  • シグナルを発生させたウィジェット
  • クリックされたボタン
  • 押されたキー
  • マウスポインターの位置
  • 入力欄に入力されている文字列
  • プログラマーが登録した追加データ

たとえば、9個のボタンで構成する○×ゲームでは、どのボタンがクリックされたかを調べる必要があります。

また、GUI電卓では、計算ボタンの処理から2つの入力欄と結果表示ラベルを利用する必要があります。このような関連するウィジェットは、構造体などにまとめてコールバック関数へ渡せます。

具体的なデータの渡し方は、第5章の「複数のウィジェットをまとめて管理する」で学習します。

コールバック関数が終わると待機へ戻る

コールバック関数は、必要な処理が終わったらGTK 4へ制御を返します。

その後、GTK 4は画面の更新を行い、次のイベントを待ちます。

  1. GTK 4がイベントを受け取る
  2. コールバック関数を呼び出す
  3. コールバック関数が処理を行う
  4. コールバック関数が終了する
  5. GTK 4へ制御が戻る
  6. 画面が更新される
  7. 次のイベントを待つ

このため、コールバック関数の中で長時間処理を続けると、その間はGTK 4が次のイベントを処理できなくなります。

長い処理で画面が応答しなくなる理由

イベント駆動型GUIアプリでは、コールバック関数を適切な時間で終了させることが大切です。

たとえば、ボタンをクリックした後、コールバック関数の中で非常に長い計算を続けると、次のような問題が起こる可能性があります。

  • ボタンを押しても反応しないように見える
  • ウィンドウを移動しにくくなる
  • 画面が更新されない
  • 別のボタンをクリックしても処理されない
  • Windowsから応答していないと判断される

これは、メインループがコールバック関数の終了を待っており、次のイベントを処理できないためです。

長い処理が必要な場合は、処理を小さく分けたり、非同期処理や別のスレッドを検討したりします。ただし、スレッドからGUIを直接操作するときには注意が必要です。初めのうちは、コールバック関数を短く分かりやすく保つことを意識しましょう。

コールバック関数を分かりやすくする

コールバック関数にすべての処理を書き込むと、関数が長くなり、動作を確認しにくくなります。

処理を役割ごとに分けると理解しやすくなります。

たとえば、GUI電卓では次のように分けられます。

  • 入力された文字列を確認する処理
  • 文字列を数値へ変換する処理
  • 四則演算を行う処理
  • 結果を表示する処理
  • エラーを表示する処理
  • 画面を初期状態へ戻す処理

コールバック関数は、必要な処理を順番に呼び出し、ユーザー操作とアプリ内部の処理をつなぐ役割を持たせると分かりやすくなります。

コンソールアプリとの違い

イベント駆動型プログラミングを理解するには、コンソールアプリの処理と比較すると分かりやすくなります。

比較項目コンソールアプリGUIアプリ
主な処理の開始プログラムの起動後イベントの発生後
操作の順序プログラム側が決めることが多いユーザーが選ぶ
入力の待ち方指定された場所で入力を待つメインループでさまざまな操作を待つ
結果の表示コンソールへ順番に表示するラベルや入力欄などを更新する
処理後の動作次の処理へ進むか終了する次のイベントを待つ
関数の呼び出しプログラムから直接呼ぶことが多いGTK 4がコールバック関数を呼ぶ
終了最後の処理まで進む終了イベントが発生するまで動作する

ただし、GUIアプリでも、1つのコールバック関数の中では処理が上から順番に実行されます。

イベント駆動型だからといって、C言語の処理順序がなくなるわけではありません。どの関数をいつ実行するのかが、ユーザーの操作によって決まる点が大きな違いです。

複数のイベントとアプリの状態

GUIアプリでは、さまざまなイベントが何度も発生します。

GUI電卓の場合は、次のような状態を管理します。

  • 1つ目の入力欄の内容
  • 2つ目の入力欄の内容
  • 選択された演算
  • 計算結果
  • エラーの有無
  • 現在フォーカスを持つウィジェット

○×ゲームでは、次のような状態を管理します。

  • 9個のマスの内容
  • 現在の手番
  • ゲームが続いているか
  • 勝者が決まっているか
  • 引き分けになったか

イベントが発生するたびに、コールバック関数は現在の状態を確認し、必要な処理を行い、新しい状態へ更新します。

同じボタンをクリックしても、現在の状態によって処理結果が変わることがあります。

たとえば、○×ゲームの同じマスをクリックした場合でも、そのマスが空いていれば記号を置けますが、すでに使用されていれば何も変更しません。

イベント駆動型プログラミングの基本的な考え方

イベント駆動型GUIアプリを設計するときは、次の順番で考えると分かりやすくなります。

  1. 画面にどのようなウィジェットを配置するか考える
  2. ユーザーがどのような操作を行うか考える
  3. 操作によって発生するシグナルを確認する
  4. シグナルごとに必要な処理を決める
  5. 処理を行うコールバック関数を用意する
  6. 必要なデータや画面の状態を管理する
  7. コールバック関数の処理結果を画面へ反映する
  8. 処理後は次の操作を待つ

最初からプログラム全体を一続きの処理として考えるのではなく、操作と処理の組み合わせに分けることが大切です。

図4:イベント・シグナル・コールバック関数の関係

この図から分かること

イベント駆動型プログラミングでは、ユーザー操作、イベントやシグナル、コールバック関数、画面更新を結び付けて考えます。

ボタンのクリックやEnterキーなどの操作が行われると、GTK 4からシグナルが発生します。対応するコールバック関数が入力確認や計算を行い、アプリの状態と画面表示を更新します。

処理が終わると、GUIアプリは次の操作を待ちます。この一連の関係を理解することが、GTK 4によるGUIプログラミングの大切な第一歩です。