首页 > 代码库 > GTK进阶学习:绘图事件

GTK进阶学习:绘图事件

GTK界面只要有图片的地方,其底层实际上是通过绘图实现的,所以,我们很有必要学习一下绘图,这里我们使用 Cairo 进行相应的绘图操作。


Cairo是用于绘制二维矢量图形的跨平台图形库,采用 C 语言实现,又被许多其它计算机语言所绑定。我们可以使用Cairo库在窗口中绘图,也可以用于生成PNG图片、PDF、PostScript、SVG文件。Cairo同时也是自由软件库,自GTK+2.8版本开始,Cairo成为GTK+库的一部分。


绘图实际上也是事件的一种,GTK中,绘图事件也叫曝光事件。


绘图时所触发的信号:expose-event

只要触发曝光事件信号"expose-event",就会自动调用所连接的回调函数。


这里需要注意的是,曝光事件信号 "expose-event" 默认的情况下,是自动触发的(当然也可以人为触发),就算我们不作任何操作,"expose-event"信号也有可能自动触发。前面我们学习中,我们按一下按钮就人为触发 "clicked" 信号,按一下鼠标人为触发 "button-press-event" 信号,如果我们不操作按钮,不操作鼠标,其对应的信号永远不会触发。


曝光事件信号 "expose-event" 什么时候会自动触发呢?

窗口状态(移动,初始化,按按钮……)改变,只用我们肉眼能看到窗口上有变化,它都会自动触发曝光事件信号"expose-event",然后就自动会调用它所连接的回调函数,但是,它不是刷新窗口的全部区域,它是按需要局部刷新,哪儿变化了就刷新那个变化的区域。


当然我们也可以人为触发曝光事件信号"expose-event",并且指定刷图区域:

触发信号,并且刷新图片的整个区域:

void gtk_widget_queue_draw(GtkWidget *widget );

widget:控件指针


触发信号,并指定刷图区域:

void  gtk_widget_queue_draw_area(

GtkWidget *widget,

gint x,

gint y,

gint width,

gint height);

widget:控件指针

x, y:刷图的起点坐标

width, height:刷图的宽、高


需要注意的是,我们绘图的操作不是写在任何函数都行,尽量在曝光事件信号 "expose-event" 所连接的回调函数里进行相应的绘图操作。

gboolean callback( GtkWidget *widget,       

GdkEventExpose *event, 

gpointer data )

{

// 绘图的相关操作

……


return FALSE; // 尽量返回FALSE

}

如果窗口里有其它控件,回调函数必须返回FALSE,否则窗口里的控件会被绘图覆盖。


使用 Cairo , 需要包含的头文件:#include <cairo.h>。


创建Cairo环境:
cairo_t *gdk_cairo_create(

GdkDrawable *drawable );

drawable:绘图区域

返回值:cairo绘图环境指针
注意:如果给窗口绘图,窗口本身不能绘图,窗口本质上是一个结构体,里面有个window成员,这个window成员才是真正的绘图区域。如:

GtkWidget *w = gtk_window_new( GTK_WINDOW_TOPLEVEL );

cairo_t *cr = gdk_cairo_create(w->window); // 注意传的参数


回收资源: 

void cairo_destroy(cairo_t *cr);

参数:cairo绘图环境指针


设置画图的图片:

void gdk_cairo_set_source_pixbuf(

cairo_t *cr, 

const GdkPixbuf *pixbuf, 

double pixbuf_x, 

double pixbuf_y );

cr:cairo绘图环境指针

pixbuf:图片资源对象

pixbuf_x,pixbuf_y:画图的起点位置


绘制设置好的图片:

void cairo_paint(cairo_t *cr);

cr:cairo绘图环境指针

注意:如果绘制图片后想继续写字或画线,必须手动设置画笔颜色( cairo_set_source_rgb() ), 否则,字体或线条会被图片覆盖。


如果在窗口上绘图,要设置允许窗口绘图:

void gtk_widget_set_app_paintable(

GtkWidget *widget, 

gboolean app_paintable )

widget:控件指针

app_paintable:TRUE允许绘图,FALSE不允许


绘图注意事项(能不用绘图尽量不用,绘图效率较低):

1)尽量不要在绘图回调函数做太多的复杂数据处理,绘图的任务只是绘图,尽量不要做别的事情 (因为绘图随时有可能自动调用,导致效率很低)

2)绘图回调函数里一定不要调用gtk_widget_queue_draw() (因为会导致死循环,效率很低)

void fun()
{
gtk_widget_queue_draw() // error
}


绘图流程:

1)允许窗口能绘图(顺序随意)
2)连接曝光信号"expose-event"

3)实现绘图回调函数(绘图是绘窗口里面的window成员,在GtkWidget这个结构体里能找到这个成员 )


以下例子为,通过绘图实现背景,按按钮窗口的笑脸会移动:

#include <cairo.h>	// 绘图所需要的头文件
#include <gtk/gtk.h>

int startx = 0;
int w = 400;
int h = 300;

// 绘图事件
gboolean on_expose_event (GtkWidget * widget, GdkEventExpose *event, gpointer data)
{
	cairo_t *cr = gdk_cairo_create(widget->window);	// 创建cairo环境
	
	// 画背景图
	// 获取图片
	GdkPixbuf *src_pixbuf = gdk_pixbuf_new_from_file("./image/back.jpg", NULL); 
	// 指定图片大小
	GdkPixbuf* dst_pixbuf = gdk_pixbuf_scale_simple(src_pixbuf, w, h, GDK_INTERP_BILINEAR);
	// dst_pixbuf作为cr环境的画图原材料,(0, 0):画图的起点坐标
	gdk_cairo_set_source_pixbuf(cr, dst_pixbuf, 0, 0);
	cairo_paint(cr);	// 绘图
	g_object_unref(dst_pixbuf);
	g_object_unref(src_pixbuf);
	
	// 画笑脸
	src_pixbuf = gdk_pixbuf_new_from_file("./image/face.png", NULL);
	dst_pixbuf = gdk_pixbuf_scale_simple(src_pixbuf, 80, 80, GDK_INTERP_BILINEAR);
	gdk_cairo_set_source_pixbuf(cr, dst_pixbuf, startx, (h/10)*3);
	cairo_paint(cr);
	g_object_unref(dst_pixbuf);
	g_object_unref(src_pixbuf);
	
	/*
	// 绘图与写字共存的测试
	// 如果绘完图片后想继续写字或画线,
	// 必须手动设置画笔颜色cairo_set_source_rgb()
	// 否则,字体或线条会被图片覆盖。
	cairo_set_source_rgb(cr, 0.627, 0, 0);  // 设置字体颜色
	cairo_set_font_size(cr, 40.0);			// 设置字体大小
	cairo_move_to(cr, 50.0, 130.0);			// 写字的起点坐标
	cairo_show_text(cr, "This is a test");	// 写字
	*/
	
	cairo_destroy(cr);	// 回收所有Cairo环境所占用的内存资源
	
	return FALSE;	// 必须返回FALSE
}

// 按钮按下回调函数
void deal_button_clicked(GtkWidget *widget, gpointer data)
{
	startx += 20;
	if(startx >= w){
		startx = 0;
	}
	
	gtk_widget_queue_draw( GTK_WIDGET(data) );	// 更新刷图区域
}

int main (int argc, char *argv[])
{
	gtk_init (&argc, &argv);
	
	GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); // 顶层窗口
	g_signal_connect(window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);	// 中央位置显示
	gtk_widget_set_size_request(window, 400, 300);		    // 窗口最小大小
	gtk_window_set_resizable(GTK_WINDOW(window), FALSE);	// 固定窗口的大小
	
	GtkWidget *table = gtk_table_new(5, 5, TRUE);	// 表格布局容器
	gtk_container_add(GTK_CONTAINER(window), table); // 容器加入窗口
	
	// button
	GtkWidget *button = gtk_button_new_with_label("click me");		// 按钮
	g_signal_connect(button, "clicked", G_CALLBACK(deal_button_clicked), window);
	gtk_table_attach_defaults(GTK_TABLE(table), button, 3, 4, 4, 5);// 把按钮加入布局
	
	// 绘图事件信号与回调函数的连接
	g_signal_connect(window, "expose-event", G_CALLBACK(on_expose_event), NULL);
	
	gtk_widget_set_app_paintable(window, TRUE);	// 允许窗口可以绘图
	
	gtk_widget_show_all(window);	// 显示所有控件
	
	gtk_main();
	
	return 0;
}

程序运行效果图如下:

技术分享


源代码下载请点此处。



GTK进阶学习:绘图事件