问题描述
我正在尝试用 C 为活动监视器构建一个 GUI 应用程序,并决定使用 GTK 库和 Glade 来帮助设计。 (使用 Ubuntu 20.04)
按下按钮后,值会显示在各自的位置,并在每次点击时更新。 唯一的问题是我需要它自己实时更新,所以我用 sleep(1) 将代码转移到无限循环中,所以它每 1 秒更新一次。但是现在甚至没有在 GUI 上显示值。 为了测试代码是否正在执行,我尝试在控制台上从代码的不同部分打印值,它们确实被打印了。
我尝试过但没有奏效的事情:
- 在循环和递归之间切换,都失败了。
- 使用 time.h 库将 sleep() 函数替换为自制定时器
- 将显示 GUI 的代码封装到一个函数中,并在循环中调用整个函数。
- 使用 GDK 函数强制刷新 GUI,因此它会在每次迭代中手动更新 GUI。
- 在代码的不同部分使用 gtk_show_all 以强制它在每次迭代结束时显示。
我认为这与按钮触发器有关,并且仅在执行回调函数后才会在 GUI 上更新输出(根据我对控制台打印的观察)。 因此,我尝试以编程方式间隔按下按钮,以避免每次都必须自己单击它,但找不到太多关于该主题的信息。
如果您能想到任何方法来完成这项工作或替代我正在采取的方法,请提供帮助。 主要思想是输出 GUI 应该实时更新值,而不管按钮如何。
提前致谢!
这是用于在 GUI 上打印值的函数:
struct timespec tm;
tm.tv_sec = 0;
tm.tv_nsec = 1000 * 1000 * 1000;
myproc_t* myprocs = NULL;
unsigned int myprocs_len = 0;
//call to function that will return the processes and their specifications
sample_processes(&myprocs,&myprocs_len,tm);
if(s == 0){
// sort by cpu usage
qsort(myprocs,myprocs_len,sizeof(myprocs[0]),myproc_comp_pcpu);
}
else if(s == 1){
// sort by Memory usage
qsort(myprocs,myproc_comp_RSS);
}
for (i = 0; i < myprocs_len && i < 5; i++)
{
if (strlen(myprocs[i].cmd) == 0) {
break;
}
//convert specs read from /proc file to string format
sprintf(pid,"%d",myprocs[i].tid);
sprintf(cpu,"%.2f",myprocs[i].pcpu);
sprintf(memory,"%lu",myprocs[i].vm_RSS/1000);
sprintf(cmd,"%s",myprocs[i].cmd);
switch(i)
{
case 0:
gtk_label_set_text(GTK_LABEL(PID1),pid);
gtk_label_set_text(GTK_LABEL(cpu1),cpu);
gtk_label_set_text(GTK_LABEL(MEM1),memory);
gtk_label_set_text(GTK_LABEL(CMD1),cmd);
case 1:
gtk_label_set_text(GTK_LABEL(PID2),pid);
gtk_label_set_text(GTK_LABEL(cpu2),cpu);
gtk_label_set_text(GTK_LABEL(MEM2),memory);
gtk_label_set_text(GTK_LABEL(CMD2),cmd);
case 2:
gtk_label_set_text(GTK_LABEL(PID3),pid);
gtk_label_set_text(GTK_LABEL(cpu3),cpu);
gtk_label_set_text(GTK_LABEL(MEM3),memory);
gtk_label_set_text(GTK_LABEL(CMD3),cmd);
case 3:
gtk_label_set_text(GTK_LABEL(PID4),pid);
gtk_label_set_text(GTK_LABEL(cpu4),cpu);
gtk_label_set_text(GTK_LABEL(MEM4),memory);
gtk_label_set_text(GTK_LABEL(CMD4),cmd);
case 4:
gtk_label_set_text(GTK_LABEL(PID5),pid);
gtk_label_set_text(GTK_LABEL(cpu5),cpu);
gtk_label_set_text(GTK_LABEL(MEM5),memory);
gtk_label_set_text(GTK_LABEL(CMD5),cmd);
}
}
解决方法
使用带有 sleep(1)
的循环或任何阻塞的东西总是不行的,因为这意味着你有效地阻止了 UI 线程做任何实际工作。正常的工作流程如下:
- 您希望通过使用
gtk_main()
或gtk_application_new()
并连接到“激活”信号(将在您调用gtk_application_run()
时调用)来运行主循环。 - 初始化您的后端代码
- 初始化您的 UI 代码,您可以在其中为每个进程创建必要的小部件。此时,您可能已经希望使用
gtk_widget_show()
和朋友 使其可见
- 对于定期更新,您应该将定期事件发布到主循环,您可以使用
g_timeout_add_seconds ()
之类的 API。在回调中,您可以在上一步中创建的标签上调用gtk_label_set_text()
。只要回调返回G_SOURCE_CONTINUE
,就会按照指定的时间间隔定期调用回调
为了扩展@nielsdg 所说的正确,基于事件循环(例如 GTK)的 UI 代码必须将阻塞代码限制在最低限度。
/* Never do this: it will freeze the UI */
static void
on_button_clicked()
{
do {
/* Your code here */
sleep(1);
while (condition);
}
相反,展开您的代码并利用主事件循环:
static gboolean
iteration(gpointer user_data)
{
/* Your code here */
return condition ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
}
static void
on_button_clicked()
{
g_timeout_add(1000,iteration,NULL);
}
这只是给你主要的想法。上面的代码有一些问题,最重要的是如果你点击两次它会很高兴地开始两个合作循环。