GTK+ 编程入门:从基础窗口到复杂视图
1. GTK+ 窗口基础
在 GTK+ 编程中,
GTK_WINDOW_TOPLEVEL
是常用的窗口类型,因为创建对话框有更便捷的方式。创建窗口时,先使用
gtk_window_new
在内存中初始化窗口,之后可以对其进行各种设置,如添加组件、调整大小、更改标题等,最后使用
gtk_widget_show
让窗口显示在屏幕上。示例代码如下:
#include <gtk/gtk.h>
int main (int argc, char *argv[])
{
GtkWidget *window;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(window);
gtk_main ();
return 0;
}
gtk_main
函数启动交互过程,将控制权交给 GTK+,直到调用
gtk_main_quit
才会返回。在上述示例中,若不进行额外处理,关闭窗口后应用程序不会结束,可通过在启动程序的 shell 窗口中输入
Ctrl+C
来退出。
2. 事件、信号与回调函数
2.1 基本概念
所有 GUI 库都需要一种机制来响应用户操作。在命令行程序中,可以暂停执行等待输入,然后根据输入使用
switch
语句分支执行。但 GUI 应用程序需要持续响应用户输入,因此现代窗口系统采用事件和事件监听器机制。GTK+ 有自己的事件和事件监听器系统,即信号和回调函数。
GTK+ 信号由
GtkObject
在用户输入等情况下发出,连接到信号的函数称为回调函数。回调函数原型通常如下:
void a_callback_function ( GtkWidget *widget, gpointer user_data);
连接回调函数使用
g_signal_connect
函数:
gulong g_signal_connect(gpointer *object, const gchar *name, GCallback func, gpointer user_data );
2.2 示例:回调函数的使用
以下是一个在窗口中添加按钮并连接 “clicked” 信号到回调函数的示例:
#include <gtk/gtk.h>
#include <stdio.h>
static int count = 0;
void button_clicked(GtkWidget *button, gpointer data)
{
printf("%s pressed %d time(s) \n", (char *) data, ++count);
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
button = gtk_button_new_with_label("Hello World!");
gtk_container_add(GTK_CONTAINER(window), button);
g_signal_connect(GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (button_clicked),
"Button 1");
gtk_widget_show(button);
gtk_widget_show(window);
gtk_main ();
return 0;
}
在这个示例中,每次点击按钮,回调函数会打印一条消息。
3. 容器组件:打包框组件
3.1 布局的重要性
GUI 的布局对其可用性至关重要,但由于用户的屏幕分辨率、窗口大小、主题、字体和配色方案不同,布局变得困难。为了创建在所有系统上都统一的 GUI,应避免使用绝对坐标放置组件,而使用更灵活的布局系统,GTK+ 提供了容器组件来实现这一点。
3.2 打包框组件介绍
打包框组件是不可见的组件,用于包含其他组件并控制其布局。GTK+ 有两种主要的打包框子类:
-
GtkHBox
:单行水平打包框组件。
-
GtkVBox
:单列垂直打包框组件。
创建打包框时,需要指定两个参数
homogeneous
和
spacing
:
GtkWidget* gtk_hbox_new (gboolean homogeneous, gint spacing);
GtkWidget* gtk_vbox_new (gboolean homogeneous, gint spacing);
homogeneous
为
TRUE
时,强制包含的组件占用相等空间;
spacing
设置组件之间的像素间隔。
添加组件到打包框使用
gtk_box_pack_start
和
gtk_box_pack_end
函数:
void gtk_box_pack_start (GtkBox *box, GtkWidget *child,
gboolean expand, gboolean fill,
guint padding);
void gtk_box_pack_end (GtkBox *box, GtkWidget *child,
gboolean expand, gboolean fill,
guint padding);
这两个函数的参数说明如下表:
| 参数 | 描述 |
| ---- | ---- |
|
GtkBox *box
| 要填充的打包框 |
|
GtkWidget *child
| 要放置在打包框中的组件 |
|
gboolean expand
| 若为
TRUE
,该组件将填充与其他设置相同标志的组件共享的所有可用空间 |
|
gboolean fill
| 若为
TRUE
,该组件将填充分配给它的空间,而不是用作边缘填充,仅在
expand
为
TRUE
时有效 |
|
guint padding
| 组件周围的像素填充大小 |
3.3 示例:组件容器布局
以下是一个使用
GtkHBox
和
GtkVBox
布局
GtkLabel
组件的示例:
#include <gtk/gtk.h>
void closeApp ( GtkWidget *window, gpointer data)
{
gtk_main_quit();
}
gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
printf("In delete_event\n");
return FALSE;
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *label1, *label2, *label3;
GtkWidget *hbox;
GtkWidget *vbox;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "The Window Title");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
g_signal_connect ( GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC ( closeApp), NULL);
g_signal_connect ( GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC ( delete_event), NULL);
label1 = gtk_label_new("Label 1");
label2 = gtk_label_new("Label 2");
label3 = gtk_label_new("Label 3");
hbox = gtk_hbox_new ( TRUE, 5 );
vbox = gtk_vbox_new ( FALSE, 10);
gtk_box_pack_start(GTK_BOX(vbox), label1, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), label2, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox), label3, FALSE, FALSE, 5);
gtk_container_add(GTK_CONTAINER(window), hbox);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
该示例创建了两个打包框
hbox
和
vbox
,并将
label1
和
label2
放入
vbox
,再将
vbox
和
label3
放入
hbox
,最后将
hbox
放入窗口并显示。
4. 常见 GTK+ 组件 API
4.1 GtkWindow
GtkWindow
是所有 GTK+ 应用程序的基本元素,以下是一些重要的函数:
| 函数 | 描述 |
| ---- | ---- |
|
GtkWidget* gtk_window_new (GtkWindowType type)
| 创建一个新的空窗口 |
|
void gtk_window_set_title (GtkWindow *window, const gchar *title)
| 设置窗口标题 |
|
void gtk_window_set_position (GtkWindow *window, GtkWindowPosition position)
| 设置窗口初始位置 |
|
void gtk_window_set_default_size (GtkWindow *window, gint width, gint height)
| 设置窗口默认大小 |
|
void gtk_window_resize (GtkWindow *window, gint width, gint height)
| 调整窗口大小 |
|
void gtk_window_set_resizable (GtkWindow *window, gboolean resizable)
| 设置窗口是否可调整大小 |
|
void gtk_window_present (GtkWindow *window)
| 确保窗口在屏幕上可见 |
|
void gtk_window_maximize (GtkWindow *window)
| 最大化窗口 |
|
void gtk_window_unmaximize (GtkWindow *window)
| 取消窗口最大化 |
4.2 GtkEntry
GtkEntry
是单行文本输入组件,常用于输入简单的文本信息,如电子邮件地址、用户名或主机名。以下是一些常用函数:
| 函数 | 描述 |
| ---- | ---- |
|
GtkWidget* gtk_entry_new (void)
| 创建一个新的
GtkEntry
组件 |
|
GtkWidget* gtk_entry_new_with_max_length (gint max)
| 创建一个有最大输入长度限制的
GtkEntry
组件 |
|
void gtk_entry_set_max_length (GtkEntry *entry, gint max)
| 设置最大允许字符数 |
|
G_CONST_RETURN gchar* gtk_entry_get_text (GtkEntry *entry)
| 获取输入的文本 |
|
void gtk_entry_set_text (GtkEntry *entry, const gchar *text)
| 设置输入的文本 |
|
void gtk_entry_append_text (GtkEntry *entry, const gchar *text)
| 追加文本到输入框 |
|
void gtk_entry_prepend_text (GtkEntry *entry, const gchar *text)
| 在输入框前添加文本 |
|
void gtk_entry_set_visibility (GtkEntry *entry, gboolean visible)
| 设置输入文本是否可见 |
|
void gtk_entry_set_invisible_char (GtkEntry *entry, gchar invch)
| 设置不可见字符 |
|
void gtk_entry_set_editable (GtkEntry *entry, gboolean editable)
| 设置输入框是否可编辑 |
4.3 示例:用户名和密码输入
以下是一个创建用户名和密码输入窗口并验证密码的示例:
#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>
const char * password = "secret";
void closeApp ( GtkWidget *window, gpointer data)
{
gtk_main_quit();
}
void button_clicked (GtkWidget *button, gpointer data)
{
const char *password_text = gtk_entry_get_text(GTK_ENTRY((GtkWidget *) data));
if (strcmp(password_text, password) == 0)
printf("Access granted!\n");
else
printf("Access denied!\n");
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *username_label, *password_label;
GtkWidget *username_entry, *password_entry;
GtkWidget *ok_button;
GtkWidget *hbox1, *hbox2;
GtkWidget *vbox;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "GtkEntryBox");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);
g_signal_connect ( GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC ( closeApp), NULL);
username_label = gtk_label_new("Login:");
password_label = gtk_label_new("Password:");
username_entry = gtk_entry_new();
password_entry = gtk_entry_new();
gtk_entry_set_visibility(GTK_ENTRY (password_entry), FALSE);
ok_button = gtk_button_new_with_label("Ok");
g_signal_connect (GTK_OBJECT (ok_button), "clicked",
GTK_SIGNAL_FUNC(button_clicked), password_entry);
hbox1 = gtk_hbox_new ( TRUE, 5 );
hbox2 = gtk_hbox_new ( TRUE, 5 );
vbox = gtk_vbox_new ( FALSE, 10);
gtk_box_pack_start(GTK_BOX(hbox1), username_label, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox1), username_entry, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox2), password_label, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox2), password_entry, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), ok_button, FALSE, FALSE, 5);
gtk_container_add(GTK_CONTAINER(window), vbox);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
该示例创建了两个
GtkEntry
组件用于输入用户名和密码,将密码输入框的可见性设置为
FALSE
以隐藏输入的密码,点击按钮时验证密码并输出相应信息。
4.4 GtkSpinButton
GtkSpinButton
适用于让用户输入数值的场景,如最大速度或设备长度。它限制用户只能输入数字字符,还能设置允许值的范围,并且提供上下箭头方便用户使用鼠标调整数值。以下是常用的 API 调用:
| 函数 | 描述 |
| ---- | ---- |
|
GtkWidget* gtk_spin_button_new (GtkAdjustment *adjustment, gdouble climb_rate, guint digits)
| 使用
GtkAdjustment
对象创建
GtkSpinButton
|
|
GtkWidget* gtk_spin_button_new_with_range (gdouble min, gdouble max, gdouble step)
| 直接指定范围创建
GtkSpinButton
|
|
void gtk_spin_button_set_digits (GtkSpinButton *spin_button, guint digits)
| 设置显示的小数位数 |
|
void gtk_spin_button_set_increments (GtkSpinButton *spin_button, gdouble step, gdouble page)
| 设置增量大小 |
|
void gtk_spin_button_set_range (GtkSpinButton *spin_button, gdouble min, gdouble max)
| 设置允许值的范围 |
|
gdouble gtk_spin_button_get_value (GtkSpinButton *spin_button)
| 获取当前值 |
|
gint gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button)
| 获取当前值的整数形式 |
|
void gtk_spin_button_set_value (GtkSpinButton *spin_button, gdouble value)
| 设置当前值 |
创建
GtkSpinButton
前,需要先创建
GtkAdjustment
对象,示例如下:
#include <gtk/gtk.h>
void closeApp ( GtkWidget *window, gpointer data)
{
gtk_main_quit();
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *spinbutton;
GtkObject *adjustment;
gtk_init (&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size ( GTK_WINDOW(window), 300, 200);
g_signal_connect ( GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC ( closeApp), NULL);
adjustment = gtk_adjustment_new(100.0, 50.0, 150.0, 0.5, 0.05, 0.05);
spinbutton = gtk_spin_button_new(GTK_ADJUSTMENT(adjustment), 0.01, 2);
gtk_container_add(GTK_CONTAINER(window), spinbutton);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
这个示例创建了一个范围在 50 到 150 之间的
GtkSpinButton
。
4.5 不同类型的 GtkButton
4.5.1 GtkToggleButton
GtkToggleButton
与
GtkButton
类似,但它有状态,可以是开启或关闭。用户点击时,它会发出 “clicked” 信号并切换状态。相关 API 如下:
| 函数 | 描述 |
| ---- | ---- |
|
GtkWidget* gtk_toggle_button_new (void)
| 创建一个新的
GtkToggleButton
|
|
GtkWidget* gtk_toggle_button_new_with_label (const gchar *label)
| 创建一个带标签的
GtkToggleButton
|
|
gboolean gtk_toggle_button_get_active (GtkToggleButton *toggle_button)
| 获取按钮的状态 |
|
void gtk_toggle_button_set_active (GtkToggleButton *toggle_button, gboolean is_active)
| 设置按钮的状态 |
4.5.2 GtkCheckButton
GtkCheckButton
是
GtkToggleButton
的一种特殊形式,外观为复选框,功能上没有区别。创建函数如下:
| 函数 | 描述 |
| ---- | ---- |
|
GtkWidget* gtk_check_button_new (void)
| 创建一个新的
GtkCheckButton
|
|
GtkWidget* gtk_check_button_new_with_label (const gchar *label)
| 创建一个带标签的
GtkCheckButton
|
4.5.3 GtkRadioButton
GtkRadioButton
可以分组,同一组中只能选择一个选项。相关函数如下:
| 函数 | 描述 |
| ---- | ---- |
|
GtkWidget* gtk_radio_button_new (GSList *group)
| 创建一个
GtkRadioButton
并指定组 |
|
GtkWidget* gtk_radio_button_new_from_widget (GtkRadioButton *group)
| 根据现有按钮创建同组的
GtkRadioButton
|
|
GtkWidget* gtk_radio_button_new_with_label (GSList *group, const gchar *label)
| 创建一个带标签的
GtkRadioButton
并指定组 |
|
void gtk_radio_button_set_group (GtkRadioButton *radio_button, GSList *group)
| 设置按钮所在的组 |
|
GSList* gtk_radio_button_get_group (GtkRadioButton *radio_button)
| 获取按钮所在的组 |
4.5.4 示例:不同类型的 GtkButton
#include <gtk/gtk.h>
#include <stdio.h>
GtkWidget *togglebutton;
GtkWidget *checkbutton;
GtkWidget *radiobutton1, *radiobutton2;
void closeApp ( GtkWidget *window, gpointer data)
{
gtk_main_quit();
}
void add_widget_with_label(GtkContainer * box, gchar * caption, GtkWidget * widget)
{
GtkWidget *label = gtk_label_new (caption);
GtkWidget *hbox = gtk_hbox_new (TRUE, 4);
gtk_container_add(GTK_CONTAINER (hbox), label);
gtk_container_add(GTK_CONTAINER (hbox), widget);
gtk_container_add(box, hbox);
}
void print_active(char * button_name, GtkToggleButton *button)
{
gboolean active = gtk_toggle_button_get_active(button);
printf("%s is %s\n", button_name, active?"active":"not active");
}
void button_clicked(GtkWidget *button, gpointer data)
{
print_active("Checkbutton", GTK_TOGGLE_BUTTON(checkbutton));
print_active("Togglebutton", GTK_TOGGLE_BUTTON(togglebutton));
print_active("Radiobutton1", GTK_TOGGLE_BUTTON(radiobutton1));
print_active("Radiobutton2", GTK_TOGGLE_BUTTON(radiobutton2));
printf("\n");
}
gint main (gint argc, gchar *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *vbox;
gtk_init (&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);
g_signal_connect ( GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (closeApp), NULL);
button = gtk_button_new_with_label("Ok");
togglebutton = gtk_toggle_button_new_with_label("Toggle");
checkbutton = gtk_check_button_new();
radiobutton1 = gtk_radio_button_new(NULL);
radiobutton2 = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(radiobutton1));
vbox = gtk_vbox_new (TRUE, 4);
add_widget_with_label (GTK_CONTAINER(vbox), "ToggleButton:", togglebutton);
add_widget_with_label (GTK_CONTAINER(vbox), "CheckButton:", checkbutton);
add_widget_with_label (GTK_CONTAINER(vbox), "Radio 1:", radiobutton1);
add_widget_with_label (GTK_CONTAINER(vbox), "Radio 2:", radiobutton2);
add_widget_with_label (GTK_CONTAINER(vbox), "Button:", button);
g_signal_connect(GTK_OBJECT(button), "clicked",
GTK_SIGNAL_FUNC(button_clicked), NULL);
gtk_container_add(GTK_CONTAINER(window), vbox);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
这个示例展示了如何使用不同类型的
GtkButton
,并通过点击 “Ok” 按钮查看各个按钮的状态。
4.6 GtkTreeView
GtkTreeView
是 GTK+ 2 中的新型组件,用于创建数据的列表和树视图,如电子表格或文件管理器中的视图。它封装了大量功能,可以创建复杂的数据视图,混合文本和位图图形,甚至使用
GtkEntry
组件输入数据。
GtkTreeView
家族由四个组件组成:
-
GtkTreeView
:树和列表视图
-
GtkTreeViewColumn
:表示列表或树的列
-
GtkCellRenderer
:控制单元格的绘制
-
GtkTreeModel
:表示树和列表数据
这里采用了 Model/View/Controller(MVC)设计模式,将视图和模型分离,数据可以由不同视图同时渲染,避免不必要的重复。
4.6.1 创建 GtkTreeStore
GtkTreeStore
用于存储多级数据,如目录层次结构。创建时需要指定列数和每列的类型,示例如下:
Gtkwidget *store = gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
添加数据时,需要使用
GtkTreeIter
结构指向新行,示例如下:
GtkTreeIter iter;
gtk_tree_store_append (store, &iter, NULL);
gtk_tree_store_set (store, &iter,
0, "Def Leppard",
1, 1987,
2, TRUE,
-1);
4.6.2 创建 GtkTreeView
创建
GtkTreeView
时,需要传入
GtkTreeStore
或
GtkListStore
模型,示例如下:
GtkWidget *view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
配置视图时,需要为每列定义
GtkCellRenderer
并设置数据源,示例如下:
GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view),
0,
"This is the column title",
renderer,
"text", 0,
NULL);
4.6.3 示例:GtkTreeView 的使用
#include <gtk/gtk.h>
enum {
COLUMN_TITLE,
COLUMN_ARTIST,
COLUMN_CATALOGUE,
N_COLUMNS
};
void closeApp ( GtkWidget *window, gpointer data)
{
gtk_main_quit();
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkTreeStore *store;
GtkWidget *view;
GtkTreeIter parent_iter, child_iter;
GtkCellRenderer *renderer;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size ( GTK_WINDOW(window), 300, 200);
g_signal_connect ( GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC ( closeApp), NULL);
store = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
gtk_tree_store_append (store, &parent_iter, NULL);
gtk_tree_store_set (store, &parent_iter, COLUMN_TITLE, "Dark Side of the Moon",
COLUMN_ARTIST, "Pink Floyd",
COLUMN_CATALOGUE, "B000024D4P",
-1);
gtk_tree_store_append (store, &child_iter, &parent_iter);
gtk_tree_store_set (store, &child_iter, COLUMN_TITLE, "Speak to Me",
-1);
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view),
COLUMN_TITLE,
"Title", renderer,
"text", COLUMN_TITLE,
NULL);
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view),
COLUMN_ARTIST,
"Artist", renderer,
"text", COLUMN_ARTIST,
NULL);
gtk_container_add(GTK_CONTAINER(window), view);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
这个示例展示了如何使用
GtkTreeView
创建一个简单的树视图,包含父行和子行,并设置列的标题和数据源。
综上所述,掌握这些常见的 GTK+ 组件及其 API,能够帮助开发者创建出功能丰富、布局合理的 GUI 应用程序。在实际开发中,可以根据具体需求选择合适的组件,并结合信号和回调函数实现交互功能。
超级会员免费看
38

被折叠的 条评论
为什么被折叠?



