コンテンツにスキップ

Top

GTK+3入門(C言語)

LinuxでC言語やC++をつかってアプリケーションを開発しているとき悩むのが、GUIをどのツールキットで実現するか?

Qt(qml)が使われることが多いと思うが、ちょっと大仰な気がしている。

あとライセンス問題もあるし。

他の選択肢としてwxWidgetsかGTK+があるが、wxWidgetsはなんかよくわからんからパス。

ので、GTK+の使い方について述べる。

なお最近ではGTK+4が出たらしいが、3ベースで。

あと、gladeというツールも使わない。

なおここのページはC言語用で、C++言語用のgtkmmの使い方は以下。
GTK+3入門 (C++版)

GTK+のインストール

UbuntuでGTKをインストールするのは容易。

sudo apt install -y libgtk-3-dev

こんだけ!

ウィンドウの表示

GUI版のHello Worldというか、ウィンドウの表示。

ソース(sample1.c)

#include <gtk/gtk.h>

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 640, 480);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // Windowの表示
    gtk_widget_show_all(window);

    // メインループ
    gtk_main(); 

    return 0;
}

んでコンパイルして実行。pkg-config便利。

$ gcc sample1.c `pkg-config --cflags --libs gtk+-3.0` -o sample1
$ ./sample1

タイトル

GUI版Hello World終了。

ちなみに右上の×印をクリックしてもGUIは消えてもコンソールはなんか戻ってこないね、という感じになると思うが、それはgtk_mainが終了していないから。
終了させたい場合は、
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
の行を追加しておけば終了するが、この呪文みたいなのいきなり出してもあれだからとりあえずは書かない。

フルスクリーンにしてみる

フルスクリーンにしてみましょう。ただ、フルスクリーンにするとタイトルバーが消え、結果として×ボタンとかも消えるので終了させることができません。

よって、以下のサンプルは実行後、Alt+Tabでコンソールを表示してCtrl+Cで殺しましょう。

#include <gtk/gtk.h>

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // フルスクリーンにする
    gtk_window_fullscreen((GtkWindow*)window);

    // Windowの表示
    gtk_widget_show_all(window);

    // メインループ
    gtk_main(); 

    return 0;
}

ラベルの表示

とりあえずなんか文字を出してみましょう。
最初のソースに以下を追加します。

// ラベルを作りWindow上に配置
GtkWidget* label;
label = gtk_label_new("Hello World! こんにちは世界!");
gtk_container_add(GTK_CONTAINER(window), label);

全体のソースは以下。

#include <gtk/gtk.h>

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 320, 240);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // ラベルを作りWindow上に配置
    GtkWidget* label;
    label = gtk_label_new("Hello World! こんにちは世界!");
    gtk_container_add(GTK_CONTAINER(window), label);

    // Windowの表示(正確にはallをつけているのでwindow上のすべてのwidgetが表示される)
    gtk_widget_show_all(window);

    // メインループ
    gtk_main(); 

    return 0;
} 

コンパイルの仕方はさっきと同じですね。
実行結果は以下のようになります。

タイトル

ボタンの表示

今度はボタンを表示してクリックしたら終了するようにしましょう。

なお、ラベルとボタンは「今は」同時に表示できないのでラベル部分を消してボタン部分を追加してください。

追加部分は以下

void clicked_button(GtkWidget* widget, gpointer data)
{
    gtk_main_quit(); // プログラムが終了する
}
// ボタンを作成し、Window上に配置
GtkWidget* button;
button = gtk_button_new_with_label("Don't Push Me!");
gtk_container_add(GTK_CONTAINER(window), button);

// 第二引数はシグナル名(勝手な名前をつけてはいけない)。第四引数はコールバック関数に渡すデータ
g_signal_connect(button, "clicked", G_CALLBACK(clicked_button), NULL);

全部のソースは以下。

#include <gtk/gtk.h>

// ボタンが押されたときに呼び出されるコールバック関数。
void clicked_button(GtkWidget* widget, gpointer data)
{
    gtk_main_quit(); // プログラムが終了する
}

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 320, 240);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // ボタンを作成し、Window上に配置
    GtkWidget* button;
    button = gtk_button_new_with_label("Don't Push Me!");
    gtk_container_add(GTK_CONTAINER(window), button);

    // 第二引数はシグナル名(勝手な名前をつけてはいけない)。第四引数はコールバック関数に渡すデータ
    g_signal_connect(button, "clicked", G_CALLBACK(clicked_button), NULL);

    // Windowの表示(正確にはallをつけているのでwindow上のすべてのwidgetが表示される)
    gtk_widget_show_all(window);

    // メインループ
    gtk_main(); 

    return 0;
} 

これを実行するとボタンなんか表示されてなくてラベルが表示されたかのように見える。
理由はWindowいっぱいにボタンが広がって配置されるから。
このあたりの調整はあとで。

タイトル

ボタンが効かない!

なんかボタン押してもうまく行かない。
ターミナルには、

(sample2:29818): GLib-GObject-WARNING **: 13:27:05.327: ../../../../gobject/gsignal.c:2524: signal 'pushed' is invalid for instance '0x563a19a54180' of type 'GtkButton'
みたいな変なエラーがでてる。

といったようなことが起きた場合、十中八九 g_signal_connect の第二引数が間違っています。

ここは "clicked" という文言でなければだめです。Clickedでもclickでも駄目。

正しく "clicked" と記述しているか確認しましょう。

ちなみにclicked以外にはpressedとreleasedがあり、clickedはreleasedと同じ挙動です。
もし押したとき(押した状態から離してなくても)に実行したい、という特殊なケースがあるのであれば"pressed"を使いましょう。
clickedだと離すまでは実行されないので。

画像の表示

画像ファイルを読みこんで表示するサンプル。
以下を追加する。

// 画像を読み込み、Window上に配置
GtkWidget* image;
image = gtk_image_new_from_file("./girl.bmp");
gtk_container_add(GTK_CONTAINER(window), image);

すべてのソース。

#include <gtk/gtk.h>

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 320, 240);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "ニセベッキー");

    // 画像を読み込み、Window上に配置
    GtkWidget* image;
    image = gtk_image_new_from_file("./girl.bmp");
    gtk_container_add(GTK_CONTAINER(window), image);

    // Windowの表示(正確にはallをつけているのでwindow上のすべてのwidgetが表示される)
    gtk_widget_show_all(window);

    // メインループ
    gtk_main(); 

    return 0;
} 

実行結果

タイトル

ここまでやって、ラベルもボタンも画像もいっぺんに表示させたい!と思ったことでしょう。

どうしたらよいかを説明します。

ラベルと画像とボタンを表示

いままで同時に1個しか表示してきませんでした。

理由はWidgetにaddできるWidgetは1個だからです。

2つ追加しても無視されます。

ではどうすればいいのでしょうか?

複数のWidgetをいったんbox(パッキングボックス)というのに追加し、そのboxをWindowにaddする、という作戦を取ります。

これによりラベルと画像とボタンの2つをいっぺんにWindowに表示できるようになりました!

また、boxは入れ子も可能なのでbox1とbox2の両方をbox3に入れて、なんてこともできます。

おソース。

#include <gtk/gtk.h>

void clicked_button(GtkWidget* widget, gpointer data)
{
    gtk_main_quit();
}

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 320, 240);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // ラベルの作成
    GtkWidget* label;
    label = gtk_label_new("Hello World! こんにちは世界!");

    // イメージの作成
    GtkWidget* image;
    image = gtk_image_new_from_file("./girl.bmp");

    // ボタンを作成
    GtkWidget* button;
    button = gtk_button_new_with_label("Don't Push Me!");
    g_signal_connect(button, "clicked", G_CALLBACK(clicked_button), NULL);

    // パッキングボックスを作成
    GtkWidget* box;
    // 第一引数で縦積みか横積みかを決めれる。第二引数はそれぞれのWidgetの隙間のピクセル数
    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); // 縦に積まれていく。横に並べたいならGTK_ORIENTATION_HORIZONTAL

    // ラベルとイメージとボタンをボックスに追加
    gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box), image, TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); 

    // ボックスをWindowに追加
    gtk_container_add(GTK_CONTAINER(window), box);

    // Windowの表示(正確にはallをつけているのでwindow上のすべてのwidgetが表示される)
    gtk_widget_show_all(window);

    // メインループ
    gtk_main(); 

    return 0;
} 

実行結果。

タイトル

いい感じで3つ並びました。
でもpackはただ積むだけなので細かな調整は難しいです。
表のように並べる「テーブル」や座標指定の「Fixed」などがあるので細かな調整をする場合はそれらを用いたほうが良いでしょう。

Widgetの座標指定配置

座標をしていしてラベルやボタンを配置する方法。 いままでpackを使っていたのをfixedを使うようにするだけ。
しかもpackと違ってboxとかに入れる必要はない(入れても良い)。

ソースコード。

#include <gtk/gtk.h>

int main (int argc, char** argv){

    gtk_init(&argc, &argv);

    GtkWidget* window;

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_size_request(window, 320, 240);
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // サイズのテストなので関数のシグナル部分は割愛
    GtkWidget* button1 = gtk_button_new_with_label("(25,25)");
    GtkWidget* button2 = gtk_button_new_with_label("(100,150)");
    GtkWidget* button3 = gtk_button_new_with_label("(150,200)");

    GtkWidget* fixed;
    fixed = gtk_fixed_new();

    gtk_fixed_put(GTK_FIXED(fixed), button1,  25,  25);
    gtk_fixed_put(GTK_FIXED(fixed), button2, 100, 150);
    gtk_fixed_put(GTK_FIXED(fixed), button3, 150, 200);

    gtk_container_add(GTK_CONTAINER(window), fixed);

    gtk_widget_show_all(window);
    gtk_main();
    return 0;
}

実行結果
タイトル

指定した座標にボタンが配置されていますね。

Widgetの大きさ指定

当然ですがWidgetの幅高さを指定することができます。
実は最初っから使っています。 gtk_widget_set_size_request です。ウィンドウのサイズをこれで指定していました。
これをラベルやボタンにも当てはめれば良いだけです。

が、これはpackではうまく行きません。

packの場合、裏技的に、VERTICALなboxに詰め込んだ後、HORIZONTALなboxに詰め込めば縦横が指定できるのですが、正直アホかという感じです。
(packを一切使うな、というわけではないです。packを使っているboxとfixedを使っているboxとを混ぜても良いわけですら適材適所で)

ただまぁ、packはほんとちょっとしたときに、という感じで基本fixedでフルスクラッチするほうが仕事としては多いと思いますが。
(Androidとかのアプリは様々な画面サイズでいい感じにしないといけないからそうでもないでしょうけど)

以下がfixedを使ってボタンの幅高さを指定したソースコード。

#include <gtk/gtk.h>

int main (int argc, char** argv){

    gtk_init(&argc, &argv);

    GtkWidget* window;

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_size_request(window, 520, 520);
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // サイズのテストなので関数のシグナル部分は割愛
    GtkWidget* button1 = gtk_button_new_with_label("160 x 120");
    gtk_widget_set_size_request(button1, 160, 120);

    GtkWidget* button2 = gtk_button_new_with_label("120 x 120");
    gtk_widget_set_size_request(button2, 120, 120);

    GtkWidget* button3 = gtk_button_new_with_label("100 x 100");
    gtk_widget_set_size_request(button3, 100, 100);

    GtkWidget* fixed;
    fixed = gtk_fixed_new();

    gtk_fixed_put(GTK_FIXED(fixed), button1,  25,  25);
    gtk_fixed_put(GTK_FIXED(fixed), button2, 100, 200);
    gtk_fixed_put(GTK_FIXED(fixed), button3, 300, 400);

    gtk_container_add(GTK_CONTAINER(window), fixed);

    gtk_widget_show_all(window);
    gtk_main();
    return 0;
}

実行結果。

タイトル

ボタンが指定した大きさになってますね。

Widgetの表示/非表示

Window上のWidgetを表示したり非表示したりします。

まずはpackでやってみましょう。ボタンを押したら消えたり表示されたり、です。

#include <gtk/gtk.h>
#include <stdbool.h>

bool show_flag = true;

void clicked_button(GtkWidget* widget, gpointer data)
{
    // gpointerはvoid*
    if (show_flag == true) {
        gtk_widget_hide(GTK_WIDGET(data));
        show_flag = false;
    } else {
        gtk_widget_show(GTK_WIDGET(data));
        show_flag = true;
    }   
}

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 320, 240);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // イメージの作成
    GtkWidget* image;
    image = gtk_image_new_from_file("./girl.bmp");

    // ボタンを作成
    GtkWidget* button;
    button = gtk_button_new_with_label("show/hide");
    g_signal_connect(button, "clicked", G_CALLBACK(clicked_button), image);

    // パッキングボックスを作成
    GtkWidget* box;
    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); 

    // ラベルとイメージとボタンをボックスに追加
    gtk_box_pack_start(GTK_BOX(box), image, TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); 

    // ボックスをWindowに追加
    gtk_container_add(GTK_CONTAINER(window), box);

    // Windowの表示(正確にはallをつけているのでwindow上のすべてのwidgetが表示される)
    gtk_widget_show_all(window);

    // メインループ
    gtk_main();

    return 0;
}

タイトル タイトル

packだと絵を非表示にしたタイミングでボタンが拡大してしまってしまいますね。

次にfixedの場合です。

#include <gtk/gtk.h>
#include <stdbool.h>

bool show_flag = true;

void clicked_button(GtkWidget* widget, gpointer data)
{
    // gpointerはvoid*
    if (show_flag == true) {
        gtk_widget_hide(GTK_WIDGET(data));
        show_flag = false;
    } else {
        gtk_widget_show(GTK_WIDGET(data));
        show_flag = true;
    }   
}

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 256, 276);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // イメージの作成
    GtkWidget* image;
    image = gtk_image_new_from_file("./girl.bmp");
    gtk_widget_set_size_request(image, 256, 256);

    // ボタンを作成
    GtkWidget* button;
    button = gtk_button_new_with_label("show/hide");
    gtk_widget_set_size_request(button, 256, 20);
    g_signal_connect(button, "clicked", G_CALLBACK(clicked_button), image);

    // Fixedを作成
    GtkWidget* fixed;
    fixed = gtk_fixed_new();

    // ラベルとイメージとボタンをボックスに追加
    gtk_fixed_put(GTK_FIXED(fixed), image,    0,    0); 
    gtk_fixed_put(GTK_FIXED(fixed), button,   0,  256);

    // ボックスをWindowに追加
    gtk_container_add(GTK_CONTAINER(window), fixed);

    // Windowの表示(正確にはallをつけているのでwindow上のすべてのwidgetが表示される)
    gtk_widget_show_all(window);

    // メインループ
    gtk_main();

    return 0;
}

packと違い、fixedの場合は当然ですが位置が固定なので、

タイトル タイトル

となり、間が詰まったりしません。

スライダー(GtkScale)

スライダーと値の取得のサンプル。スライダーとは言わずにスケールという。

#include <gtk/gtk.h>

void value_changed(GtkWidget* widget, gpointer data)
{
    printf("%f\n", gtk_range_get_value(GTK_RANGE(widget))); // gtk_range_get_valueの返却値は%fなので注意
}

int main (int argc, char* argv[])
{
    GtkWidget* window;

    // 初期化
    gtk_init(&argc, &argv);

    // Windowの作成
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // アプリの終了時にgtk_mainが死ぬようにする
    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK (gtk_main_quit), NULL);

    // Windowsの大きさを指定
    gtk_widget_set_size_request(window, 320, 100);

    // Windowタイトルの設定
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // スケールを作成
    GtkWidget* scale;
    scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 255, 1); // 縦横、最小値、最大値、ステップ数
    // gtk_scale_set_digits(scale, GTK_SCALE(scale), 2); // 小数点以下とかの指定

    gtk_scale_set_draw_value(GTK_SCALE(scale), TRUE); // これがtrueのときは値が表示される。ないときは非表示。
    gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_BOTTOM); // 値が表示される位置。GTK_POS_LEFT、GTK_POS_RIGHT、GTK_POS_TOP、GTK_POS_BOTTOM
    gtk_range_set_value(GTK_RANGE(scale), 128.0); // GTK_RANGEなので注意

    // 変更時にコールバックを呼ぶシグナル設定
    g_signal_connect(GTK_SCALE(scale), "value-changed", G_CALLBACK(value_changed), NULL); 

    // ボックスをWindowに追加
    gtk_container_add(GTK_CONTAINER(window), scale);

    // Windowの表示(正確にはallをつけているのでwindow上のすべてのwidgetが表示される)
    gtk_widget_show_all(window);

    // メインループ
    gtk_main(); 

    return 0;
} 

実行結果
タイトル

チェックボタン、ラジオボタン

チェックボタンは普通のボタンと大差ないが、ラジオボタンの作り方が結構独特。

ソースコード。

#include <gtk/gtk.h>
#include <stdbool.h>

void cb_check_button(GtkToggleButton* widget, gpointer data)
{
    bool status = gtk_toggle_button_get_active(widget);
    printf("%d\n", status);
}

void cb_radio_button(GtkRadioButton* widget, gpointer data)
{
    // 他のラジオボタンがチェックされたということは元のラジオボタンのチェックは外れるということ
    // 言い換えるとチェックされたボタンとチェックが外れたボタンの2つデータが送られてくる
    // なのでactiveなのだけを拾う必要がある
    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
        printf("radio button%d.\n", GPOINTER_TO_INT(data));
    }   
}

int main (int argc, char* argv[])
{
    GtkWidget* window;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    gtk_widget_set_size_request(window, 320, 100);
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");

    // チェックボックスを作成
    GtkWidget* check_button;
    check_button = gtk_check_button_new_with_label("チェックボタン");
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button), TRUE);
    g_signal_connect(G_OBJECT(check_button), "toggled", G_CALLBACK(cb_check_button), NULL);

    // ラジオボタンを作成
    GtkWidget* radio_button[3];
    GSList* group = NULL;
    radio_button[0] = gtk_radio_button_new_with_label(group, "first");
    radio_button[1] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio_button[0]), "second");
    radio_button[2] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio_button[0]), "third");

    g_signal_connect(G_OBJECT(radio_button[0]), "toggled", G_CALLBACK(cb_radio_button), GINT_TO_POINTER(0));
    g_signal_connect(G_OBJECT(radio_button[1]), "toggled", G_CALLBACK(cb_radio_button), GINT_TO_POINTER(1));
    g_signal_connect(G_OBJECT(radio_button[2]), "toggled", G_CALLBACK(cb_radio_button), GINT_TO_POINTER(2));

    // ボックスを作成してチェックボックスとラジオボタンを格納しWindowに追加
    GtkWidget* box;
    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); 
    gtk_box_pack_start(GTK_BOX(box), check_button, TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box), radio_button[0], TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box), radio_button[1], TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box), radio_button[2], TRUE, TRUE, 0); 
    gtk_container_add(GTK_CONTAINER(window), box);

    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}

実行結果
タイトル

ダイアログの表示

ダイアログ。面倒くさい。

#include <gtk/gtk.h>

GtkWidget* window;

void show_dialog(GtkButton* button, gpointer data)
{
    GtkWidget* dialog;

    GtkButtonsType btype[] = {GTK_BUTTONS_CLOSE, GTK_BUTTONS_OK_CANCEL, GTK_BUTTONS_YES_NO, GTK_BUTTONS_OK};

    GtkMessageType mtype = (GtkMessageType)data;

    dialog = gtk_message_dialog_new(GTK_WINDOW(window), 
                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                                    mtype,
                                    btype[mtype],
                                    "ダイアログサンプル");

    int result = gtk_dialog_run(GTK_DIALOG(dialog));

    switch (result)
    {   
    case GTK_RESPONSE_OK:
        printf("ok\n");
        break;
    case GTK_RESPONSE_CANCEL:
        printf("cancel\n");
        break;
    case GTK_RESPONSE_CLOSE:
        printf("close\n");
        break;
    case GTK_RESPONSE_YES:
        printf("yes\n");
        break;
    case GTK_RESPONSE_NO:
        printf("no\n");
        break;
    default:
        printf("???\n");
        break;
    }   

    gtk_widget_destroy(dialog);
}

int main(int argc, char** argv)
{
    GtkWidget* box;
    GtkWidget* button;
    char* label[] = {"information", "warning", "question", "error"};

    gtk_init(&argc, &argv);

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size(GTK_WINDOW(window), 320, 320);
    gtk_window_set_title(GTK_WINDOW(window), "サンプル");
    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK (gtk_main_quit), NULL);

    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);

    for (int n=0; n<4; n++) {
        button = gtk_button_new_with_label(label[n]);
        g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(show_dialog), GINT_TO_POINTER(n));
        gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
    }

    gtk_container_add(GTK_CONTAINER(window), box);
    gtk_widget_show_all(window);
    gtk_main();
    return 0;
}

実行結果
タイトル  タイトル

ボタンを押すとそれぞれのダイアログが出ますね!

文字のフォントの種類やサイズを変えたい

cairoを使うかcssファイルを使うかなどあるみたいだけどどっちにしろかなり面倒くさそう。
もっとかんたんに変更できればいいのに、特にフォントサイズぐらいは。

まずはtest.cssを作ります。
ちなみにGtk2.0だとGtkLabel { としていたけど、Gtk3以降はlabelじゃないといけないので注意。

label {
    color: green;
    font-size: 30px;
    font-family:'メイリオ', 'Meiryo', sans-serif;
}

んで、ソース。

#include <gtk/gtk.h>

void apply_css(char* css_file)
{
    GtkCssProvider* provider = gtk_css_provider_new();
    gtk_css_provider_load_from_path(provider, css_file, NULL);
    gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
}

int main(int argc, char* argv[])
{
    // 初期化
    gtk_init(&argc, &argv);

    // CSSの適用
    apply_css("test.css"); // gtk_initより先に呼んではだめ

    // ウィンドウ
    GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_size_request(window, 320, 240);
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // ラベル
    GtkWidget* label;
    label = gtk_label_new("Hello World! こんにちは世界!");

    // ボタン(コールバックは割愛)
    GtkWidget* button;
    button = gtk_button_new_with_label("Don't Push Me!");
    //g_signal_connect(button, "clicked", G_CALLBACK(clicked_button), NULL);

    // ボックス
    GtkWidget* box;
    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); 
    gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); 
    gtk_container_add(GTK_CONTAINER(window), box);

    // 表示
    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}

実行結果
タイトル

なんか、label指定するとGtkLabelもGtkButtonも一緒に変わるので良くないね。Buttonだけ変えたい、とかどうしたら良いのか。
まぁいいや。

GUI上のクリック(タッチ)された点の座標を取得する

画面上をマウスでクリックしたときの座標を取得します。

気をつけないといけないのはクリックされたWidget上の座標であって、モニターの絶対座標ではないです。

#include <gtk/gtk.h>

gboolean button_press_event_callback(GtkWidget* widget, GdkEventButton* event, gpointer data)
{
    printf("(%3d,%3d)\n", (int)event->x, (int)event->y);
}

int main(int argc, char* argv[])
{
    gtk_init(&argc, &argv);

    GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");
    gtk_widget_set_size_request(window, 320, 240);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
    g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(button_press_event_callback), NULL);

    gtk_widget_show_all(window);
    gtk_main();
    return 0;
}

実行結果
タイトル

クリックした座標がコンソールに出ます。

$ ./gtk_mouse_click_sample 
( 85, 89)
(142,145)
( 99,205)
( 51,220)
(206,183)
(206,183)
(206,183)
(250,114)
(233, 62)

今回はWindow全面でやりましたが、例えばあるGtkImage上でのみ拾いたい、とかある場合は、GtkImageにはイベントを拾う機能がないため、event_boxという箱に入れてあげる必要がります。
以下のような感じで。

    GtkWidget* image = gtk_image_new_from_file("./girl.bmp");
    GtkWidget* box = gtk_event_box_new();
    gtk_container_add(GTK_CONTAINER(box), image);
    g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(button_press_event_callback), NULL);

GTK+の画面遷移

GUIといえば画面遷移ですわな。

HTMLとかのWeb系ならリンク貼って別ページに、って感じ。

じゃあGTKでは?

いろいろ方法はあるのかもしれないですが、代表的なのは表示/非表示を利用して画面遷移したかのようにみせる、です。

要は画面Aと画面Bを作ってそれぞれの画面を表示/非表示することでまるで遷移したかのように見せかけるのです。

ソース。

#include <gtk/gtk.h>

typedef struct _box_container {
    GtkWidget* box_a;
    GtkWidget* box_b;
} _box_container;

void clicked_button_a(GtkWidget* widget, gpointer data)
{
    _box_container* tmp = (_box_container*)data;
    gtk_widget_hide(GTK_WIDGET(tmp->box_a));
    gtk_widget_show(GTK_WIDGET(tmp->box_b));
}

void clicked_button_b(GtkWidget* widget, gpointer data)
{
    _box_container* tmp = (_box_container*)data;
    gtk_widget_hide(GTK_WIDGET(tmp->box_b));
    gtk_widget_show(GTK_WIDGET(tmp->box_a));
}

int main (int argc, char* argv[])
{
    gtk_init(&argc, &argv);

    _box_container box_container;

    // ウィンドウ作成
    GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_size_request(window, 256, 276);
    gtk_window_set_title(GTK_WINDOW(window), "タイトル");
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 画面A作成
    GtkWidget* label_a  = gtk_label_new("画面A");
    GtkWidget* image_a  = gtk_image_new_from_file("./Lenna.bmp");
    GtkWidget* button_a = gtk_button_new_with_label("画面Bへ");
    GtkWidget* box_a    = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); 
    gtk_box_pack_start(GTK_BOX(box_a), label_a,  TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box_a), image_a,  TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box_a), button_a, TRUE, TRUE, 0); 

    // 画面B作成
    GtkWidget* label_b  = gtk_label_new("画面B!");
    GtkWidget* image_b  = gtk_image_new_from_file("./girl.bmp");
    GtkWidget* button_b = gtk_button_new_with_label("戻る");
    GtkWidget* box_b    = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); 
    gtk_box_pack_start(GTK_BOX(box_b), label_b,  TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box_b), image_b,  TRUE, TRUE, 0); 
    gtk_box_pack_start(GTK_BOX(box_b), button_b, TRUE, TRUE, 0); 

    // AとBを入れる箱Cを作る(windowに追加できるwidgetは1個なので
    // このとき、AとBは同じ座標(0,0)に入れること
    GtkWidget* box_c = gtk_fixed_new();
    gtk_fixed_put(GTK_FIXED(box_c), box_a, 0, 0);
    gtk_fixed_put(GTK_FIXED(box_c), box_b, 0, 0);

    // Windowにbox_cを追加
    gtk_container_add(GTK_CONTAINER(window), box_c);

    // ボタン押されたときの処理
    box_container.box_a = box_a;
    box_container.box_b = box_b;
    g_signal_connect(button_a, "clicked", G_CALLBACK(clicked_button_a), &box_container);
    g_signal_connect(button_b, "clicked", G_CALLBACK(clicked_button_b), &box_container);

    // 表示
    gtk_widget_show_all(window);
    // 初期値としてB画面は消しておく
    gtk_widget_hide(GTK_WIDGET(box_b));

    // メインループ
    gtk_main();

    return 0;
}

実行結果
タイトル  タイトル

ボタンを押したらA,Bが切り替わりましたね。
もちろん本当に画面遷移しているわけではなく、2つの画面が表示非表示しているだけですが。

"destroy"シグナル

そのWidgetが存在しなくなったときに呼び出されるシグナルです。

一番メインのWindowに対して"destroy"シグナルを設定しておけば、アプリ終了時に必ずコールバック関数が呼び出されるので終了処理がしやすいです。

特に終了処理なんてない!という場合でも、windowに対して以下の行を追加しておけば、×印とかで終了したときにgtk_mainが終了する。 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK (gtk_main_quit), NULL);

番外編。cv::Matの値をGTK+で表示する

OpenCVでVideoCaptureして取得したcv::Mat形式の画像をGtkWidgetで表示できる形式に変換して表示する方法。
・・・と思ったが、OpenCVはC言語じゃなくC++なので、C++編の方で解説。
GTK+3入門 (C++版)

以上!

GTK2のサンプルコードを持ってきて失敗する例

GtkWidget has no member named ‘window’

gtk+2のサンプルコードをそのままつかってgtk+3でコンパイルするとよく出てくるエラー。

error: ‘GtkWidget {aka struct _GtkWidget}’ has no member named ‘window’

widget->window

のように直接アクセスできるwindow変数がGTK3ではなくなったためエラーになる。

ではウィジェトからウィンドウを取りたい場合はどうすればよいか?

GdkWindow* window = gtk_widget_get_window(widget);

のように、 gtk_widget_get_window という関数を使えば取得できる。

以上!