使用GTK+库实现一个扫雷程序.docx
《使用GTK+库实现一个扫雷程序.docx》由会员分享,可在线阅读,更多相关《使用GTK+库实现一个扫雷程序.docx(16页珍藏版)》请在冰豆网上搜索。
使用GTK+库实现一个扫雷程序
使用GTK+库实现一个扫雷程序
目录
目录1
1最基本的GTK+程序1
2事件处理与界面布局3
3扫雷程序5
1最基本的GTK+程序
1.1GTK库的主要功能
GTK+,这个库实现的功能有,
1图形显示(Display)
2事件处理(Event)
1.2GTK库的基本要素
GTK+的主要函数和数据结构包括以下三个部分:
1控制流程(ControlFlow)
2控件管理(ManageWidget)
3事件处理(DisposeEvent)
1.3一个最基本的GTK+程序
现在以一个简单的程序进行说明:
代码:
simple.c
----------------------------------------
01#include
02
03intmain(intargc,char**argv)
04{
05GtkWidget*window;
06GtkWidget*label;
07gtk_init(&argc,&argv);
08window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
09g_signal_connect(G_OBJECT(window),"delete_event",
10gtk_main_quit,NULL);
11label=gtk_label_new("GTK+");
12gtk_container_add(GTK_CONTAINER(window),label);
13gtk_widget_show_all(window);
14gtk_main();
15return0;
16}
----------------------------------------
对代码进行简单说明:
01控制流程
gtk_init,gtk_main是控制流程函数,gtk_init是建立GTK程序的初始化环境,gtk_init可以接受命令行参数。
而gtk_main函数则开始GTK函数的主循环。
此外,还有一gtk_main_quit函数,它的功能是退出gtk环境。
02控件和控件管理
这部分的函数和数据结构占据了GTK+库的大部分。
GtkWidget就是控件的数据结构,这个程序里有两个控件,window和label,window是程序的主窗口控件,而label是附着于程序窗口上一个显示文字内容(这里文字内容是“GTK+”)的控件。
gtk_window_new将创建一个窗口,它接受参数以生成不同类型的窗口,这里的参数是GTK_WINDOW_TOPLEVEL。
gtk_label_new将生成一个带有文字内容的控件。
gtk_widget_show_all用来显示窗口。
关于窗口控件,它们之间有一个树型关系,即顶层窗口包含子窗口,子窗口又可以包含新的子窗口。
子窗口只有一个上层窗口作为,但可以包含多个子窗口(树型关系)。
在这个程序里,gtk_container_add即把label作为子窗口添加到window窗口中。
03事件处理
关于事件,包含有:
鼠标的点击、键盘的输入。
事件处理函数负责把这些事件绑定到控件上,并指定事件发生时的处理函数。
在这个程序里,g_signal_connect函数把delete_event事件绑定到window控件上,并且指明了当发生这一件事件时,调用gtk_main_quit函数。
即退出GTK+环境。
总结:
GTK+程序流程
1初始化环境
在main函数的一开始处调用gtk_init函数,标志着进入GTK+环境。
2生成控件
使用各种可以创建控件的函数(*_widget_new,比如gtk_window_new,gtk_label_new)创建控件。
3关联各个控件间关系
即使用gtk_container_add函数将各个控件组织成“一棵树”。
4绑定各种事件到控件上
使用g_signal_connect函数把事件及发生事件时调用的函数绑定到控件上。
5显示根窗口
使用gtk_widget_show函数显示根窗口控件。
6启动GTK+主函数
调用gtk_main函数进入事件处理循环。
7关联根窗口退出事件
使用
g_signal_connect(G_OBJECT(window),"delete_event",
gtk_main_quit,NULL);
函数将退出事件绑定到跟窗口以退出GTK环境,释放资源。
2事件处理与界面布局
2.1事件处理
所谓的事件(event),在这里用来描述执行过程中所遇到的状况。
键盘输入,鼠标的移动,计时通知等等,皆是硬件的事件.。
大家很容易就能想象事件发生的原因跟过程。
来自软件本身产生的事件则不这么明确.例如一个button被按下的事件,窗口大小被调整的事件,画面需要更新的重绘事件,档案内容被更新的事件……许多都是依系统的设计而被定义出来的。
在GTK中在每个widget上都可能会发生好几种事件。
事实上,在程序设计时多半都是在写如何处理各种事件。
这些事件处理的function(eventhandler)以g_signal_connect()注册到指定的widget中。
让指定的object在发生name事件时呼叫func(可指定自己的额外参数func_data):
guintg_signal_connect(GObject*object,
constgchar*name,
GCallbackfunc,
gpointerfunc_data);
返回值
代表这个signalconnection的id。
object
要处理事件的对象。
我们用的widget可以通过G_OBJECT()转换,如G_OBJECT(window).
name
事件的名称,请参考GTK文件上的列表.
func
当事件发生后会呼叫这个callbackfunction.
func_data
提供给func的额外数据.一般不需提供而使用NULL.
callbackfunction的参数和返回值格式最正确的格式可以在GTK的参考手册中各个widget的SignalPrototypes列表里查到。
例如GtkButton下的几个:
"clicked"voiduser_function(GtkButton*button,
gpointeruser_data);
"enter"voiduser_function(GtkButton*button,
gpointeruser_data);
"leave"voiduser_function(GtkButton*button,
gpointeruser_data);
要在button被按下时执行某个function我们可以connect那个function到button的"clicked"事件.用:
voidon_clicked()
{
g_print("Helloworld!
\n");
}
....
g_signal_connect(G_OBJECT(button),"clicked",
G_CALLBACK(on_click),NULL);
在自己定义的callbackfunction中传回TRUE表示这个事件已经被处理完毕。
传回FALSE则GTK会继续找是否还有其它合适的eventhandler。
2.2界面布局
GTK中用box来排放widget可说是最常见也最容易写的.一个box的功能主要是容纳以及计算里面widget的大小,最后决定自己要占用的空间大小.Box又分为HBox跟VBox.HBox把里面的widget以左右横排,vbox则上下直排.
hbox跟vbox的建立跟基本的widget一样,各为gtk_hbox_new()和gtk_vbox_new(),不过需要提供两个参数:
homogeneous(每个格子等宽/等高)及spacing(格子之间的距离,单位为pixel).
GtkWidget*box1;
GtkWidget*box2;
box1=gtk_hbox_new(FALSE,4);/*不等宽,间隔4px*/
box2=gtk_vbox_new(TRUE,0);/*等高,无间隔(0px)*/
将widget放入box里可以使用gtk_box_pack_start()或是gtk_box_pack_end()两个function.gtk_box_pack_start()会将widget依从左到右(hbox)或从上到下(vbox)的顺序找位置存放,gtk_box_pack_end()则是倒着放过来.
3扫雷程序
程序中出现的数据类型以及函数请自行参阅GTK+文档。
功能分析
在挖雷的过程中,玩家可以掀开或标记某个格子,假如掀开的格子有地雷,游戏结束。
否则显示周围有多少地雷。
游戏中也应该提供一个数字,避免让玩家需要计算剩下多少地雷。
当所有不含地雷的格子都被掀开后,恭喜玩家并结束游戏,此外也需要计算游戏时间,以反映玩家对游戏的熟练程度。
至于其它功能如提供玩家改变地雷数目,格子数目等等,目前不考虑包含。
具体实现
格子:
structblock
{
gintcount;/*周围有多少地雷*/
gbooleanmine;/*是否藏有地雷*/
gbooleanmarked;/*是否被标记过*/
gbooleanopened;/*是否已被掀开*/
GtkWidget*button;
};
雷区:
staticstructblock*map;/*地雷区资料*/
staticgintwidth=10;/*地雷区宽度*/
staticgintheight=10;/*地雷区高度*/
staticgintmines=20;/*地雷数量*/
布雷:
/*以随机数安置地雷*/
gintsize=width*height;
ginti=0;
while(igintindex;
gintrow,col;
index=g_random_int_range(0,size);
if(map[index].mine==TRUE)/*已有地雷*/
continue;
map[index].mine=TRUE;
row=index/width;
col=index%width;
/*四周格子的count加一*/
if(row>0){
/*左上*/if(col>0)map[index-width-1].count++;
/*正上*/map[index-width].count++;
/*右上*/if(col}
/*左*/if(col>0)map[index-1].count++;
/*右*/if(colif(row/*左下*/if(col>0)map[index+width-1].count++;
/*正下*/map[index+width].count++;
/*右下*/if(col}
i++;
}
全局数据:
staticGtkWidget*mine_label;/*显示剩余地雷数*/
staticGtkWidget*time_label;/*显示游戏时间*/
staticgintbutton_size=16;/*button大小*/
设置界面及关联事件处理函数:
GtkWidget*vbox;
GtkWidget*hbox;
GtkWidget*label;
ginti,jindex;
vbox=gtk_vbox_new(FALSE,0);
/*存放label的第一个hbox*/
hbox=gtk_hbox_new(FALSE,0);
label=gtk_label_new("Mines:
");
gtk_box_pack_start(GTK_BOX(hbox),label,
FALSE,FALSE,4);
mine_label=gtk_label_new("0");
gtk_box_pack_start(GTK_BOX(hbox),mine_label,
FALSE,FALSE,2);
label=gtk_label_new("Time:
");
gtk_box_pack_start(GTK_BOX(hbox),label,
FALSE,FALSE,4);
time_label=gtk_label_new("0");
gtk_box_pack_start(GTK_BOX(hbox),time_label,
FALSE,FALSE,2);
gtk_widget_show_all(hbox);
gtk_box_pack_start(GTK_BOX(vbox),hbox,
FALSE,FALSE,0);
/*widthxheight个button的格子*/
for(i=0,index=0;ihbox=gtk_hbox_new(FALSE,0);
for(j=0;jGtkWidget*button;
button=gtk_button_new();
gtk_widget_set_usize(button,
button_size,button_size);
g_object_set(G_OBJECT(button),
"can-focus",FALSE,NULL);
gtk_box_pack_start(GTK_BOX(hbox),
button,FALSE,FALSE,0);
gtk_widget_show(button);
g_signal_connect(G_OBJECT(button),
"button-press-event",
G_CALLBACK(on_mouse_click),
(gpointer)index);
map[index].button=button;
index++;
}
gtk_box_pack_start(GTK_BOX(vbox),hbox,
FALSE,FALSE,0);
gtk_widget_show(hbox);
}
注意:
g_signal_connect(G_OBJECT(button),
"button-press-event",
G_CALLBACK(on_mouse_click),
(gpointer)index)
这一函数将事件”button-press-event”关联到函数on_mouse_click()上了。
鼠标点击事件的处理:
函数用到的全局数据
staticgintopened_count;/*已经掀开多少格子*/
staticgintmarked_count;/*已经标记多少格子*/
staticgbooleangame_over;/*游戏是否已结束*/
函数:
on_mouse_click
01gbooleanon_mouse_click(GtkWidget*widget,
02GdkEventButton*event,
03gpointerdata)
04{
05gintindex;
06gintrow,col;
07gcharbuf[4];
08
09if(game_over==TRUE)returnTRUE;/*游戏已结束*/
10
11index=(gint)data;
12
13switch(event->button){
14case1:
/*鼠标左键*/
15/*从index算出发生事件格子的行列*/
16row=index/width;
17col=index%width;
18/*掀开格子*/
19open_block(col,row);
20break;
21case2:
/*鼠标中键*/
22break;
23case3:
/*鼠标右键*/
24/*已掀开的格子不做记号*/
25if(map[index].opened==TRUE)
26break;
27/*原来有记号则消掉,沒有则画上记号*/
28if(map[index].marked!
=TRUE){
29map[index].marked=TRUE;
30gtk_button_set_label(
31GTK_BUTTON(widget),"@");
32marked_count++;
33}else{
34map[index].marked=FALSE;
35gtk_button_set_label(
36GTK_BUTTON(widget),"");
37marked_count--;
38}
39/*显示新的地雷数*/
40g_snprintf(buf,4,"%d",
41MAX(0,mines-marked_count));
42gtk_label_set_text(GTK_LABEL(mine_label),buf);
43}
44
45returnTRUE;
46}
说明:
第09行检查游戏是否已经结束。
若游戏已结束,玩家按下button也没有反映。
第11行将callback接受的data换成数字的index来使用。
这个index也就是在g_signal_connect()
中每个button自己的index。
第19行使用了open_block()來掀开指定的格子。
open_block()是接下來要说明的函数。
第45行传回TRUE表示這个事件已经被处理完毕,GTK不需要再寻找其他callbackfunction处理。
函数:
open_block()
特别说明:
當玩家掀开一块周围完全沒有地雷的格子时(count=0),可以安全的掀开周围的八个格子.若这八个格子之中又有遇到相同的情況则那个格子周围又可以继续掀开.因此我们准备了open_block這个重复呼叫自己的recursivefunction,并由它來检查遊戏是否结束.
01voidopen_block(gintx,ginty)
02{
03gintindex;
04GtkWidget*button;
05
06index=x+y*width;
07
08if(game_over==TRUE||map[index].marked==TRUE)
09return;/*游戏已结束或防止玩家误翻有记号的格子*/
10
11button=map[index].button;
12gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
13TRUE);/*改变button状态为按下*/
14
15if(map[index].opened==TRUE)/*掀开的格子保持按下状态即可*/
16return;
17
18map[index].opened=TRUE;/*格子状态为掀开*/
19
20if(map[index].mine==TRUE){/*若藏有地雷*/
21gtk_button_set_label(GTK_BUTTON(button),"*");
22gameover(FALSE);/*踩到地雷游戏結束*/
23return;
24}
25
26if(map[index].count>0){/*若周围有地雷*/
27gcharbuf[2];
28g_snprintf(buf,2,"%d",map[index].count);
29gtk_button_set_label(GTK_BUTTON(button),buf);
30}
31
32opened_count++;/*已掀开的格子又多了一个*/
33
34if(opened_count+mines==width*height){
35gameover(TRUE);/*所有空地都被翻完時游戏结束*/
36return;
37}
38
39if(map[index].count==0){/*若周围沒有地雷*/
40/*掀开周围格子*/
41if(y>0){
42if(x>0)open_block(x-1,y-1);
43open_block(x,y-1);
44if(x45}
46if(x>0)open_block(x-1,y);
47if(x48if(y49if(x>0)open_block(x-1,y+1);
50open_block(x,y+1);
51if(x52}
53}
54}
第12,13行使用了GtkToggleButton的gtk_toggle_button_set_active()將button設定成按下的状态。
TRUE是按下,FALSE则是未按下的状态。
第21行以gtk_button_set_label()来改变button上显示的文字。
第28行的g_snprintf()是GLIB版本的snprintf(),确保在各平台上都可以使用。
第39~53行是在掀开周围沒有地雷(count=0)的格子時自动將四周格子也加以掀开的部份.周围的每個格子都要先检查是否超出地雷区范围。
函数