Gtk4-tutorial/gfm/sec4.md

368 lines
12 KiB
Markdown
Raw Normal View History

Up: [README.md](../README.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)
# Widgets (1)
2020-12-21 13:12:05 +01:00
## GtkLabel, GtkButton and GtkBox
2020-12-21 13:12:05 +01:00
### GtkLabel
2020-12-21 13:12:05 +01:00
In the previous section we made a window and displayed it on the screen.
Now we go on to the next topic, where we add widgets to this window.
The simplest widget is GtkLabel.
It is a widget with text in it.
2020-12-21 13:12:05 +01:00
2021-01-25 10:35:49 +01:00
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app) {
2021-01-25 10:35:49 +01:00
5 GtkWidget *win;
6 GtkWidget *lab;
7
8 win = gtk_application_window_new (GTK_APPLICATION (app));
9 gtk_window_set_title (GTK_WINDOW (win), "lb1");
10 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
11
12 lab = gtk_label_new ("Hello.");
13 gtk_window_set_child (GTK_WINDOW (win), lab);
14
2023-01-03 07:30:06 +01:00
15 gtk_window_present (GTK_WINDOW (win));
2021-01-25 10:35:49 +01:00
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_DEFAULT_FLAGS);
2021-06-14 10:10:23 +02:00
24 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
2021-01-25 10:35:49 +01:00
25 stat =g_application_run (G_APPLICATION (app), argc, argv);
26 g_object_unref (app);
27 return stat;
28 }
29
~~~
2020-12-22 03:30:06 +01:00
Save this program to a file `lb1.c`.
Then compile and run it.
2020-12-22 03:30:06 +01:00
$ comp lb1
$ ./a.out
2020-12-22 03:30:06 +01:00
A window with a message "Hello." appears.
2020-12-22 03:30:06 +01:00
2021-01-13 12:59:15 +01:00
![Screenshot of the label](../image/screenshot_lb1.png)
2020-12-22 03:30:06 +01:00
There's only a little change between `pr4.c` and `lb1.c`.
2021-06-14 10:10:23 +02:00
A program `diff` is good to know the difference between two files.
2020-12-22 03:30:06 +01:00
~~~
$ cd misc; diff pr4.c lb1.c
4c4
< app_activate (GApplication *app, gpointer user_data) {
---
> app_activate (GApplication *app) {
5a6
> GtkWidget *lab;
8c9
< gtk_window_set_title (GTK_WINDOW (win), "pr4");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb1");
9a11,14
>
> lab = gtk_label_new ("Hello.");
> gtk_window_set_child (GTK_WINDOW (win), lab);
>
18c23
< app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_DEFAULT_FLAGS);
---
> app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_DEFAULT_FLAGS);
~~~
2020-12-22 03:30:06 +01:00
This tells us:
2020-12-22 03:30:06 +01:00
- A signal handler `app_activate` doesn't have `user_data` parameter.
If the fourth argument of `g_signal_connect` is NULL, you can leave out `user_data`.
- The definition of a new variable `lab` is added.
- The title of the window is changed.
2021-06-14 10:10:23 +02:00
- A label is created and connected to the window as a child.
2020-12-22 03:30:06 +01:00
The function `gtk_window_set_child (GTK_WINDOW (win), lab)` makes the label `lab` a child widget of the window `win`.
Be careful.
A child widget is different from a child object.
2021-06-14 10:10:23 +02:00
Objects have parent-child relationships and widgets also have parent-child relationships.
But these two relationships are totally different.
Don't be confused.
In the program `lb1.c`, `lab` is a child widget of `win`.
2021-06-14 10:10:23 +02:00
Child widgets are always located in their parent widget on the screen.
See how the window has appeared on the screen.
The application window includes the label.
2020-12-22 03:30:06 +01:00
2021-01-27 01:12:58 +01:00
The window `win` doesn't have any parents.
We call such a window top-level window.
An application can have more than one top-level window.
2020-12-22 03:30:06 +01:00
### GtkButton
2020-12-22 03:30:06 +01:00
The next widget is GtkButton.
It displays a button on the screen with a label or icon on it.
In this subsection, we will make a button with a label.
When the button is clicked, it emits a "clicked" signal.
The following program shows how to catch the signal and do something.
2020-12-21 13:12:05 +01:00
2021-01-25 10:35:49 +01:00
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 click_cb (GtkButton *btn) {
2021-01-25 10:35:49 +01:00
5 g_print ("Clicked.\n");
6 }
7
8 static void
9 app_activate (GApplication *app) {
2021-01-25 10:35:49 +01:00
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 (click_cb), NULL);
20
2023-01-03 07:30:06 +01:00
21 gtk_window_present (GTK_WINDOW (win));
2021-01-25 10:35:49 +01:00
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_DEFAULT_FLAGS);
2021-06-14 10:10:23 +02:00
30 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
2021-01-25 10:35:49 +01:00
31 stat =g_application_run (G_APPLICATION (app), argc, argv);
32 g_object_unref (app);
33 return stat;
34 }
35
~~~
2020-12-22 03:30:06 +01:00
Look at the line 17 to 19.
2021-06-14 10:10:23 +02:00
First, it creates a GtkButton instance `btn` with a label "Click me".
Then, adds the button to the window `win` as a child.
Finally, connects a "clicked" signal of the button to the handler `click_cb`.
So, if `btn` is clicked, the function `click_cb` is invoked.
2021-06-14 10:10:23 +02:00
The suffix "cb" means "call back".
Name the program `lb2.c` and save it.
2020-12-22 03:30:06 +01:00
Now compile and run it.
2021-01-13 12:59:15 +01:00
![Screenshot of the label](../image/screenshot_lb2.png)
A window with the button appears.
2021-06-14 10:10:23 +02:00
Click the button (it is a large button, you can click everywhere in the window), then a string "Clicked." appears on the terminal.
It shows the handler was invoked by clicking the button.
It's good that we make sure that the clicked signal was caught and the handler was invoked by using `g_print`.
However, using `g_print` is out of harmony with GTK, which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
2021-01-25 10:35:49 +01:00
~~~C
1 static void
2 click_cb (GtkButton *btn, GtkWindow *win) {
3 gtk_window_destroy (win);
4 }
5
6 static void
7 app_activate (GApplication *app) {
8 GtkWidget *win;
9 GtkWidget *btn;
10
11 win = gtk_application_window_new (GTK_APPLICATION (app));
12 gtk_window_set_title (GTK_WINDOW (win), "lb3");
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
14
15 btn = gtk_button_new_with_label ("Close");
16 gtk_window_set_child (GTK_WINDOW (win), btn);
17 g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
18
2023-01-03 07:30:06 +01:00
19 gtk_window_present (GTK_WINDOW (win));
20 }
2021-01-25 10:35:49 +01:00
~~~
And the difference between `lb2.c` and `lb3.c` is as follows.
~~~
$ cd misc; diff lb2.c lb3.c
4,5c4,5
< click_cb (GtkButton *btn) {
< g_print ("Clicked.\n");
---
> click_cb (GtkButton *btn, GtkWindow *win) {
> gtk_window_destroy (win);
14c14
< gtk_window_set_title (GTK_WINDOW (win), "lb2");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb3");
17c17
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Close");
19c19
< g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
29c29
< app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_DEFAULT_FLAGS);
---
> app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_DEFAULT_FLAGS);
35d34
<
~~~
The changes are:
- The function `g_print` in `lb2.c` was deleted and two lines are inserted.
- `click_cb` has the second parameter, which comes from the fourth argument of the `g_signal_connect` at the line 19.
One thing to be careful is the types are different between the second parameter of `click_cb` and the fourth argument of `g_signal_connect`.
The former is `GtkWindow *` and the latter is `GtkWidget *`.
The compiler doesn't complain because `g_signal_connect` uses gpointer (general type of pointer).
In this program the instance pointed by `win` is a GtkApplicationWindow object.
It is a descendant of GtkWindow and GtkWidget class, so both `GtkWindow *` and `GtkWidget *` are correct types for the instance.
- `gtk_destroy (win)` destroys the top-level window. Then the application quits.
- The label of `btn` is changed from "Click me" to "Close".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
The most important change is the fourth argument of the `g_signal_connect`.
This argument is described as "data to pass to handler" in the definition of [`g_signal_connect`](https://docs.gtk.org/gobject/func.signal_connect.html).
### GtkBox
GtkWindow and GtkApplicationWindow can have only one child.
2021-06-14 10:10:23 +02:00
If you want to add two or more widgets in a window, you need a container widget.
GtkBox is one of the containers.
It arranges two or more child widgets into a single row or column.
The following procedure shows the way to add two buttons in a window.
2021-06-14 10:10:23 +02:00
- Create a GtkApplicationWindow instance.
- Create a GtkBox instance and add it to the GtkApplicationWindow as a child.
- Create a GtkButton instance and append it to the GtkBox.
- Create another GtkButton instance and append it to the GtkBox.
2021-06-14 10:10:23 +02:00
After this, the Widgets are connected as the following diagram.
2021-01-13 12:59:15 +01:00
![Parent-child relationship](../image/box.png)
The program `lb4.c` is as follows.
2021-01-25 10:35:49 +01:00
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 click1_cb (GtkButton *btn) {
5 const char *s;
2021-01-25 10:35:49 +01:00
6
7 s = gtk_button_get_label (btn);
8 if (g_strcmp0 (s, "Hello.") == 0)
9 gtk_button_set_label (btn, "Good-bye.");
10 else
11 gtk_button_set_label (btn, "Hello.");
12 }
13
14 static void
15 click2_cb (GtkButton *btn, GtkWindow *win) {
16 gtk_window_destroy (win);
17 }
18
19 static void
20 app_activate (GApplication *app) {
21 GtkWidget *win;
22 GtkWidget *box;
23 GtkWidget *btn1;
24 GtkWidget *btn2;
25
26 win = gtk_application_window_new (GTK_APPLICATION (app));
27 gtk_window_set_title (GTK_WINDOW (win), "lb4");
28 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
29
30 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
31 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
32 gtk_window_set_child (GTK_WINDOW (win), box);
33
34 btn1 = gtk_button_new_with_label ("Hello.");
35 g_signal_connect (btn1, "clicked", G_CALLBACK (click1_cb), NULL);
36
37 btn2 = gtk_button_new_with_label ("Close");
38 g_signal_connect (btn2, "clicked", G_CALLBACK (click2_cb), win);
39
40 gtk_box_append (GTK_BOX (box), btn1);
41 gtk_box_append (GTK_BOX (box), btn2);
42
2023-01-03 07:30:06 +01:00
43 gtk_window_present (GTK_WINDOW (win));
44 }
45
46 int
47 main (int argc, char **argv) {
48 GtkApplication *app;
49 int stat;
50
51 app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_DEFAULT_FLAGS);
52 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
53 stat =g_application_run (G_APPLICATION (app), argc, argv);
54 g_object_unref (app);
55 return stat;
56 }
2021-01-25 10:35:49 +01:00
~~~
2021-06-14 10:10:23 +02:00
Look at the function `app_activate`.
2021-06-14 10:10:23 +02:00
After the creation of a GtkApplicationWindow instance, a GtkBox instance is created.
~~~C
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
~~~
2021-06-14 10:10:23 +02:00
The first argument arranges the children of the box vertically.
The orientation constants are defined like this:
- GTK\_ORIENTATION\_VERTICAL: the children widgets are arranged vertically
- GTK\_ORIENTATION\_HORIZONTAL: the children widgets are arranged horizontally
2021-06-14 10:10:23 +02:00
The second argument is the size between the children.
The unit of the length is pixel.
2021-06-14 10:10:23 +02:00
The next function fills the box with the children, giving them the same space.
2021-06-14 10:10:23 +02:00
After that, two buttons `btn1` and `btn2` are created and the signal handlers are set.
Then, these two buttons are appended to the box.
~~~C
1 static void
2 click1_cb (GtkButton *btn) {
3 const char *s;
4
5 s = gtk_button_get_label (btn);
6 if (g_strcmp0 (s, "Hello.") == 0)
7 gtk_button_set_label (btn, "Good-bye.");
8 else
9 gtk_button_set_label (btn, "Hello.");
10 }
~~~
2023-03-23 09:15:17 +01:00
The function `gtk_button_get_label` returns a text from the label.
The string is owned by the button and you can't modify or free it.
The `const` qualifier is necessary for the string `s`.
If you change the string, your compiler will give you a waring.
You always need to be careful with the const qualifier when you see the GTK 4 API reference.
2021-01-13 12:59:15 +01:00
![Screenshot of the box](../image/screenshot_lb4.png)
2023-03-23 09:15:17 +01:00
The handler corresponding to `btn1` toggles its label.
The handler corresponding to `btn2` destroys the top-level window and the application quits.
2020-12-21 13:12:05 +01:00
Up: [README.md](../README.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)