22. Gtk4 ตอน 2, GtkLabel, GtkButton and Gtkbox

Yothin Inbanleng
5 min readDec 10, 2023

--

บทความที่ผ่านมาเราได้เริ่มต้นเขียนโปรแกรมด้วย Gtk4 ไปกันแล้ว หลักการเบื้องต้นก็สร้าง GtkApplication ในฟังค์ชัน main() แล้วเชื่อมต่อ signal ชื่อ activate ไปยังฟังค์ชันชื่อ app_activate เพื่อสร้างวินโดว์สำหรับเริ่มต้น ทุกอย่างจะเริ่มต้นที่ฟังค์ชัน app_activate (หรือชื่ออื่น) ที่เราเชื่อมโยงกับ signal ชื่อ active

ตอนที่ 2 นี้ก็จะเป็นการเพิ่ม GtkLabel, GtkButton และ GtkBox ให้กับวินโดว์ที่สร้างขึ้น รายละเอียดก็เริ่มกันดังนี้

 1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app, gpointer user_data) {
5 GtkWidget *win;
6 GtkWidget *lbl; //add
7
8 win = gtk_application_window_new (GTK_APPLICATION (app));
9 gtk_window_set_title (GTK_WINDOW (win), "GtkLabel");
10 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
11
12 lab = gtk_label_new ("CFromZero"); //add
13 gtk_window_set_child (GTK_WINDOW (win), lbl); //add
14
15 gtk_widget_show (win);
16 }
17
18 int
19 main (int argc, char **argv) {
20 GtkApplication *app;
21 int stat;
22
23 app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
24 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
25 stat =g_application_run (G_APPLICATION (app), argc, argv);
26 g_object_unref (app);
27 return stat;
28 }

จากตัวอย่างโปรแกรมข้างบนมีจุดเพิ่มจากตัวอย่างตอนที่แล้ว 3 บรรทัด คือ 6, 12 และ 13 เพียงเท่านี้แหละ

GtkWidget *lbl;

บรรทัดนี้จะกำหนดประเภทของ lbl ให้เป็นชื่ออ้างอิงสำหรับ GtkWidget ที่เป็น GtkLabel ตรงจุดนี้เราใช้ GtkWidget เพราะเป็นคลาสแม่ของ GtkLabel อีกที โดย GtkLabel และจะเพิ่มเติมรายละเอียดภายหลังตอนนี้เอาวิธีการง่ายๆ ก่อน

โปรแกรมที่เพิ่มมาในบรรทัดที่ 12 คือ lbl = gtk_label_new(“CFromZero”); เป็นการสร้างอินสแตนซ์ชื่อ lbl เพื่อเก็บข้อความ “CFromZero” เพื่อเอาไปแสดงผลบนวินโดว์อีกที

บรรทัดที่ 13 คือ gtk_window_set_child (GTK_WINDOW (win), lbl); เป็นคำสั่งสำหรับเพิ่ม lbl เข้าไปในวินโดว์ win

หลังจากนั้นก็สั่งแสดงวินโดว์ win ข้อความ CFromZero ก็จะแสดงอยู่กึ่งกลางหน้าจอ ดังรูปภาพ

ฟังค์ชัน gtk_window_set_chid(GTK_WINDOW(win), lbl) เป็นการสั่งให้เพิ่ม GtkWidget *lbl เข้าไปใน GtkWidget *win ตรงนี้เรียกว่า child widget ซึ่งจะแตกต่างจาก child object โดย object กับ widget ต่างก็มี child ที่จะสืบทอด เพียงแต่มีความแตกต่างกัน (จะหารายละเอียดมาเพิ่มเติมภายหลัง)

ในที่นี้ GtkWindow ที่อ้างอิงกับ win จะไม่มี Parents จึงถูกเรียกว่า top-level window และใน GtkApplication จะมีหลาย top-level window ได้ (จะหารายละเอียดเพิ่มเติมอีกที)

GtkButton

ลำดับต่อไปจะกล่าวถึง GtkButton ซึ่งเป็นปุ่มกด ที่แสดงไว้ในหน้าจอวินโดว์ โดยปุ่มกดนั้นก็จะมีข้อความกำกับด้วย เมื่อคลิกปุ่มนั้น ก็จะเชื่อมไปยังฟังค์ชันใดๆ ตามที่กำหนด โดยผ่าน signal ชื่อ clicked

ตัวอย่างโปรแกรม

 1 #include <gtk/gtk.h>
2
3 static void
4 btn_clicked (GtkButton *btn, gpointer user_data) {
5 g_print("%s, Clicked.\n", (gchar*) user_data);
6 }
7
8 static void
9 app_activate (GApplication *app, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *btn;
12
13 win = gtk_application_window_new (GTK_APPLICATION (app));
14 gtk_window_set_title (GTK_WINDOW (win), "lb2");
15 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
16
17 btn = gtk_button_new_with_label ("Click me");
18 gtk_window_set_child (GTK_WINDOW (win), btn);
19 g_signal_connect (btn, "clicked", G_CALLBACK (btn_clicked), (gpointer) "Button");
20
21 gtk_widget_show (win);
22 }
23
24 int
25 main (int argc, char **argv) {
26 GtkApplication *app;
27 int stat;
28
29 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
30 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
31 stat =g_application_run (G_APPLICATION (app), argc, argv);
32 g_object_unref (app);
33 return stat;
34 }

จากตัวอย่างด้านบนได้เพิ่มในส่วนโปรแกรมดังนี้

กำหนดปุ่มให้มีชื่อเป็น btn เป็น GtkWidget แล้วสร้างปุ่มด้วยคำสั่ง

17   btn = gtk_button_new_with_label ("Click me");

บรรทัดที่ 17 ฟังค์ชัน gtk_button_new_with_label(“Click me”); จะสร้างปุ่มกดไว้บนหน้าจอ window แล้วมีข้อความว่า Click me ปรากฏ แล้วส่งตำแหน่งไปที่ btn เพื่อใช้ในการอ้างอิงในครั้งต่อไป

18   gtk_window_set_child (GTK_WINDOW (win), btn);

บรรทัดที่ 18 กำหนดให้ btn เป็น widget ลูกของ GtkWindow *win

19   g_signal_connect (btn, "clicked", G_CALLBACK (btn_clicked), (gpointer) "Button");

บรรทัดที่ 19 กำหนด signal ให้กับปุ่มกดอ้างอิงถึง btn โดยกำหนดอีเวนต์ clicked เมื่อมีการคลิกปุ่มให้เรียกใช้ฟังค์ชัน btn_clicked และตัวอย่างนี้ผมส่งข้อความ Button ไปด้วยเมื่อมีการคลิกปุ่มกด

สำหรับฟังค์ชัน btn_clicked ก็มีดังนี้

 3 static void
4 btn_clicked (GtkButton *btn, gpointer user_data) {
5 g_print("%s, Clicked.\n", (gchar*) user_data);
6 }

บรรทัดที่ 4 btn_clicked เป็นชื่อฟังค์ชัน มีพารามิเตอร์ 2 ตัว โดยตัวแรก GtkButton *btn ก็คือ ตัวปุ่มกดนั่นแหละ เป็นค่าโดยปริยาย เราไม่ต้องส่งอาร์กิวเมนต์มา (ลองสังเกตบรรทัดที่ 19 เราส่งมาเพียงแต่ข้อความ (gpointer) “Button” อันเดียวเท่านั้น ซึ่งเป็นอาร์กิวเมนต์ตัวที่ 2 ในบรรทัดที่ 4 คือ gpointer user_data โดยสิ่งที่ส่งมานี้เป็น pointer ชี้ไปยังที่เก็บข้อความ “Button” และเวลาจะใช้งาน หรือแสดงข้อความ “Button” ออกทางหน้าจอก็ cast หรือแปลงให้เป็นข้อความหรือ gchar* เสียก่อน ตามบรรทัดที่ 5

5   g_print("%s, Clicked.\n", (gchar*) user_data);

ระบบก็จะวิ่งไปตำแหน่งที่ส่งมาแล้วอ่านเอาข้อมูลที่เป็นข้อความแล้วนำมาแสดงผลในคำสั่ง g_print() ดังนั้นเมื่อมีการคลิกปุ่มกดก็จะมีข้อความ Button, Clicked. ออกทางหน้าจอทุกครั้ง

ตัวอย่างต่อไปยังอยู่ที่ GtkButton เหมือนเดิม แต่รอบนี้จะส่งอินสแตนซ์ win ไปด้วย ดังนี้

 1 static void
2 btn_clicked (GtkButton *btn, gpointer user_data) {
3 GtkWindow *win = GTK_WINDOW (user_data);
4 gtk_window_destroy (win);
5 }
6
7 static void
8 app_activate (GApplication *app, gpointer user_data) {
9 GtkWidget *win;
10 GtkWidget *btn;
11
12 win = gtk_application_window_new (GTK_APPLICATION (app));
13 gtk_window_set_title (GTK_WINDOW (win), "lb3");
14 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
15
16 btn = gtk_button_new_with_label ("Quit");
17 gtk_window_set_child (GTK_WINDOW (win), btn);
18 g_signal_connect (btn, "clicked", G_CALLBACK (btn_clicked), win);
19
20 gtk_widget_show (win);
21 }
22
23 int
24 main (int argc, char **argv) {
25 GtkApplication *app;
26 int stat;
27
28 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
29 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
30 stat =g_application_run (G_APPLICATION (app), argc, argv);
31 g_object_unref (app);
32 return stat;
33 }

บรรทัดที่ 18 เมื่อปุ่มกดถูกคลิกระบบจะเรียกฟังค์ชัน btn_clicked พร้อมส่งอินสแตนซ์ที่ชื่อ win ไปด้วย ซึ่งเป็นพอยเตอร์

เมื่อฟังค์ชัน btn_clicked ถูกเรียก ตอนนี้พารามิเตอร์ user_data จะกลายเป็นตำแหน่งที่เก็บของ win ดังนั้นเราสามารถอ้างอิงไปยังตำแหน่งนั้นๆ ได้ แต่เราไม่สามารถอ้างถึงตัวแปร win หรืออินสแตนซ์ win ได้โดยตรง เพราะชื่อนี้ถูกสร้างในฟังค์ชัน main() เมื่อออกจากขอบเขตของฟังค์ชันแล้ว จะไม่สามารถอ้างถึงชื่อ win ได้

แต่ที่เราส่งมานั้นตัวแปร user_data จะเป็นหมายเลขตำแหน่งของอินสแตนซ์ของ win ซึ่งในฟังค์ชัน btn_clicked นี้ไม่รู้จัก win ดังนั้นก็ต้องสร้างขึ้นใหม่โดยอ้างอิงไปยังตำแหน่งที่ส่งมา และใช้มาโคร GTK_WINDOW(user_data) เพื่อ cast จาก gpointer user_data ให้เป็น GtkWidget *win ตามที่ปรากฏในบรรทัดที่ 3

3   GtkWindow *win = GTK_WINDOW (user_data);

หลังจากเราได้อินสแตนซ์ใหม่ที่ชื่อ win แล้ว (อินสแตนซ์นี้จะมีขอบเขตการทำงานเฉพาะฟังค์ชัน btn_clicked เท่านั้น จะถูกทำลายเมื่อออกจากฟังค์ชัน btn_clicked ดังนั้นอย่างสับสนว่าชื่อซ้ำกันระหว่างบรรทัดที่ 3 กับบรรทัดที่ 9)

จากนั้นเราก็ใช้ฟังค์ชัน gtk_window_destroy(win) เพื่อทำลายหรือลบ GtkWidget win ออกจากระบบ นั่นก็ คือ การออกจากโปรแกรมนั่นเอง

เมื่อรันโปรแกรมตัวอย่างนี้ก็จะได้เหมือนในรูป

ถ้าคลิกปุ่ม Quit โปรแกรมก็จะถูกปิดตามคำสั่งในฟังค์ชัน btn_clicked

GtkBox

GtkWindow และ GtkApplicationWindow สามารถมีได้เพียง child เดียวเท่านั้น นั่นหมายความว่าเราจะเพิ่มปุ่มไปแล้วจะเพิ่ม child ที่เป็นข้อความเพิ่มอีกไม่ได้

ถ้าเราพยายามเพิ่ม child ใหม่ลงไป child เก่าก็จะไม่ถูกนำมาแสดง คือ จะแสดงเฉพาะอันใหม่เท่านั้น

ดังนั้นหากต้องการเพิ่มหลายๆ widget ลงไปก็ต้องเพิ่มเข้าไปในกล่องอะไรสักอย่างหนึ่ง แล้วค่อยเอากล่องนั้นไปเป็น child ให้กับ window ใน Gtk เรียกว่า GtkBox โดยมีหลักการดังนี้

  1. สร้าง GtkApplicationWindow
  2. สร้าง GtkBox เป็น child แล้วเพิ่มลงไปใน GtkApplicationWindow
  3. สร้าง GtkButton แล้วเพิ่มเข้าไปใน GtkBox
  4. สร้าง GtkButton หรือ widget อื่นๆ แล้วเพิ่มลงในกล่อง GtkBox ได้อีก

โดยระบบจะมีผังดังนี้

ตัวอย่างโปรแกรม

#include <gtk/gtk.h>
static void btn_clicked1 (GtkButton *btn, gpointer user_data){
g_print("Button 1 clicked.\n");
}
static void btn_clicked2 (GtkButton *btn, gpointer user_data){
GtkWindow *win = GTK_WINDOW(user_data);
gtk_window_destroy(win);
}
static void app_activate (GApplication *app, gpointer user_data){
GtkWidget *win;
GtkWidget *box;
GtkWidget *lbl;
GtkWidget *btn1;
GtkWidget *btn2;
win = gtk_application_window_new(GTK_APPLICATION(app));
gtk_window_set_title(GTK_WINDOW(win), "GtkBox");
gtk_window_set_default_size(GTK_WINDOW(win), 400,200);
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous(GTK_BOX(box), TRUE);
gtk_window_set_child(GTK_WINDOW(win), box);
lbl = gtk_label_new("CFromZero");
btn1 = gtk_button_new_with_label("Button 1");
g_signal_connect(btn1, "clicked", G_CALLBACK(btn_clicked1), NULL);
btn2 = gtk_button_new_with_label("Button 2 (Quit)");
g_signal_connect(btn2, "clicked", G_CALLBACK(btn_clicked2), win);

gtk_box_append(GTK_BOX(box), lbl);
gtk_box_append(GTK_BOX(box), btn1);
gtk_box_append(GTK_BOX(box), btn2);
gtk_widget_show(win);
}
int main(int argc, char **argv){
GtkApplication *app;
int stat;

app = gtk_application_new("com.example.www", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(app_activate), NULL);
stat = g_application_run(G_APPLICATION(app), argc, argv);
g_object_ref(app);

return stat;
}

ตัวอย่างหน้าจอขณะรันโปรแกรม

box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous(GTK_BOX(box), TRUE);
gtk_window_set_child(GTK_WINDOW(win), box);

จากคำสั่งสามบรรทัดข้างบนนี้ บรรทัดแรก box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); คือ สร้างกล่องขึ้นมา 1 อันให้จัดเรียง widget ที่เพิ่มเข้ามาให้อยู่แนวตั้ง และกำหนดระยะห่างระหว่างแต่ละ widget อยู่ 5 pixel

ถัดมา gtk_box_set_homogeneous(GTK_BOX(box), TRUE); กำหนดให้ ทุก widget ที่เพิ่มเข้ามาใช้พื้นที่ความยาวเต็มหน้าเท่ากันทุก widget

บรรทัดที่ 3 gtk_window_set_child(GTK_WINDOW(win), box); เป็นการเพิ่ม box เข้าไปใน win หลังจากนั้นก็เพิ่ม widget อื่นๆ ด้วยคำสั่ง gtk_box_append(GTK_BOX(box), lbl); เข้าไปตามลำดับบนลงล่าง

gtk_box_append(GTK_BOX(box), lbl);
gtk_box_append(GTK_BOX(box), btn1);
gtk_box_append(GTK_BOX(box), btn2);

คำสั่ง gtk_window_set_child และ gtk_box_append จะมีใน gtk4 เป็นการเพิ่ม object ชื่อ box เข้ากับ gtk_window ชื่อ win และ เพิ่มออบเจ็กต์ lbl, btn1, btn เข้ากับ gtk_box

ทั้งหมดที่กล่าวมานี้ คือ การสร้าง GtkWidget เพื่อสร้าง Label หรือป้ายชื่อ โดยใช้ GtkLabel, สร้างปุ่มกดโดยใช้ GtkButton และสร้างกล่องสำหรับบรรจุ widget ต่างๆ โดยใช้ GtkBox

สิ่งเหล่านี้ก็เป็นพื้นฐานในการแสดงผลของโปรแกรมที่เราเขียนขึ้นหากค่อยๆ ทำความเข้าใจต่อไปก็เรียนรู้เพิ่มเติมได้ไม่ยากนัก…

--

--

Yothin Inbanleng
Yothin Inbanleng

Written by Yothin Inbanleng

สนใจเรียนรู้โดยเฉพาะการเขียนโปรแกรมด้านฐานข้อมูล ไมโครคอนโทรเลอร์ และงานอิเลกโทรนิคส์ ส่วนด้านอื่นๆ ชอบศึกษาประวัติศาสตร์, ถ่ายรูป,ᨲᩫ᩠ᩅᨾᩮᩥ᩠ᨦ, ธรรมะและการบริหาร

No responses yet