GTK中绑定一个回调函数到相应构件中基本上都是采用GObject中所提供的g_signal_connect()等函数,而回调函数的定义形式又是void function(GtkWidget *widget, gpointer data);
这两者的形式很明显就是只允许传一个参数。但一个参数就只能包含一个内容吗?显然C语言中的结构体此处就是一个必不可少的角儿了,所以呢,我们应该恰当的去定义一个结构体,包含我们需要传递的变量,然后进行传参。
下面是一个代码示例,其中创建的结构体包含了两个变量:
struct EntryStruct
{
GtkEntry *usernameEntry;
GtkEntry *passwordEntry;
};
代码如下:
#include <gtk/gtk.h> #include <stdlib.h> /*因为要传参给回调函数,故此处设直一个结构体以将usernameEntry和idEntry包装起来*/ struct EntryStruct { GtkEntry *usernameEntry; GtkEntry *passwordEntry; }; void approveButton_clicked(GtkWidget* button, gpointer data); void backToMainButton_clicked(GtkWidget* button, gpointer data); int main(int argc, char *argv[]) { gtk_init(&argc, &argv); struct EntryStruct entries; GtkLabel *usernameLabel, *passwordLabel; GtkButton *approvetButton, *cancelButton; GtkTable *tableView; GtkWindow *window; window = (GtkWindow*) gtk_window_new(GTK_WINDOW_TOPLEVEL); /*初始化label*/ usernameLabel = (GtkLabel*) gtk_label_new(NULL); gtk_label_set_markup(usernameLabel, "<b>账号</b>"); passwordLabel = (GtkLabel*) gtk_label_new(NULL); gtk_label_set_markup(passwordLabel, "<b>密码</b>"); /*初始化entry*/ entries.usernameEntry = (GtkEntry*) gtk_entry_new(); entries.passwordEntry = (GtkEntry*) gtk_entry_new(); /*初始化button,并设置相应事件*/ approvetButton = (GtkButton*) gtk_button_new_with_label("确认"); g_signal_connect(GTK_OBJECT(approvetButton), "clicked", GTK_SIGNAL_FUNC(approveButton_clicked), (gpointer) & entries); cancelButton = (GtkButton*) gtk_button_new_with_label("取消"); g_signal_connect(GTK_OBJECT(cancelButton), "clicked", GTK_SIGNAL_FUNC(backToMainButton_clicked), NULL); /*创建新布局*/ tableView = (GtkTable*) gtk_table_new(4, 4, FALSE); gtk_table_attach(tableView, GTK_WIDGET(usernameLabel), 2, 3, 1, 2, GTK_EXPAND , GTK_FILL, 30, 15); gtk_table_attach(tableView, GTK_WIDGET(entries.usernameEntry), 3, 4, 1, 2, GTK_EXPAND , GTK_FILL, 30, 15); gtk_table_attach(tableView, GTK_WIDGET(passwordLabel), 2, 3, 2, 3, GTK_EXPAND , GTK_FILL, 30, 15); gtk_table_attach(tableView, GTK_WIDGET(entries.passwordEntry), 3, 4, 2, 3, GTK_EXPAND , GTK_FILL, 30, 15); gtk_table_attach(tableView, GTK_WIDGET(approvetButton), 2, 3, 3, 4, GTK_FILL | GTK_EXPAND , GTK_FILL, 30, 15); gtk_table_attach(tableView, GTK_WIDGET(cancelButton), 3, 4, 3, 4, GTK_FILL | GTK_EXPAND , GTK_FILL, 30, 15); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(tableView)); gtk_widget_show_all(GTK_WIDGET(window)); gtk_window_set_title(GTK_WINDOW(window), "用户登录"); gtk_window_set_default_size(GTK_WINDOW(window), 400, 300); gtk_main(); return EXIT_SUCCESS; } /*确认事件的回调函数*/ void approveButton_clicked(GtkWidget* button, gpointer data) { struct EntryStruct *entry = (struct EntryStruct*) data; GtkEntry *usernameEntry = (GtkEntry*) entry->usernameEntry; GtkEntry *passwordEntry = (GtkEntry*) entry->passwordEntry; char username[20]; char password[20]; strcpy(username, gtk_entry_get_text((GtkEntry*) usernameEntry)); strcpy(password, gtk_entry_get_text((GtkEntry*) passwordEntry)); //下面就是你自己的操作了 } /*取消事件的回调函数*/ void backToMainButton_clicked(GtkWidget* button, gpointer data) { }
当然,如果这篇博客就是这么一段代码也就
这段代码中有一个缺陷,那就是参数虽然传递给了回调函数,但回调函数却有可能无法获取那两个Entry实例 。
先说下缺陷在哪里吧,就是在定义EntryStruct时(代码的第17行),我并没有添加一个static,结果就...当然在这个程序中还是没有错误的。
static struct EntryStruct entries;
这个问题得说到C语言内存分配的三种形式:
1.在静态存储区域分配。这个在程序编译的时候就已经分配好了,在整个程序运行其间都存在,如全局变量,static变量。
2.在栈上中分配。在执行函数时,函数内局部变量的存储单元都可以在栈上创建。函数执行结束时这些存储单元自动被释放(即栈的pop)。
3.从堆上分配,亦即动态内存分配。程序在运行时由程序员手动去分配,通常采用malloc和free(C++中还多了一个new和delete)。
程序的问题就出在这里,定义了一个局部的非静态变量,C语言自动的将其压入栈中,而函数运行完之后,结构体就会被释放,虽然传递了一个指向结构体的指针过去,但这块内存上的内容已经不是原来那样的了。而加了static之后,就很理解了。(当然也可以将其定义为全局变量,不过这个可是很容易污染程序的哦。)
这个程序的缺陷就在于此,虽然main是最后一个结束的函数(这也意味着结构体在程序运行期间是一直存在的)。但若出现在其他函数中,问题就显而易见了,感觉这个问题还是让人挺容易忽视的。