我的C语言学习历程:GUI篇

此 CSDN 博客不再更新,如需了解 GUI 项目开发状况,可前往项目主页:https://lcui.lc-soft.io

该文章内容已经重新整理,建议访问以下链接以提升阅读体验:https://blog.lc-soft.io/posts/c-lang-learning.html


编写自己的GUI图形库

游戏的编写已经在数月前暂停,因为我又有了个想法:自己写个GUI库,这样,以后写图形界面的程序就方便多了,不必每写个程序就要为它的图形界面再写代码,直接写个通用的API就可以了。

因为在学习机端还没有能用的GUI库,Qt呢,其他编译过Qt程序的机友说在显示父窗口时会出现段错误,能用的就是MessageBox来显示界面。

 

就这样,我决定编写一个适合学习机的GUI图形库,这个图形库我命名为LCUI,LC就是我的名字拼音LiuChao的首字母,UI即User Interface(用户界面)的简称。

目前只添加了Picture_Box部件、Label部件,这些部件的名称、功能,仿照了VC的工具箱中的窗口控件。

头文件的编写,我先仿照了FreeType2的头文件编写风格,但是,遇到了一些问题,头文件包含头文件,循环包含,最终编译器编译时一直在循环打印同一个错误,关于这个问题,解决方法是使用宏进行头文件保护,提问的帖子在这:http://topic.csdn.net/u/20111108/14/0247823b-5c99-47da-86b2-aa20640b2580.html

在后来,参考了gtk的头文件,还是把我的头文件中的结构体的定义方式改了一下。

 

图形的显示,不再是利用mgaview程序源码中的write_to_fb函数显示图形了,我从里面直接提取出核心代码,做了修改,用这个代码自己操作framebuffer。

 

窗口的绘制代码还是比较简陋,能用就行了,以后再完善一下。

 

这个是Label部件的使用效果图:

使用的是微软雅黑字体,字体图形的获取,我用了FreeType2提供的API,这个FreeType2我折腾了很久,还是没有解决文字对齐问题,网上也没有相关的代码,都是清一色的获取单个文字字形的示例代码;实现这个窗口的源代码在这里:http://blog.csdn.net/liuchao35758600/article/details/6953328

现在倒是解决了文字对齐问题了,可是获取宋体字体的汉字位图出了问题,不能显示汉字,汉字是乱点。

如果有使用过FreeType2的朋友,请提供联系方式,想交流一下相关的问题。

 

这个是Picture_Box部件的使用效果:

Picture_Box部件的尺寸我设置为最大,也就是窗口内的整个区域,显示的图片为居中显示,这里有源代码:http://blog.csdn.net/liuchao35758600/article/details/7023631

这里也有:http://lcui.sourceforge.net/bbs/forum.php?mod=viewthread&tid=9&extra=page%3D1

即使是图片尺寸超过窗口尺寸,也能显示,只不过是显示一小块区域,按方向键移动区域,查看其它内容。

图片的缩放功能还未添加,添加后,可以进行图片的放大和缩小的操作。

 

由于Picture_Box部件的实现,我又想做个图片查看器,这个是现在的效果图:

使用了Label部件在窗口内的左上角显示图片信息,2秒后,逐渐减少Label部件的透明度,直到0(完全透明),方向键移动浏览范围,i键调出图片信息标签(不透明)。

还不能放大和缩小图片,因为没有为PictureBox部件加入图像缩放算法。

 

以上是0.10.0 至 0.12.0版的LCUI图形库的编写过程,没做过多的说明。

由于闲着蛋疼,在sourceforge.net上搭建了一个自己的网站,并且还建了个论坛,主页地址:http://lcui.sourceforge.net/

主页的内容几乎没有,网页的源代码是从其它网站上抄下来的,自己修改,看到中意的地方就把相关网页代码复制过来,然后组合。

网页html源码通过w3c验证,无任何错误和警告,但是,css就不保证了。

 

 

第1.2.1版,目前正在编写中,修改了数据类型,什么Label部件、PictureBox部件等,都改成用LCUI_Widget结构体储存,而不是之前单独的结构体。

由于数据结构体的改变,各个函数就需要进行相应的修改,数据的处理方式也要改,代码修改量比较大,花费了几天的时间来测试、纠错、完善,终于完成了。

label部件的字体处理做了优化,频繁打开关闭字体文件来获取每个字的位图,效率很低,于是我将字体相关的数据用结构体保存,字体文件的句柄也保存在结构体中;

第一次打开字体文件,获取完字体后,不会关闭字体文件,这是为了在下一次获取字体位图时能够跳过打开文件这个步骤,节省时间。

局部刷新机制也做了修改,针对label部件的文本内容的变动,先遍历原有的文本与新的文本内容进行对比,如果有改动,就重绘这个字体,并将这个字体所在的区域记录下来,等待局部刷新之,比起之前的处理方式,效率相对来说提高了一些,至少不会因为改动几个字或者新增几个字而全部重绘。

 

窗口还是矩形窗口,想实现圆角窗口,根据圆的方程,写了个矩形圆角化的算法,可是,边缘没有线条,理论上圆角化的同时会绘制边缘线,可是实际效果并没看到边缘线,暂时先不管了,还是继续编写主要功能的代码。

 

一个图形界面,鼠标操作是少不了的,源代码比较好找:http://blog.csdn.net/liuchao35758600/article/details/7173458

获取的只有鼠标左右键的状态,不能获取鼠标滚轮的状态,以后需要这个时再找解决方法。

 

能支持鼠标了,我就加了个基本功能:拖动窗口移动,为了实现局部刷新,又写了个算法,为了解决如下图所示的问题:

根据矩形的坐标、尺寸以及移动至的新位置的坐标,得出需要刷新的两个矩形A、B,刷新这两个区域的图形就可以抹去残余图形了。

现在贴出适用于1.2.1版LCUI库的helloworld源码:

 

#include "../include/LCUI.h"

void Exit_LCUI(void *in)
{
	LCUI_Window *win_p = (LCUI_Window*)in;/* 转换数据类型 */
	Close_Window(win_p); /* 调用Close_Window函数关闭窗口 */
}

int main(int argc,char*argv[])
/* 主函数,程序的入口 */
{
	LCUI_App     this;		  /* LCUI程序 */
	LCUI_Widget  *label;	  /* 使用指向widget部件的指针 */
	LCUI_Window  *main_window;   /* 使用指向窗口的指针 */
	int           width, height; 
	/* 初始化LCUI */
	LCUI_Init(&this); 
	/* 设定默认的字体 */
	Set_Default_Font("../fonts/msyh.ttf"); 

	/* 创建一个LCUI程序窗口 */
	width  = 180; /* 窗口的宽度 */
	height = 110; /* 窗口的高度 */
	
	/* 创建一个窗口,并获取指向窗口数据的指针 */
	main_window = Create_Window(&this, width, height); 
	Set_Window_Title_Text(main_window, "Hello World!");
	/* 在窗口内创建一个label部件 */
	label = Create_Widget(main_window, LABEL);

	/* 更改label部件中的文本内容 */
	Set_Label_Text(label, "Hello World! \n测试一下!这是中国字! \nabcdefghijklmnopqrstuvwxyz\n1234567890\n!@#$%^&*()_+{}|:\"<>?");
	
	Show_Widget(label); /* 显示部件 */
	Show_Window(main_window); /* 显示窗口 */
	/* 将返回键与Exit_LCUI函数关联,当返回键被按下后,程序退出 */
	LCUI_Key_Event_Connect(main_window, KEY_ESC, Exit_LCUI, (void*)main_window);
	LCUI_Main(&this); /* 进入主循环 */
	return 0;
}

 

左边是Qt的鼠标指针,右边是我的LCUI图形库的鼠标指针。

鼠标指针的移动距离是2倍,最小移动距离是2个像素点。

 

学习机上测试效果还不怎么满意,程序从启动到显示界面耗时比较长,2秒以内,估计是字体处理拖慢了处理速度,需要进一步优化。

 

起初,将窗口移动至屏幕以外的区域时,显示会出问题,于是,将图形写入至帧缓冲时要考虑到是否超出显示范围,超出范围的就进行裁剪。

 

使用的是分别指向窗口和部件的结构体的指针,直接使用结构体会有问题,因为窗口和部件的结构体数据不是在main函数中保存的。
目前实现了LCUI_Key_Event_Connect函数,原型为:

int LCUI_Key_Event_Connect(LCUI_Window *win_p, int key_value, void (*func)(void*), void *arg);

主要功能就是将对应键值的按键产生的事件与函数关联,当按下被设定的键值对应的按键后,触发相应事件,LCUI_Main函数就会调用与该事件关联的函数,并将之前保存的参数传过去。
参数只能是一个void*类型的变量,和pthread_create函数原型类似,想要多个不同类型的参数,就需要自己写个结构体,包含这些变量,之后转换成void*型,在需关联的函数里,把void*型参数转换成原来的类型。


还有个LCUI_Widget_Event_Connect函数,原型为:

int LCUI_Widget_Event_Connect(LCUI_Widget *widget, int event, void (*func)(void*), void *arg);


这个是将某个窗口部件产生的事件与函数关联,比如:
将Exit_LCUI函数与按钮部件的clicked事件关联,当鼠标左键点击这个按钮部件并松开后,就会调用Exit_LCUI函数,关闭窗口,并退出LCUI。


功能是这样的,但相应的处理功能没完成,窗口没有添加关闭按钮,不能靠鼠标关闭窗口。


算是模拟实现了Qt的connect函数的一些功能,至少能满足现在的需求,之前为了模拟实现Qt的connect函数,发了个帖子:

 

 

 

http://topic.csdn.net/u/20120115/12/b50cac9e-5f4f-44da-adf8-6e51a87191ce.html

本来是想能够接受多个参数的,现在暂时只能用一个void*型的变量作为参数,打算让LCUI_Key_Event_Connect函数和LCUI_Widget_Event_Connect函数支持可变参数,这样,与事件关联的函数的参数就不必是void类型变量,也不必是限制为1个参数。




需要有一个字体数据队列来保存已打开的字体文件,避免重复打开同一字体文件。

要有队列处理函数,用于处理窗口显示顺序队列、部件显示顺序队列,具备新增、删除队列成员的功能。

 

经过2天的奋斗,终于完成了Button部件,能在窗口中使用按钮了。完成了LCUI_Widget_Event_Connect函数,LCUI_Main函数中,添加了对部件事件的处理。

中途修正了部分代码的逻辑错误,纠正了队列处理函数出现的错误,这个队列处理函数,用于处理程序窗口的显示顺序、窗口内部件的显示顺序以及部件事件注册、部件触发的事件,虽然每种队列的队列成员类型不一样,但大致的处理代码是一样的。
现在的按钮绘制得很简陋,但大致的处理框架已经完成,想要好一点的按钮,只需要调整一下按钮绘制函数绘制的图形内容即可。
 

程序源代码如下:

 

#include "../include/LCUI.h"

void Exit_LCUI(void *in)
{
	LCUI_Window *win_p = (LCUI_Window*)in;/* 转换数据类型 */
	Close_Window(win_p); /* 调用Close_Window函数关闭窗口 */
}

int main(int argc,char*argv[])
/* 主函数,程序的入口 */
{
	LCUI_App     this;		  /* LCUI程序 */
	LCUI_Widget	 *label, *button; /* 使用指向widget部件的指针 */
	LCUI_Window	 *main_window;   /* 使用指向窗口的指针 */
	int			 width, height; 
	/* 自定义默认字体文件位置 */
	Set_Default_Font("msyh.ttf");
	/* 初始化LCUI */
	LCUI_Init(&this);  

	/* 创建一个LCUI程序窗口 */
	width  = 240; /* 窗口的宽度 */
	height = 180; /* 窗口的高度 */
	
	/* 创建一个窗口,并获取指向窗口数据结构体的指针 */
	main_window = Create_Window(&this, width, height); 
	Set_Window_Title_Text(main_window, "Hello World!");
	/* 在窗口内创建一个label部件 */
	label = Create_Widget(main_window, LABEL);
	button = Create_Button_With_Text(main_window, "退出");
	Resize_Widget(button, 80, 30);
	/* 更改label部件中的文本内容 */
	Set_Label_Text(label, "Hello World! \n"
	"测试一下!这是中国字! \n"
	"彩色文字:红色,绿色,蓝色\n"
	"现在的LCUI创建的窗口可以使用按钮了!\n"
	"虽然按钮绘制得比较简单,\n但大致的框架已经完成!");
	/* 为label部件中指定区域的文本设定颜色 */
	Set_Label_Text_Color(label, 3, 5, 7, Value_To_Color(255,0,0)); 
	Set_Label_Text_Color(label, 3, 8, 10, Value_To_Color(0,255,0)); 
	Set_Label_Text_Color(label, 3, 11, 13, Value_To_Color(0,0,255)); 
	/* 改变部件位置 */
	Set_Widget_Position(label, 5, 5);
	Set_Widget_Position(button, Get_Window_Center_X(main_window) - button->width/2, 115);
	/* 显示部件以及窗口 */
	Show_Widget(label); 
	Show_Widget(button);
	Show_Window(main_window);
	
	/* 将返回键与Exit_LCUI函数关联,当返回键被按下后,程序推出 */
	LCUI_Key_Event_Connect(main_window, KEY_ESC, Exit_LCUI, (void*)main_window);
	/* 将按钮的左键单击事件与Exit_LCUI函数关联,当按钮被按下后,程序推出 */
	LCUI_Widget_Event_Connect(button, Clicked, Exit_LCUI, (void*)main_window);
	
	LCUI_Main(&this); /* 进入主循环 */
	return 0;
}

 

 

 

 

 

这是效果图:

label部件支持彩色文本显示,部件共有6种事件:

 

Normal部件由其它状态切换至普通状态时,才会触发该事件。
Clicked当部件被点击,并且在该部件上释放按键,该事件会被触发。
Over当鼠标覆盖在该部件上的时候,该事件被触发。
Down部件被鼠标点击,但按键未被释放,处于按住状态,该事件被触发。
Focus部件处于焦点状态,该事件被触发。(有待完善,有的时候可以和其它状态重叠)
Disable部件未被启用,该事件被触发。

 

Button部件在创建的时候就已经注册了这6个事件,与这些事件关联的是Update_Widget函数,更新按钮时,会跟据部件的状态来绘制相应的部件图形。

 

 

 

在这之前的测试,都是单个窗口的,在调试我编写的MessageBox时,发现了一个问题:程序有没有主窗口?

主窗口关闭了,子窗口也应该被关闭。

 

手动设定部件在窗口中的位置还真蛋疼,看了一下GTK和QT的程序代码,有个水平布局盒子(HBox)和垂直布局盒子(VBox),用来调整部件在窗口中的布局的,如果窗口尺寸改变,部件的布局也跟着改变,说到窗口尺寸改变,正考虑是添加窗口尺寸改变的事件处理,还是在调用相应函数改变窗口尺寸时自动处理布局,这个布局盒子,可以被其它布局盒子包含,也可以包含窗口部件,至于处理方法,想得很纠结,算了,等以后实在是需要这个时再花时间来想吧。

 

GUI的核心部分还是比较难搞,也就是事件机制,各种事件,各种返回值类型的回调函数,而这些回调函数的参数可能不定个数,不定类型,如何统一?

与事件关联的多个回调函数,是应该分别开线程一起运行,还是按顺序一个一个调用?

万一出现一个需要等待很久才能退出的回调函数,那整个程序不就卡住了吗?其它的数据处理工作都暂停?难道这就是传说中的程序未响应?

 

至于事件处理,还是一个一个的调用回调函数,要想不暂停事件处理,自己在回调函数里开线程。

 

个人认为如果每种部件都要写相应的处理函数,会变得很麻烦,代码编写量也多,这些部件应该需要有统一的处理函数,方便代码维护,也减少了代码编写量,于是,整理出了一些设计思路:

每个部件创建之初都是透明的。
label部件如果没有背景图,那么字体位图中不透明的像素点对应部件中的像素点也不透明。如果有背景图,那透明度为完全不透明,先填充背景色再混合背景图,根据背景图的布局来做相应的处理,例如:拉伸、缩放、居中、平铺,最后粘贴字体位图。除了背景图,还有一个图层,这个图层也可以显示图像,但没有背景图那样的处理,只是根据对齐方式来调整图像的粘贴位置,该图层与部件图形混合的时,如果没有背景图,就和粘贴文字位图那样。
更新该部件时的处理顺序为:
开始-》判断是否有背景图-》有就让部件不透明,否则透明-》判断是否有要显示的图像-》有的话,根据图像对齐方式来调整图像位置,之后根据图像的alpha通道而局部改变部件的alpha通道-》粘贴文字位图-》结束。


picture_box部件,和label部件一样,如果没有背景图,那么就全透明。如果有要显示的图像,那么,部件图形数据中的alpha通道会根据图像的alpha通道而局部改变,更新该部件时的处理顺序为:
开始-》判断是否有背景图-》有就让部件不透明,否则透明-》判断是否有要显示的图像-》有的话,根据图像处理模式来处理图像,之后根据图像的alpha通道而局部改变部件的alpha通道-》结束。

button部件,和上面几个部件一样,但是可以设定风格,有默认风格和自定义风格,默认风格就是根据部件的状态,绘制不同颜色的背景图,加上边框;而自定义,就是自己指定按钮在不同状态时所使用的图形,切换状态时,就会使用自己定义的按钮图形,默认对图形进行拉伸处理,如果没有,就会使用默认的按钮图形,更新该部件时的处理顺序为:
开始-》判断是否有背景图-》有就让部件不透明,否则透明-》判断按钮风格-》默认风格则绘制简陋按钮图形,自定义风格则使用自定义的图形-》判断是否有要显示的图像-》有的话,根据图像的对齐方式以及与文本的关系,来处理图像,否则不处理-》根据字体对齐方式以及与图像的关系,在对应的位置粘贴字体-》结束。

 

根据以上思路,得到这些部件处理的共同处,合并了一些代码,写了Update_Widget函数,用于更新部件,先处理部件背景图,之后根据不同的部件类型来调用相应的函数以完成部件的更新。

 

不久,又添加了部件布局功能,总共有9个布局方式:左上、中上、右上、中左、中间、中右、左下、中下、右下,并支持偏移坐标,每次改变窗口尺寸时,自动根据每个部件的布局方式以及偏移坐标得出位置,并设定,有的时候并不需要完全是这个布局,比如:某个部件要在右下角,但是,要与边缘距离5个像素点,也就是向左移动5个像素点,向上移动5个像素点,那么,就可以用偏移坐标了,当offset_x和offset_y为负数时,就会在这个以通过布局方式计算出的xy坐标中加上这个偏移值,由于是负数,就相当于减去了。

写了一个石头剪刀布的游戏,用到了label部件、picture_box部件以及button部件,也用到了事件关联和部件布局功能,具体请看图:

在测试游戏期间,纠正了事件处理、部件图形更新处理、字体位图处理、局部图形刷新处理等功能的代码所出现的问题。

编写了一个图片水平翻转函数,如上图所示(第二张),同一张图片显示成水平对称的两张图片。

此小游戏的源代码在这里:http://lcui.sourceforge.net/bbs/forum.php?mod=viewthread&tid=12&page=1&extra=#pid15

 

完善了窗口标题栏刷新功能,窗口尺寸被改变后,标题栏会被重绘,在标题栏上的关闭按钮也会被刷新。

 

几天后,加入了触屏的支持,用了tslib提供的示例程序的代码,窗口右上角也添加了关闭按钮(如下图所示)。

 

在调试触屏功能的过程中,纠正了部件图形刷新功能,因为有两种部件:一种在标题栏,一种在客户区,两种部件的图形刷新要分开处理,混在一起的话,会出现刷新问题。

完善了触屏点击处理,之前测试时,点击一下按钮,只变成了下凹的状态,没有触发部件被点击的事件,部件状态更改的函数的代码也做了相应更改,因为这问题是这函数的问题所导致的。

还发现label部件在改变尺寸并移动位置后,存在残余图形,它的更新方式就需要改动:如果有一行文本内容中的一个被字改动,就刷新该字后面一整行的字,单个字进行局部刷新不准确,会有残余图形。

添加一个变量,保存部件位置的类型,用于指示部件是在标题栏还是在用户区,绘制标题栏按钮需要用到。

 

窗口风格为无(NONE),不会绘制窗口标题栏,那个“点击圆圈中心,笔点校正”(label部件)本来是显示在中间的,可是测试时,发现圆圈(picture_box部件)移动到中央的过程中,留下了label部件残余图形,这问题暂时先放着,因此,就把label部件的位置调整到圆圈不会覆盖到的位置。

 

 

写照片查看器的时候,发现我写的这个GUI库还有蛮多问题的,为部件添加背景图后,段错误,不添加的话,label部件中的文字有时会不完整,打开图片文件后,有时会出现部件排列顺序出错,本来在底层的部件居然显示在其它部件之上,刷新也是,不仅如此,段错误居然不是必然事件了,同一个操作,重复多次,有几次不同的结果,这几个不同的结果就是刚刚所说的,这怎么找BUG?悲剧了。

 

思考了一下,感觉应该是没有进行数据保护,之前储存图形数据的结构体没有加入互斥锁的功能,导致频繁切换图形显示时直接出现段错误,因为我的这个GUI的实现用了多线程,每个窗口的图形数据是共享的,如果一个线程在使用free函数释放一个内存空间的同时,另一个线程又对这个内存空间进行读取数据的操作,那么就会出现段错误。在加了数据保护后,问题解决了,其实就是加了个变量,表示该结构体中的指针是否正在被使用,是的话,用usleep函数等待,直到没有被用为止。

 

经过几天的奋斗,问题终于解决了,修改了主循环的图形数据处理功能,之前的label部件的问题,是由于对部件更新队列的处理不当,而不是因为数据同步问题,在更新label部件中的文本时,如果开启了自动调整大小,那么就会在生成文字位图后重新计算尺寸,之后就会调用相关函数来调整部件尺寸,但是调整部件的尺寸需要再次对部件进行更新,更新队列默认是不添加重复的部件指针的,于是使用了一个函数来强制将这个部件加入至更新队列,这样问题就解决了。

 

编写了0.12版的照片查看器,在电脑端测试正常,而在学习机里,程序居然卡住了,在主循环中加上printf函数来打印信息,学习机上的测试结果表明是在部件更新队列的循环中一直循环,这个问题就是上面所说的问题,调整了部件更新队列的处理方式就能解决问题。

对按钮部件使用透明效果时,发现不起作用,于是我就用gdb的watch命令查看按钮部件的图形数据结构体中的一个变量,这个变量表示整个图形的全局透明度,经过变量跟踪,发现是实现图形裁剪、图形缩放的函数有问题,这两个函数都修改了输出的图形数据的全局透明度为255,(不透明)。

这个是0.12版的程序截图:

用到了多线程,也就是调用pthread提供的函数实现的,但是,在程序退出时发现了一个问题:在退出LCUI后,创建的线程还在运行,而这些线程还在使用之前LCUI提供的资源,由于LCUI已经退出,之前分配的资源已经无效,这就导致了段错误。

为解决这个问题,暂时添加一个函数,实现标题栏中关闭按钮的事件关联,在点击标题栏中的关闭按钮后,先撤销线程,之后再释放LCUI占用的内存资源。

之前考虑了为LCUI添加线程管理功能,使用LCUI提供的函数创建线程,在这个LCUI程序退出时,LCUI会将这个程序创建的所有线程撤销,然后再释放LCUI分配给程序的资源占用的内存空间,但是,仅仅是考虑罢了,看看以后能否用到。

 

之前的部件只有Label、PictureBox和Button三个,也该考虑添加新部件,完成了下拉菜单、列表框、文本框、滚动条和进度条,就可以写文件管理器;

进度条最容易实现,其次是滚动条,下拉菜单、列表框和文本框这三个有点难度;

经过仔细研究系统显示的下拉菜单的效果,费了1周左右的课余时间,算是实现了下拉菜单,支持多级菜单,一个按钮点击后,可以显示一个菜单,这个菜单显示的位置,由相关函数根据按钮的位置和尺寸以及菜单的尺寸来确定菜单显示位置。

显示菜单,我用了新窗口,往菜单里添加选项,就相当于往窗口里添加部件,在添加的同时,相关代码会根据窗口内的各个部件的宽度,获取最大宽度,之后,调整每个部件的宽度,使之一致,窗口的宽度和高度也会调整。

菜单的显示位置,如果超出了屏幕范围,就调整位置,使菜单内在屏幕有效范围内显示。

 

由于菜单使用的是窗口,但又与普通窗口不一样,普通窗口全部关闭后,菜单的窗口也需要关闭,否则,程序就不会退出。

我在结构体中加了个变量,用于区分这两种窗口,当普通窗口全部关闭后,自动关闭菜单窗口,释放占用的内存资源。

可是,完成这些功能后,问题又出现了: *** glibc detected *** double free or corruption (out): 0xXXXXXX

问题出在 释放 FreeType2打开字体文件后 保存的 相关数据 占用的内存资源,字体文件只打开过一次,退出LCUI时,只释放一次,为什么在添加下拉菜单后就出问题了呢?

 

我处理问题有两种方法:

一,调试程序,分析代码,查错误。

二,修改程序的主要代码,改变程序的数据处理方式,以与之前使用的方式不一样的另一种方式工作,相同的结果,可以有不同的实现方法,新的方法很有可能不会出现前一种方法所出现的同一个错误,这样就可以避免前一个方法产生的错误了。

 

有时嫌第一种方法太麻烦,太耗时间和脑力,就考虑第二种方法,思考新的程序运行方案,当然,至少要比之前采用的方案好,否则不就是写垃圾代码吗?

 

 

考虑到以后能够让程序使用自己添加自定义部件,部件的结构体需要进行修改,大致如下:

添加一个widget结构体指针和一个widget队列,前者保存父部件指针,后者保存多个子部件的指针。
添加一个void型指针,用于引用自定义部件自己的数据结构体。
 

App结构体(储存程序相关数据)也要进行修改,window不再是所有部件的容器,它属于widget类。

添加一个widget类型管理系统,能保存处理该widget的相关函数指针、字符串和所属程序,要有几个函数用于对它进行管理:。 

大部分代码需要进行修改,减少冗余代码,总代码量会减少。

对于部件的处理,先从部件库中搜索对应类型的部件,之后获取该类型相关的函数指针,运行之。

部件相关的处理大致是这样:

 

处理类型相关函数
创建并初始化Create_Widget()
图形更新Update_Widget()
尺寸改变Resize_Widget()
显示Show_Widget()
隐藏Hide_Widget()
销毁Free_Widget()

而这些函数大致的工作流程基本一样:

先进行所有部件都需要进行的相关操作,之后,从部件库中根据部件类型来搜索对应函数指针,得到函数指针后,就执行它,参数就是部件的LCUI_Widget结构体指针。

例如:

窗口部件,初始化时,Create_Widget()函数会调用相关函数对窗口的私有结构体中的数据进行初始化。

图形更新,调用Update_Widget()函数会调用相关函数对窗口的图形数据进行更新。

尺寸改变,在部件尺寸被改变后,Resize_Widget()函数会调用相关函数进行其它操作,考虑到以后窗口的视觉特效的实现,就留下这个函数,等待扩展。
显示和隐藏也是,改变LCUI_Widget结构体中的公有变量,之后调用相关函数进行后期处理,典型的例子是添加视觉特效。
销毁,先将已经为LCUI_Widget结构体中的部分公有变量分配的内存资源释放掉,然后调用相关函数将该类型的部件的私有数据占用的内存资源释放掉。

 

以上进行操作的函数,只是将函数指针和参数发送至程序的任务任务队列,让程序在主循环里处理这些任务。

 

添加了“容器”功能,可将一个部件作为另一个部件的容器,例如:将窗口作为窗口内部件的容器。

 

之前写的代码只是为了完成功能而写,有些功能可以用更好的方法来实现;

花了几天时间,鼠标事件机制经过了修改,可关联鼠标事件,鼠标事件有两个:Click和Move,前者是在鼠标按键按下或者松开后触发,后者是在鼠标移动时触发,触发后,LCUI会将与事件关联的函数指针发送至程序的任务队列中,供程序在主循环中处理,而传递给函数指针指向的函数的参数,是Mouse_Event结构体指针,里面记录了鼠标按键状态、鼠标指针覆盖到的部件的指针 以及 鼠标指针的全局位置和相对位置。


添加了函数:
int leftbutton(LCUI_Mouse_Event *event)
int rightbutton(LCUI_Mouse_Event *evemt)
返回值:
-1 不是这个键
0  释放
1  按下


添加了一个队列,用于保存已经按下的按键的键值,在按下鼠标左键后添加该键值至队列,当释放左键后,从队列中移除左键的键值,移除成功就触发鼠标左键点击事件,失败则表明左键没有按下。如果从队列中移除键值失败,则不触发click事件,因为不存在这个键值,说明之前没有按下这个键。
click事件,可得知按键的两个状态:按下、释放,也可得知按键的键值,如果这个键没有按下,就不会触发click事件。
与click事件关联的回调函数,在按键被按下时和释放时才调用,传递给它的参数是LCUI_Mouse_Event类型,回调函数可以根据这个参数得知某个按键是否按下/释放;参数中还包含当前鼠标指针覆盖到部件的指针。

 

函数的递归也用了,其实作用还蛮大的,例如:在部件内寻找与指定区域重叠的最顶层的部件,父部件中有子部件,子部件中可以有子部件,而子部件中还可以有子部件,可以变得很深,简单的用几个for循环是无法遍历完每个子部件的指针的。

 

话说回来,根据以上的描述,好像不知不觉中用了链表,虽然之前没学过链表,但是,不完全像链表,只是结构有点像。。。貌似又是N叉树,主分支里有多个分支,分支里再分支。。。

 

几天时间已经过去,代码终于修改完成了,上述的功能也已经实现,调试和分析问题花费了绝大部分时间,由于本人的头脑有点迟钝,因此浪费了过多的时间。

现在,需要考虑图形显示的优化,之前测试窗口的移动的结果还不怎么满意,有些地方还是需要改一下,比如:

图层的合成,这个还可以优化,节省更多的耗时;

局部区域刷新,刷新多个区域时,由于这些区域之间有些会有重叠的区域,而处理时,是把重叠的区域重复刷新了多次,这个没必要,浪费时间!

 

图中所示的是:将4个重叠的区域处理成5个不重叠的区域,这样就可以减少不必要的时间浪费了,具体算法目前还在思考中。。。


对于多个重叠矩形的处理,在添加矩形时就应该处理掉被多个矩形重叠的区域,具体如下:
每添加一个矩形时,先将它与队列中的各个矩形对比,对比结果有几种:
1,不重叠,继续与下个矩形对比。
2,被其中一个矩形包含,不添加该矩形至队列,直接退出函数。
3,包含其中一个矩形,将这个矩形从队列中移除,继续与下一个矩形对比。
4,与其中一个矩形有重叠,先裁剪矩形区域,然后将不重叠的部分分成两个矩形,并再次调用本函数以添加至队列,会用到函数递归调用;

 

重叠矩形区域的裁剪有点难度,需要纠结一段时间。

最近的几次测试,问题比较多,这个功能暂时先放着不用,以后再来做。
 

 

 

“好的程序是调试出来的”,这句话说得没错!

在与BUG斗争的这段时间,本人深有体会,gcc提示的那些错误倒是不需要管,我通常是先想好思路,之后用代码描述出来,盼着就是编译时gcc给的错误/警告信息,根据这些来对代码进行细节上的修改,这其实也不难应付,怕的就是程序运行过程中因逻辑错误而导致的段错误,以及意料之外的程序运行结果;

调试是少不了的,gdb我也就只会用break设定断点,back查看程序之前调用了哪些函数,step慢慢执行代码并核对运行结果,finish跳出当前函数,watch查看变量的变化。

起重大作用的还是printf函数,调试时都是在某个的位置添加printf函数,输出变量的内容,查看变量是否正常。
 

 

 



局部区域刷新功能还是需要完善,在绘制父部件中的子部件时,不仅要得出子部件在该区域内的有效显示区域,还要得出在父部件区域范围内的有效显示区域,之后,根据这两个区域得出子部件实际要裁剪的图形区域。这是为了避免子部件超出父部件的范围时,还能将子部件显示出来,之前测试按钮部件时,按钮尺寸为0x0,而按钮中的label部件却完全显示出来了。。。


有了个想法:

将结构体的定义隐藏,不能直接访问结构体的成员变量,只能通过函数库提供的函数来获取,这样,每次修改数据结构后,之前编译的程序就不会因为数据结构改变而导致程序无法正常运行了,省去了重新编译程序的麻烦,但是,这只是想法,暂时不会去实现。
 

 

最近发现一个问题,新版的png库不兼容那些使用旧版png库的程序。

没有更新png库时,我将一个可以创建png图片文件的代码整理进我的LCUI的代码里,想创建文件,直接传文件路径和图片数据结构体进去,函数就会创建一个png图片。可是呢,电脑上编译了最新的png库源码,安装之,再次使用相关函数来创建png图片文件时,创建出来的图片内容有问题,几乎是全黑的,

仔细看官方介绍时发现了他发布了两个版本,一个是1.5.10,一个是1.2.49,为兼容那些使用老版png库的程序,可以下载libpng-1.2.49。

 

测试按钮部件时,又发现一个问题:

在鼠标移到按钮上的时候,该改变谁的状态?

理论上,应该改变按钮的状态,可实际上,鼠标指针是覆盖在按钮里的label部件上,改变的是按钮里的label部件的状态。

按钮的各种状态的切换遇到了一些麻烦,应该添加一个回调函数,用于鼠标跟踪,跟踪的目的,是用于处理按钮的几种状态的切换,大致处理流程是这样的:

如果这个部件不是按钮,并且有父部件,那么就一直往上遍历这个部件的父部件,直到父级部件是按钮为止,如果这个部件的父级部件没有一个是按钮,那就算了。

 

 

按钮部件和图片盒子这两个部件已经完成了,开始添加进度条部件!

 

测试动态改变label部件的文本内容时发现一个问题:

我创建一个线程,用于动态改变label部件的文本内容,可是,由于搜索不到线程ID,确定不了程序数据结构体指针,导致往程序任务队列添加任务出错。

 

嗯,该添加线程管理功能了,我使用以下结构体:

 

typedef struct	_Thread_Queue			Thread_Queue;
typedef struct	_Thread_TreeNode		Thread_TreeNode;

/************ 线程队列 **************/
struct _Thread_Queue
{
	Thread_TreeNode **queue;	/* 储存队列成员 */
	int max_num;				/* 最大成员数量 */
	int total_num;				/* 当前成员总数 */
};
/***********************************/


/************ 线程树的结点 ***************/
struct _Thread_TreeNode
{
	Thread_TreeNode *parent;	/* 父线程结点指针 */
	pthread_t tid;				/* 父线程ID */
	Thread_Queue child;		/* 子线程队列 */
};
/***************************************/

 

 

 

 

 

 

 

用于储存这些线程ID以及关系,与其说是链表,不如说是一个“树”,根线程只有一个,也就是第一个初始化LCUI时的程序的主线程ID,线程可以有子线程,这样就会产生很多分支,每个结点的分支数量可能不一样。

 

由于LCUI还未实现多进程通信,只能靠线程ID来区分哪个操作是哪个程序的,目前打算是将程序以线程的形式运行,而不是以进程的形式运行,主线程为运行平台,其它程序就是该平台上运行的子线程,虽然能实现数据的交换,但是,一旦某个程序出现错误,整个进程就会退出。
 

在使用LCUI提供的线程相关的函数时,会对线程关系树(我觉得叫这个名字好一些)进行相应操作,比如:

创建线 程,就会先得到创建时的父线程ID,搜索这个树中的每个结点,如果有匹配的线程ID,就将这个子线程ID加入这个父线程的子线程队列中;如果没有匹配的,那就新增一个至主队列中,对于处理这种数据结构,用函数递归来处理还是比较方便的。

而撤销线程,也类似,先查找,后移除。

 

创建线程,撤销线程,阻塞等待线程退出等功能的函数,主要是调用了pthread.h头文件中声明的函数来实现的,这些函数只是顺便对线程树中的数据进行修改而已。

在使用我定义的LCUI_Thread_Exit函数时,编译器会提示它所在的函数需要有返回值,看了pthread_exit的函数声明,原来在函数声明后面加上:

 

__attribute__ ((__noreturn__));

即可让编译器给出的这条警告消失。

 

 

 

在这里只是大致的描述LCUI的设计思路,具体可以等待以后的LCUI的源码公开时,参考源代码。

 

进度条完成了,测试时,发现图像缩放函数有点小问题,小图并不能完全拉伸,拉伸后的图形边缘部分还有混乱颜色。

共两种风格:带动态效果的,和经典风格。
动态效果的,就是图中绿色的进度条,里面有个高亮区域,它会自己移动,就和win7里的进度条一样,进度条中有闪光从左至右移动。
该部件可以设定闪光的移动速度,以及它本次移动完后 到 下次从头开始移动的间隔时间。先设定进度条最大值,再设定当前值,槽中的进度条就会变成相应长度。


以后有的程序可能需要这个,在启动时,先显示动画,等待程序初始化完成。
中间一个圈,里面是字母LC,LC也就是我名字的缩写。

下面那个雪花动画,和QQ2012登录时显示的动画一样,其实就是用了QQ安装目录里的图片。

想弄成环形波浪动画,从中间的LC圆圈标志扩散到四周,可是,涉及到圆形的绘制,有点难度。。。

 

为了实现图形旋转算法,参考了一些与图形旋转相关的资料,最终参考了这里的代码:http://read.pudn.com/downloads154/sourcecode/graph/684994/%E5%9B%BE%E5%83%8F%E6%97%8B%E8%BD%AC/%E5%9B%BE%E5%83%8F%E6%97%8B%E8%BD%AC/%E6%BA%90%E4%BB%A3%E7%A0%81/MyDIPView.cpp__.htm

以这代码为基础,修改了一下。

 

既然有了图形旋转算法,那就可以做一个时钟了。

动画改成旋转中的绿圈,围绕LC标志旋转,使用了图形旋转算法,可是,这个程序在学习机端跑很卡。

用了Go桌面的时钟部件安装包里的图形素材,尺寸有点大,也就是根据时间来计算指针角度。

 

 

为了以后的源代码开放,开始研究如何用automake创建共享库。

我参考了Automake相关文章:

Automake中文手册

Linux下Makefile的automake生成全攻略

用automake建立共享库(动态链接库)Makefile

使用 GNU Libtool 创建库

最终算是完成了LCUI的Makefile创建,这样,以后发布LCUI的源代码,只要用cd命令进入源码根目录,./configure运行congfigure脚本,进行环境检测,检测通过后, 用make命令将项目源码编译, 用make install将已经编译的项目安装。

想用Automake制作自己的项目的Makefile,可以参考我的源码包里的相关文件。

 

该添加具体日期了,看看进展。

 

2012-4-25

为了这个开源项目有个好的主页,最近花了一点时间重新修改了网站的网页,网页的某些效果的实现,借用了一些网站的网页源码并稍加修改,项目的主页:http://lcui.sourceforge.net/

http://savannah.gnu.org注册了账号。

 

 

2012-4-26

在对菜单部件进行调试的过程中,发现了一些BUG:

1) 程序主循环中,每次都要处理每个部件中的矩形数据队列,不管这个队列中是否有成员,都会进行全面扫面,浪费了时间,降低了程序效率;解决方法是:添加一个变量作为标志,如果往部件添加矩形数据,这个标志值被改变,等到处理程序的每个部件的矩形数据时,就会根据这个标志的值来判断是否需要进行处理,处理完后,该标志复位。

 

2)改变部件的容器时,由于部件创建时默认容器是屏幕,部件的指针保存在程序的部件队列中,又由于相关功能的函数只在它的父部件有效时才转移它之前的容器中的数据,

因此,改变部件的容器后,程序的部件队列中残留了该部件的数据,导致了处理时出现的错误;解决方法是:在不满足父部件指针有效时,就将它在程序的部件队列中的数据移除。

 

2012-5-1

发现了几个BUG:

1)鼠标指针在label部件上左右移动时,有时会把label部件的图形数据抹去。问题原因是判断矩形重叠时出了点问题,假设有矩形A(2,5,183,18), 矩形B(170,4,12,19),如果传递给函数的参数顺序是矩形A和矩形B,结果是重叠;如果是矩形B和矩形A,结果却是不重叠。

2)部件图形的叠加有不足之处,只考虑到子部件不在父部件的有效显示范围内需进行的裁剪,由于部件可以层层嵌套,有时部件会超出容器显示范围,需要进行裁剪,如果只考虑子部件和上一级容器,没考虑到该容器的上级容器,这会造成部件图形显示出问题。

3)测试照片查看器时,效果不怎么让人满意,尤其是图像缩放的效果,看来,不能与LCUI的项目源码同时发布了,只有等下个版本的LCUI。

 

2012-5-18

好久没花时间去写代码了,临近期末,作业也多了。

有了个想法:

使用libxml库,用于实现xml文件的数据读写,懒得再为配置文件的读写功能另写代码来实现了,直接用libxml提供的API来完成。

脏矩形矩阵机制正在编写,目前的脏矩形矩阵创建功能在多次调试后已经完成。

根据屏幕尺寸,生成一个由64x64个脏矩形组成的矩阵,这个测试程序是根据生成的脏矩形矩阵的数据来在屏幕上绘制这些黑白矩形的。

初次测试时,每行矩形绘制完后都有个问题,绘制了一个坐标有问题的矩形,经过调试分析后,发现代码中的变量名用错,导致用了错误的数据生成了错误的结果。

 

接下来就是脏矩形矩阵的裁剪、尺寸调整、复制以及采样点的坐标生成、采样、对比。还需要写个函数,用于将各个矩形区域数据处理成互不重叠的,之后再进行相应屏幕区域内容更新。

修改了configure.ac,使configure脚本支持--enable-debug选项,这样,gcc编译器在编译源码时,加了-g参数,方便以后用gdb来调试代码。

具体,参考了这篇文章:http://hi.baidu.com/howlwolf/blog/item/f2d2ca13f6cb6229dc540169.html

 

经过一番调试分析后,脏矩形矩阵的尺寸已经可以调整了,中途出现的问题,发现是自己在realloc时,由于给realloc的第一个参数写错(本来是用一维指针,却写成了二维指针),第二个参数中,sizeof()里的类型名写错(Dirty_Rect写成了Dirty_Rect*)。在测试过程中,发现有的问题会造成操作系统卡住,响应巨慢!用free命令查看后,发现每次调试卡住后,可用内存越来越多,已使用的内存变少了,这应该是由于测试程序申请了过多的内存空间导致的。

 

2012-5-19

脏矩形矩阵的采样点的生成已经完成。

 

2012-5-21

需要一个定时器,用于每隔一段时间去执行某个函数。

添加了移动对象处理机制,这个移动对象指的是需要改变位置的部件,部件移动前,将部件指针添加至队列,等待函数来处理并更新部件位置。

 

2012-6-3

完善了部件嵌套的图形处理功能,处理多级部件嵌套的图形显示时,不会出现图形显示错乱的问题。

 

2012-6-4

准备做一个锁屏程序

2012-6-5

修改了锁屏程序,添加了背景图,调整了文本的位置

滑块用picturebox部件代替,考虑到要有固定的滑动轨迹,widget部件要添加max_pos和min_pos属性,用于限制部件的最大位置和最小位置,也就是限制了它的可移动范围。

 
已标记关键词 清除标记
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页