【问题标题】:How do I tell GTK to update application externally?我如何告诉 GTK 从外部更新应用程序?
【发布时间】:2020-04-17 20:54:07
【问题描述】:

背景和问题

出于某些原因,我需要分叉我的代码并在两个分叉上更新一个变量。该变量通过mmap 存储在内存中,因此所有进程都可以访问它。在一个子进程中,我增加了变量。如何告诉 GTK 应用程序从子进程刷新/更新/重绘?

MWE

/*
 * Update GTK label from variable stored in mmap
 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <gtk/gtk.h>

static void activate (GtkApplication *app, gpointer localval) {
    GtkWidget *window;
    // Button Containers
    GtkWidget *button_box_quit;
    // Buttons
    GtkWidget *exit_button;
    // Text
    GtkWidget *text_status;
    
    // Define Window, dynamic size for screen.
    window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "test");
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
    
    // Define Button Boxes.
    button_box_quit = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
    
    // Define Exit Button, put it in a box, put box in window
    exit_button = gtk_button_new_with_label ("Exit");
    gtk_container_add(GTK_CONTAINER (button_box_quit), exit_button);
    gtk_container_add(GTK_CONTAINER (window), button_box_quit);

    // Connect signals to buttons
    g_signal_connect_swapped (exit_button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
    
    // Define text status
    char msg[32]={0};
    // The "print" line
    g_snprintf(msg, sizeof msg, "val: %d\n", *(int *)localval);
    text_status = gtk_label_new(msg);
    gtk_container_add(GTK_CONTAINER (button_box_quit), text_status);
    
    //Activate!
    g_snprintf(msg, sizeof msg, "val: %d\n", *(int *)localval);
    gtk_label_set_text(GTK_LABEL(text_status), msg);
    gtk_widget_show_all (window);
}

int main (int argc, char **argv) {
    GtkApplication *app;
    int status;
    
    int *VAL = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    int *ABORT = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    int pid = fork();
    
    if (pid == 0) {
        while(!*ABORT) {
            printf("%d\n", *VAL);
            // Increments here should be reflected outside this PID.
            *VAL = *VAL + 1;
            usleep(1000000);
        }
        exit(0);
    } else {
        app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
        // The passing line
        g_signal_connect (app, "activate", G_CALLBACK (activate), VAL);
        status = g_application_run (G_APPLICATION (app), argc, argv);
        g_object_unref (app);
        *ABORT = 1;
    }
    *ABORT = 1;
    return status;
}

运行时会发生什么

当 MWE 运行时,终端会尽职尽责地在每次更新时打印该值。然而,GTK 窗口永远显示“val: 1”。我们可以通过在activate 进程中的gtk_widget_show_all 之前添加usleep(3000000) 来告诉GTK 进程可以访问存储在mmap 中的值。在此变体中,窗口将永远显示“val: 4”。

重申的问题

如何使 GTK 窗口上的输出与终端匹配?

【问题讨论】:

    标签: c gtk3


    【解决方案1】:

    那是因为activate 只被调用一次(当窗口被加载/激活时)但加载后没有刷新标签,我在代码中做了一些更改(使用全局,非常难看但很简单来说明问题),“退出按钮”现在是“刷新按钮”。按下它,你会看到VAL的变化。

    /*
     * Update GTK label from variable stored in mmap
     */
    
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <gtk/gtk.h>
    
    static GtkWidget *text_status;
    
    static void refresh(GtkWidget *widget, gpointer data)
    {
        (void)widget;
    
        char msg[32]={0};
    
        g_snprintf(msg, sizeof msg, "val: %d\n", *(int *)data);
        gtk_label_set_text(GTK_LABEL(text_status), msg);
    }
    
    static void activate (GtkApplication *app, gpointer localval) {
        GtkWidget *window;
        // Button Containers
        GtkWidget *button_box_quit;
        // Buttons
        GtkWidget *refresh_button;
        // Text
    
    
        // Define Window, dynamic size for screen.
        window = gtk_application_window_new (app);
        gtk_window_set_title (GTK_WINDOW (window), "test");
        gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
    
        // Define Button Boxes.
        button_box_quit = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
    
    
    
        // Define Exit Button, put it in a box, put box in window
        refresh_button = gtk_button_new_with_label ("Refresh");
        gtk_container_add(GTK_CONTAINER (button_box_quit), refresh_button);
        gtk_container_add(GTK_CONTAINER (window), button_box_quit);
    
        // Connect signals to buttons
        g_signal_connect(refresh_button, "clicked", G_CALLBACK (refresh), localval);
    
        // Define text status
        char msg[32]={0};
        // The "print" line
        g_snprintf(msg, sizeof msg, "val: %d\n", *(int *)localval);
        text_status = gtk_label_new(msg);
        gtk_container_add(GTK_CONTAINER (button_box_quit), text_status);
    
        //Activate!
        g_snprintf(msg, sizeof msg, "val: %d\n", *(int *)localval);
        gtk_label_set_text(GTK_LABEL(text_status), msg);
        gtk_widget_show_all (window);
    }
    
    int main (int argc, char **argv) {
        GtkApplication *app;
        int status;
    
        int *VAL = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
        int *ABORT = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
        int pid = fork();
    
        if (pid == 0) {
            while(!*ABORT) {
                printf("%d\n", *VAL);
                // Increments here should be reflected outside this PID.
                *VAL = *VAL + 1;
                usleep(1000000);
            }
            exit(0);
        } else {
            app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
            // The passing line
            g_signal_connect (app, "activate", G_CALLBACK (activate), VAL);
            status = g_application_run (G_APPLICATION (app), argc, argv);
            g_object_unref (app);
            *ABORT = 1;
        }
        *ABORT = 1;
        return status;
    }
    

    如果你想在不按按钮的情况下刷新标签,可以使用g_timeout_add并设置一个函数定期调用刷新VAL

    【讨论】:

    • 非常感谢这个有效的解决方案。我之前尝试过类似的东西,但我没有包括关键的(void)widget。不幸的是,将此答案移植到使用g_timeout_add 的答案并不简单。我到处玩,想出了一些适合的东西。我会将其作为单独的答案发布给将来遇到类似挑战的人们。
    • 不客气,嗯...,(void)widget; 根本不是关键,只是通知编译器没有使用参数并避免警告。
    【解决方案2】:

    g_timeout_add解决方案

    如@David Ranieri 指出的,为了允许从应用程序自动更新主循环,我们可以使用g_timeout_add。但是,GTK3 的 API 要求我们将 refresh 函数传递给 g_timeout_add 略有不同。

    修改 OP MWE 和@David Ranieri 的回答:

    /*
     * Update GTK label from variable stored in mmap
     * Timeout Method
     */
    
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <gtk/gtk.h>
    
    static GtkWidget *text_status;
    
    static gboolean refresh(gpointer data) {
        char msg[32]={0};
    
        g_snprintf(msg, sizeof msg, "val: %d\n", *(int *)data);
        gtk_label_set_text(GTK_LABEL(text_status), msg);
    
        return TRUE;
    }
    
    static void activate (GtkApplication *app, gpointer localval) {
        GtkWidget *window;
        // Button Containers
        GtkWidget *button_box_quit;
        // Buttons
        GtkWidget *exit_button;
    
        // Define Window, dynamic size for screen.
        window = gtk_application_window_new (app);
        gtk_window_set_title (GTK_WINDOW (window), "test");
        gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
    
        // Define Button Boxes.
        button_box_quit = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
    
        // Define Exit Button, put it in a box, put box in window
        exit_button = gtk_button_new_with_label ("Exit");
        gtk_container_add(GTK_CONTAINER (button_box_quit), exit_button);
        gtk_container_add(GTK_CONTAINER (window), button_box_quit);
    
        // Connect signals to buttons
        g_signal_connect_swapped (exit_button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
    
        // Define text status
        text_status = gtk_label_new(NULL);
        gtk_container_add(GTK_CONTAINER (button_box_quit), text_status);
    
        // Define timeout
        g_timeout_add(500, G_SOURCE_FUNC(refresh), localval);
    
        // Activate!
        refresh(localval);
        gtk_widget_show_all (window);
    }
    
    int main (int argc, char **argv) {
        GtkApplication *app;
        int status;
    
        int *VAL = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
        int *ABORT = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
        int pid = fork();
    
        if (pid == 0) {
            while(!*ABORT) {
                printf("%d\n", *VAL);
                // Increments here should be reflected outside this PID.
                *VAL = *VAL + 1;
                usleep(1000000);
            }
            exit(0);
        } else {
            app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
            // The passing line
            g_signal_connect (app, "activate", G_CALLBACK (activate), VAL);
            status = g_application_run (G_APPLICATION (app), argc, argv);
            g_object_unref (app);
            *ABORT = 1;
        }
        *ABORT = 1;
        return status;
    }
    

    重要的区别:

    • 我们不再像使用回调时那样传递空小部件来刷新。
    • 必须明确告知 GTK3 refreshG_SOURCE_FUNC

    【讨论】:

    • 很好,为了让代码在生产中更简洁,我会在 // 定义文本状态 部分中重用 refresh()ideone.com/uSI9er
    • 我还将在创建text_status 小部件之后放置g_time_add() 行,注意如果在创建小部件之前执行回调,它将在refresh 内部崩溃(例如,如果有一天你从 500 毫秒切换到 50 毫秒)
    • 根据您的建议在编辑中清理。我在 Raspberry Pi 上运行,所以我没有观察到超时减少导致的崩溃。即使是创建一个简单的 GTK 窗口也需要超过 1 秒的时间。对于使用此示例的快速机器,添加启动延迟可能是值得的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 2023-03-26
    • 2012-10-16
    • 1970-01-01
    • 2018-07-15
    • 1970-01-01
    相关资源
    最近更新 更多