【问题标题】:How to modify the elements of an array of GtkWidget's with a callback如何使用回调修改 GtkWidget 数组的元素
【发布时间】:2017-11-08 16:48:27
【问题描述】:

我需要编写一个回调来更改存储在 GtkWidget 数组中的标签的字体颜色。

当前状态的代码在这里https://github.com/dustynine/colta-gtk/blob/struct/src/main.c

在初始化接口的函数中,我的数组如下所示:

GtkWidget *cells[CELLS];
for (int i=0; i<CELLS; i++) {
    cells[i] = gtk_label_new("██");
}

连接到按钮的回调:

g_signal_connect(
    G_OBJECT(button),
    "clicked",
    G_CALLBACK(encode),
    make_data(plaintext, key, cells)
);

make_data() 是一个返回带有回调参数的结构的函数:

struct passed_widgets {
    GtkWidget *plaintext;
    GtkWidget *key;
    GtkWidget **cls;
};

gpointer make_data(GtkWidget *text, GtkWidget *key, GtkWidget *cells[CELLS]) {
    static struct passed_widgets pw;
    pw.plaintext = text;
    pw.key = key;
    pw.cls = cells;
    return (gpointer) &pw;
}

在回调内部我有这个:

static void encode(GtkButton *button, gpointer *data) {
    struct passed_widgets *pw;
    GdkColor color;
    pw = (struct passed_widgets *) data;

    gdk_color_parse("#07d9d9", &color);
    gtk_widget_modify_fg(GTK_WIDGET(pw -> cls[0]), GTK_STATE_NORMAL, &color);
}

编译器不会抱怨(除了警告不推荐使用与颜色相关的东西)但是当我实际按下按钮时,Gtk 会报告:

(colta-gtk:3993): GLib-GObject-WARNING **: invalid uninstantiatable type 'GInterface' in cast to 'GtkWidget'
(colta-gtk:3993): Gtk-CRITICAL **: gtk_widget_modify_fg: assertion 'GTK_IS_WIDGET (widget)' failed

根据explore pw -&gt; cls[0],我在gdb中挑选了一下:

'pw -> cls[0]' is a pointer to a value of type 'GtkWidget'

那么,我该如何按预期进行这项工作?我怀疑我要么错误地将数组分配给结构(但这是程序工作的唯一方式),要么在回调结束时尝试修改它时错误地访问它。


编辑:我按照 liberforce 的建议重写了程序。为我的 gui 创建了一个结构:

struct template {
    GtkWidget *window;
    ...
    GtkWidget *plaintext;
    GtkWidget *cells[CELLS];
};

现在我只是将我实例化为gui 的这个结构传递给回调:

g_signal_connect(
    G_OBJECT(gui.button),
    "clicked",
    G_CALLBACK(encode),
    &gui
);

但是我再也无法在 encode() 函数中工作了:

static void encode(GtkButton *button, gpointer data) {
    gchar *plaintext;
    struct template *pw;

    pw = (struct template *) data;

    plaintext = gtk_entry_get_text(GTK_ENTRY(pw -> plaintext));
    printf("   plaintext contents: %s\n", plaintext);

我又一次不明白出了什么问题。根据 gdb 类型匹配,编译器没有给出警告,但是当我运行程序并使用回调时,它给了我:

(colta-gtk:21589): Gtk-CRITICAL **: gtk_entry_get_buffer: assertion 'GTK_IS_ENTRY (entry)' failed

(colta-gtk:21589): Gtk-CRITICAL **: gtk_entry_buffer_get_text: assertion 'GTK_IS_ENTRY_BUFFER (buffer)' failed

地址如下:

gui.plaintext address: 0x55babcfc6290
gui address: 0x7ffd13fe8a88
&gui address: 0x7ffd13fe8910
data address: 0x7ffd13fe8910
pw address: 0x7ffd13fe8910
*pw address: 0x7ffd13fe8a88
pw -> plaintext address: (nil)

*pw -&gt; plaintext 导致分段错误

【问题讨论】:

    标签: c arrays struct callback gtk


    【解决方案1】:

    我想我明白了。我在main() 之外创建了结构原型

    struct template {
        GtkWidget *window;
        ...
        GtkWidget *plaintext;
        GtkWidget *cells[CELLS];
    };
    

    然后在应用程序的“激活”信号上调用的回调中,我这样做:

    static void activate(GtkApplication* app, gpointer user_data) {
        static struct template gui;
    
        gui.plaintext = gtk_entry_new();
        printf("gui.plaintext address: %p\n", gui.plaintext);
    
        g_signal_connect(
            G_OBJECT(gui.button),
            "clicked",
            G_CALLBACK(encode),
            &gui
        );
    

    在编码回调中我有这个

    static void encode(GtkButton *button, gpointer data) {
        gchar *plaintext;
        struct template *pw;
    
        pw = (struct template *) data;
    
        plaintext = gtk_entry_get_text(GTK_ENTRY(pw -> plaintext));
        printf("   plaintext contents: %s\n", plaintext);
    
        GdkColor color;
        gdk_color_parse("#ff0000", &color);
        gtk_widget_modify_fg(GTK_WIDGET(pw -> cells[0]), GTK_STATE_NORMAL, 
        &color);
    }
    

    现在一切都按预期进行。基本上我通过在activate 回调中添加staticstruct template gui 声明来解决我的问题。我将在 github 上保留该文件的工作版本的链接以供将来参考。感谢 liberforce 为我指明了正确的方向。

    【讨论】:

    • 好吧,我不知道你在使用 GtkApplication。如果在连接到activate 信号时使用将结构的地址作为user_data 传递,则在堆栈上分配主要工作。或者您可以将其声明为静态,但不要在回调处理程序中执行此操作,而是在文件顶部执行此操作,以便可以看到文件全局的对象。您还可以使用g_new 在堆上创建结构。重要的是,您的 gui 结构必须与您的应用程序具有相同的生命周期。
    • 这意味着如果你在回调中声明堆栈上的结构,当你退出回调时它会被销毁:你正在跟踪一个指向它的位置的指针,但是一旦堆栈被修改,这个空间就是覆盖。
    • 顺便说一句,你也应该尝试切换到真正的构建系统。例如,您的构建脚本对我不起作用,因为我有bash,但没有sh。 Meson 构建系统是 GTK/GNOME 项目的推荐构建系统:mesonbuild.com。例如,Meson 将启用 -Wall 以使您启用一组合理的警告(并且您应该始终使用它作为最低限度)。你可能想看看我在 github.com/liberforce/metrognome 的 repo,我正在努力跟上编写现代 GTK+/C 应用程序的最佳实践。
    • 好的,我一定会去看看的。我对 C 和 GTK 还是很陌生,非常感谢您的帮助。
    【解决方案2】:

    encode 的原型错误。它应该是gpointer data,而不是gpointer *datagpointer 已经是 void * 的 typedef。

    此外,您的make_data 函数应该采用指向您的结构参数的指针,然后填充它。不要使用静态结构,因为它会降低代码的可重用性。

    通常我只是在我的main 中创建一个struct 代表我的gui,填充它,然后传递堆栈分配的结构(或其子元素)的地址作为g_signal_connect 的最后一个参数。

    还有一点是不需要显式地将指针转换为gpointer,因为每个指针都可以隐式转换为gpointer

    【讨论】:

    • 按照您的建议进行操作,并将我所有的 gui 放入一个结构中,但我根本无法使这个变体工作。我已将详细信息添加到 OP
    • 发生这些断言是因为您传递了无效的指针。它要么不是有效的指针,要么是有效的小部件,但不是您期望的正确类型。 pw-&gt;plainext 应该是一个 GtkEntry。我认为您使用它的方式没有任何问题,因此错误可能在于您初始化结构的方式。
    • 另外,只需调用gtk_entry_get_text 以获取GtkEntry 中的文本。
    • 我像 struct template gui; 这样初始化结构,然后调用:gui.plaintext = gtk_entry_new(); 就是这样。
    • 另外,你说pw-&gt;plaintext 应该是GtkEntry 但我认为调用GTK_ENTRY() 会将它从一个小部件转换为一个条目
    猜你喜欢
    • 1970-01-01
    • 2015-03-27
    • 2021-12-16
    • 2018-04-20
    • 1970-01-01
    • 2016-10-20
    • 1970-01-01
    • 2023-02-15
    • 1970-01-01
    相关资源
    最近更新 更多