Gtk对于通常的gui程序,大家想做的事就是做一点事件处理(包括各种计算、文件操作等),然后在界面上显示出来

以下内容全部为个人在linux下编程的经验,为个人原创。如转载请注明出处,并请保持文章完整。

编写GTK+程序基本思想

社团 linuxfans 作者 sagaeon

对于通常的gui程序,大家想做的事就是做一点事件处理(包括各种计算、文件操作等),然后在界面上显示出来。以下分步骤以gtk为例地介绍一些概念上的入门知识。(因所看资料已无法找到,不对之处请大家指出)。

一我们想完成什么功能及会碰到的问题

我们先考虑在编写带图形界面的程序时几种情况吧。
1.你想单击按钮button_dis后在button_dis上显示一个数字
2. 你想每1秒在按钮button_dis上显示一个数字。
3..你想用libpcap抓包、分析、并在界面上显示分析结果
4.做个游戏,通过按銉控制你的动作。

1 对于第一种情况,我们给个列子看看吧
代码:

//gcc pro1.c -o pro1 `pkg-config --cflags --libs gtk+-2.0`

#include <gtk/gtk.h>
#include <unistd.h>

static int  count=1;
char c[2];
static void hello( GtkWidget *widget,
                  gpointer   data )
{
  c[0] = (char)((*((int*)data))+48);
   
  count=(count++)%9;     
  gtk_button_set_label ((GtkButton*)widget,c);
   
}

static void destroy( GtkWidget *widget,
                    gpointer   data )
{
   gtk_main_quit ();
}

int main( int   argc,
         char *argv[] )
{  
   GtkWidget *window;
   GtkWidget *button;

   gtk_init (&argc, &argv);
   /* create a new window */
   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  
   g_signal_connect (G_OBJECT (window), "destroy",
           G_CALLBACK (destroy), NULL);
   gtk_container_set_border_width (GTK_CONTAINER (window), 20);
   button = gtk_button_new_with_label ("Hello World");
   g_signal_connect (G_OBJECT (button), "clicked",
           G_CALLBACK (hello), NULL);
   gtk_container_add (GTK_CONTAINER (window), button);
   gtk_widget_show (button);
   gtk_widget_show (window);
   gtk_main ();
   return 0;
}


然后 gcc pro1.c -o pro1 `pkg-config --cflags --libs gtk+-2.0` 就可以啦
很容易实现吧。大家花点时间学学基本的gtk编程,这个程序就很好理解啦。我们再来看第二种情况

2. 大家想是不是可以这样,在button的回调函数中放以下代码: 代码:

static void hello( GtkWidget *widget,
                  gpointer   data )
{
   
  int i=0;
  for(;i<5;i++){
    
    c[0] = (char)(count+48);
    gtk_button_set_label ((GtkButton*)widget,c);
    count=(count++)%9;
    sleep(1);
  }
   
}

运行一下,结果不是如你所愿,每秒(不太精确的)增加一个数字。观察一下有什么问题?结果并不如愿每秒按钮上的数字自动刷新,而是5秒后才显示5,并且在这期间,窗口就像死了一样,不能响应鼠标事件。要解决这个问题,就要考虑gtk中的gui主循环。
大多数gui主程序都是一个事件驱动的无限循环。这个主循环会检测一个由下层系统提供的事件队列,从队列中取出事件并处理。处理事件的函数一般叫回调函数。将事件与回调函数联系起来的机制一般叫注册。比如
g_signal_connect ((gpointer) about1, "activate",
G_CALLBACK (on_about1_activate),
NULL);
先不论第四个参数,这个函数就表示:若在小部件about1上发生activate信号,就调用on_about1_activate函数。这种注册机制其实很好理解,比如你可以用一个表,里面装的就是每个小部件的信号与回调函数对应表。而调用g_signal_connect就是填充这个表。(在gtk中事件与信号有区别。见文章底部的背景资料1)。
这个主循环(gtk_main()函数)就这样循环检测在本窗口上有无信号和事件发生,并根据你的回调函数响应。这里有个前提就是gtk要不停地热循环,才能及时地响应外部请求。当它调用回调函数时,注意,这段时间它没有进行外部事件的循环检测,所以不能响应外部事件。如果你的回调函数处理时间特别长,自然,你就会看到我们程序中出现的问题。要解决这个问题,我们先看一个简单的方法。
代码:

static void hello( GtkWidget *widget,
                  gpointer   data )
{
   
  int i=0;
  for(;i<5;i++){
    
    c[0] = (char)(count+48);
    gtk_button_set_label ((GtkButton*)widget,c);
    while (gtk_events_pending ())
    gtk_main_iteration ();
    count=(count++)%9;
    sleep(1);
  }
   
}

编译,如何,这此行了吧。可是我们还是有问题。
待续。。。。。。。

三。如何完成?----MVC结构

解决办法就是mvc结构,即 model --- view ---controller。使用这个术语的有java、smalltalk(注1)等,《面向对象分析与设计(2e)》Grady Booch用display item object表示与model一样的含义(注2),MFC中用document代表model(《MFC程序设计》)。通过对view与model的分离,我们实现了一个良好的程序框架(注3)。


注1:《design pattern》英 page 4《面向对象分析与设计2e》page 98
注2: 《面向对象分析与设计2e》 page 98
注3:如何分离view与model见《applying UML and patterns 2e》Craig Larman page471
待补充

四。model 与 view之间的可选通信机制
待续.....




背景资料:

1.信号与事件在gtk中的区别(为个人学习总结,不具权威性,不对之处请多探讨)
在X系统中,由下而上关系是Xlib-->GDK-->GTK。我们先看GDK,大家知道, X和GDK都是一个事件驱动系统,也就是说,它们的工作方式就是从底层系统的事件队列中不停取事件,然后进行相应的处理。当然,这个事件队列是更底层的系统产生的(比如GDK的事件队列是从X中取得,X又从....)。这里,事件是什么?是一种结构,这个结构包括许多要素,比如(鼠标单击,在窗口A上,坐标为X,Y)就是一个事件,可见,事件是自包含的,它含有你所想要的全部信息,根据这些信息,你就可以做许多事。比如,在GDK的基础上你可以设计一个主循环,假设你有两个小部件A,B,你想在当鼠标在A上单击时(一个事件)在B上显示“saga"。你可以这样做你的主循环:1.初始化,做一个表,表中有两项,分别为A,B。A为空,B中放两项clicked-->display表示当B收到clicked时display(显示saga)。2.循环:取事件,若a.单击事件发生的地点在A上(看事件这个结构中的位置项,可以是坐标,也可以是对应的窗口),则A向B发出一个"clicked"(记住这句话),B收到"clicked"就display,b.单击不发生在A上,丢掉这个事件,再取下一个事件进行判断,周而复始。看到吗?假设屏幕上有几十个小部件,每个小部件就如A,B那样做一些表,放入自己收到"clicked"(或其它的什么)时要做的事(一个函数),在主循环中查到(根据位置)在某个小部件上发生"clicked"就查表并做相应的事件(函数),它的功能就如同GTK一样强大了。把这当成一个框架,任何人用它,只要填好那个表,再写好收到"clicked"后要执行的函数就可以了,很方便吧,你都可以做系统架构师了。GTK就是这样做的!(当然GTK还做了太多的其它的事,看它的名字"Gimp ToolKit"--画好小部件是它要做的正事)
将一些词用正式术语替换,这个过程就是这样:主循环收到一个事件(鼠标单击,在窗口A),便由窗口A向B发出一个信号"clicked",B看 (在这儿就是查表了)有没有注册了的回调函数(注册--就是那个g_connect_signal的作用了),有一个注册了的函数,就是display (...),便去执行这个函数(在B上显示saga)。

大家看到A是向B发一个信号,其实也可以向自己发一个信号,就是这样描述:当有一个发生在A上的单击事件时,A向A发出一个"clicked"信号,A收到后在A上显示"saga"!而且大多数编程人员的实现中,A就是向自己发一个信号,也就是说,常常是经历了某个事件(A经历了单击这个事件,说经历,是因为发生在自己身上)的小部件发出一个给自己的信号并处理这个信号,有点晕?这正是常常把事件与信号混淆的原因!!!

这样我们就可以总结一些区别了:

1.信号总是由一个小部件发出的。而事件是下层驱动产生的(当然,大多数情况是由人发出再经过系统、X的包装,传到GDK),它自身带有事件产生的位置信息。
2.它们之间有许多相似的函数,比如你可以用gtk_widget_set(add)_events,让一个小部件监听一个事件(比如你可以偷听在不同小部件上发生的鼠标单击),就象听一个信号一样,不同的是你可以听到在其它部件上发生的事件,却不能听到不是发给你的信号。
3.信号有一个传递机制,比如A并没有处理这个信号的回调函数,就会把这个信号向父类传,直到有人处理或到最顶层。而事件始终在事件队列中,你可以向操作系统说(术语就是注册)我关心鼠标单击事件,有了告诉我,你不说,它就不告诉你!
4.还有许多细节上的差异。

posted on 2007-06-16 00:39  cy163  阅读(...)  评论(...编辑  收藏

导航