`
ZeaLoVe
  • 浏览: 91717 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

qtdemo浅析

 
阅读更多

    之所以写这篇文章,是因为之前做了一些关于修改QtDemo的工作,而且之前的备忘里承诺过,虽然不一定很多人记得或者看过,但说到就要做到,哪怕说给自己听的事情。

 

   不多扯了直接进入主题,qtdmemo想必大部分接触过Qt的都见过,他是一个用Qt实现的看起来非常炫的说明文档,里面列了很多基本的QT GUI程序的样例图,还可以点击运行样例,或者弹出代码说明文档。我主要看中的是它飞进飞出的文字想过还有动态的图标列表。当然如果一个正常的说明文档用CHM的是最好的,但如果你追求效果一定希望做一个和QTDemo一样的,本文就是简单的介绍一下该程序源码的结构,然后提供一些我已经完成的修改工作的总结和源码,希望对看的同志们有一定的帮助。

 

   本文的目标是帮助你修改qtdemo,去除其中你不想要的动画和图片,然后扩展功能,实现多级目录(原程序只支持3级),去除QHelpEngine的依赖,实现按需载入自定义的说明文字和图示。(文末附带部分修改的源代码)

 

   只要你安装了QT4,那么你一定有这个工程的源码,就在QT安装路径下,如:

C:\Qt\4.5.0\demos\qtdemo 如果你已经配置了QT ,那么在QT Assistant下面就有qtdemo的可执行文件,就可以看看效果了。

 

   现在简单的说明下源码的文件分类和基本作用,由于每个CPP文件都对应一个H文件,所以我就去除后缀,只列出名字:

 

Colors//主要是一些配置信息,比如是否显示动画效果,还有字体大小之类的

Demoitem//Item名字的基类

Demoitemanimation

Demoscene

Dockitem

Guide

Guideline

Guidecircle

Headingitem

Imageitem

Itemcircleanimation

Letteritem

Score//动画效果实现的类,文字飞入飞出

Scanitem

Textbutton//定义显示在左边的按钮的一些属性,如果需要修改按钮的外观或者位置,在这里修改

以上的文件,除了需要了解Colors里面的配置信息外,其他部分均可直接跳过。如果需要修改,则可以根据目标,还有名字去查看。因为我发现他们和我要达到的目标无联系,故没有深入研究这些细节部分的内容。下面的类才是工作的重点,将会一一细讲。

 

Mainwindow

这个是主窗体类,其实就是整个程序UI的核心部分,所以前面说的去除不想要的两个图片,或者修改背景图(我觉得背景可以不修改,修改的话需要调整Button大小,增加工作量),或者程序图标,都在这个类中,只要根据资源图片的名字,查找代码里面出现这些名字的位置,很容易发现和去掉的,这里就不麻烦大家,我列出在mainwindow.h的下面有两个ImageItem*的对象,就是图标,去掉即可。其他部分,相信只要熟悉GUI编程的,很容易定制,就不多说了。

 

Menumanager

这个是核心部分,因为这里管理这整个程序MENU的结构以及相应的按钮和部件的初始化。涉及的操作有读取XML配置文件,生成显示按钮的列表,根据每个按钮所处位置的不同,定义不一样的值这个值会导致点击每个按钮时候的响应事件不同。同时menumanager是一个单例模式的典型应用(请看改类开头的注释)

首先看BUTTON_TYPE的定义,明显看出它仅仅支持三个层次,所以只定义到Menu2,第一件要做的事情就是扩展这个定义,一定要保持连续性,这样方便处理。改成这样:

 

 

    
enum BUTTON_TYPE {ROOT, MENU1, MENU2, MENU3, MENU4, MENU5, MENU6, QUIT, FULLSCREEN, UP, DOWN, BACK};
 

 

然后是XML的读取,是readXmlDocumentmenumanager初始化的时候就读入了,然后后面会根据读入的内容遍历节点生成按钮的列表(即目录结构)。要扩展目录结构,首先要扩展XML文档的定义,根据我的定义,层次节点的命名最好统一,虽然不统一其实不影响Menu的生成,但会造成录入时候的混乱。每一个节点要包含filenamename属性,image属性可有可无(不一定每个部分都需要图示)。定义完XML扩展规则后就可以修改对XML的处理过程了,因为原始只处理到第三层(包括ROOT)。首先看按钮生产的函数,有三种,一个是createRootMenu生成根目录,createSubMenu生成非叶子(即目录节点),createLeafMenu生成叶子节点。需要做修改就是createSubMenu因为原来仅仅支持三层,Root-Menu-Leaf 每个Menu没有层次的区别和概念,但我们的需求是扩展Menu支持多层,那么Menu就需要有层次的概念了,所以加入一个类型为BUTTON_TYPE的参数区分Menu的层次。之所以先提这些是因为XML和这几个函数很有关系,在menumanager::init函数里负责初始化工作,这里对XML的节点进行遍历,载入节点的属性和信息(readInfoAboutExample函数的工作),然后根据节点的层次不同生成不同的按钮。同时根据XML节点Name的属性对按钮进行命名,按钮显示的内容默认就是name的值,我这里采用的是广度优先的遍历方法遍历所有XML节点,依次生成列表,代码如下:

 

 

typedef struct
{
	BUTTON_TYPE type;
	QDomElement element;
}NodeRecode; 


// Create first level menu:
QDomElement rootElement = this->contentsDoc->documentElement();
this->createRootMenu(rootElement);
    
QDomNode node;
NodeRecode recode;
recode.type = MENU1;
QList<NodeRecode> nodeList;
node = rootElement.firstChild();
while(!node.isNull())//将根节点的子节点放入队列,一遍遍历
{	

	recode.element = node.toElement();
	nodeList.push_back(recode);
	node =node.nextSibling();
}
	
//遍历所有节点
while(!nodeList.isEmpty())
{
	recode= nodeList.takeFirst();//队列中取出一个节点
	if(recode.element.attribute("name")==NULL)//无名字节点剔除不做任何处理,包括子节点
	{
		continue;
	}
	this->readInfoAboutExample(recode.element);
	if( info[recode.element.attribute("name")]["isLeaf"] != "true")//非叶子
	{
		recode.type = BUTTON_TYPE((int)recode.type + 1);// BUTTON_TYPE中的MENU必须按顺序摆放
		this->createSubMenu(recode.element,recode.type);//创建目录按钮
		node = recode.element.firstChild();
		while(!node.isNull())
		{
			recode.element = node.toElement();
			nodeList.push_back(recode);
			node = node.nextSibling();
		}			
	}
	else//叶子
	{		
		this->createLeafMenu(recode.element);//创建叶子按钮
	}
}
 

 

其他一些边角的按钮我就不细说了,修改方法也很简单,找到也很简单,根据名字搜索即可找到按钮生成的代码了。

 

这个类另一个重点在于,既然扩展了层次的数量,那么原来的BACK按钮将不适用了(之前的处理方式是,不管你点到什么程度点BACK一律回到ROOT目录)所以需要修改以支持多级目录,我的方法是采用一个结构记录回退层次以及按钮的ID,处理逻辑是,每次点击按钮,就做适当的记录,然后点Back后,弹出最近的那个记录,然后恢复到那个点(ItemSelect就是按钮点击事件的处理逻辑,参数是按钮的BUTTON_TYPE和名字)

这个是回退记录的结构和处理的代码,以及包含了层次节点按钮事件处理的代码,以及BACK按钮事件的代码:

 

回退结构定义:

 

//后进先出队列记录 返回位置
typedef struct 
{
	BUTTON_TYPE type;
	QString ID;
}BackPoint;
QList<BackPoint> backList;//记录回退地址
void setBackList( BackPoint );

 

 

层次按钮事件处理部分代码(itemSelected的一个Case):

注:以下的所有this->score都是处理文字飞入飞出的,可以无视,保留之即可。

 

case MENU1:
	type=MENU1;
	goto here;
case MENU2:
	type=MENU2;
	goto here;
case MENU3:
	type=MENU3;
	goto here;
case MENU4:
	type=MENU4;
	goto here;
case MENU5:
	type=MENU5;
	goto here;
case MENU6://如果需要添加层次 依上添加即可BUTTON_TYPE中 MENU N 必须按序排列否则会出错
	type=MENU6;
	goto here;
here:
	if(info[menuName]["isLeaf"] == "true")//叶子节点处理方法
	{
		   //对于Back按钮的操作,叶子节点不记录会跳路径
	          this->score->queueMovie(this->currentInfo + " -out", Score::NEW_ANIMATION_ONLY);
                  this->score->queueMovie(this->currentInfo + " -buttons -out", Score::NEW_ANIMATION_ONLY);
                  // book-keeping:
                  this->currentMenuCode = type;
                  this->currentInfo = menuName;
                  // in / shake:
                  this->score->queueMovie("upndown -shake");
                  this->score->queueMovie("back -shake");
                  this->score->queueMovie(this->currentMenu + " -shake");
                  this->score->queueMovie(this->currentInfo, Score::NEW_ANIMATION_ONLY);
                  this->score->queueMovie(this->currentInfo + " -buttons", Score::NEW_ANIMATION_ONLY);
                  if (!Colors::noTicker){
                        this->score->queueMovie("ticker -out", Score::NEW_ANIMATION_ONLY);
                        this->window->switchTimerOnOff(false);
                }
	}
	else//非叶子节点(即目录节点)的操作
	{
		//目录节点需要记录回跳路径
	         this->score->queueMovie(this->currentMenu + " -out", Score::FROM_START, Score::LOCK_ITEMS);
                 this->score->queueMovie(this->currentMenuButtons + " -out", Score::FROM_START, Score::LOCK_ITEMS);
                  this->score->queueMovie(this->currentInfo + " -out");
                  // book-keeping:
                  this->currentMenuCode = type;
                  this->currentCategory = menuName;

		  tmp.type = type;
		  tmp.ID = menuName;
		  this->setBackList(tmp);

                  this->currentMenu = menuName + " -menu1";
                  this->currentInfo = menuName + " -info";
                  // in:
                 this->score->queueMovie("upndown -shake");
                 this->score->queueMovie("back -in");
                 this->score->queueMovie(this->currentMenu, Score::FROM_START, Score::UNLOCK_ITEMS);
                 this->score->queueMovie(this->currentInfo);
                 if (!Colors::noTicker)
                       this->ticker->useGuideTt();
	}
	break;
 

 

 BACK按钮处理代码(ItemSelected的一个Case)

 

 

case BACK:{
            // out:
            this->score->queueMovie(this->currentInfo + " -out", Score::NEW_ANIMATION_ONLY);
            this->score->queueMovie(this->currentInfo + " -buttons -out", Score::NEW_ANIMATION_ONLY);
            // book-keeping:
            this->currentMenuCode --;
            this->currentMenuButtons = this->currentCategory + " -buttons";
            this->currentInfo = this->currentCategory + " -info";
            // in / shake:
            this->score->queueMovie("upndown -shake");
            this->score->queueMovie(this->currentMenu + " -shake");
            this->score->queueMovie(this->currentInfo, Score::NEW_ANIMATION_ONLY);
            this->score->queueMovie(this->currentInfo + " -buttons", Score::NEW_ANIMATION_ONLY);
            if (!Colors::noTicker)
			{
                this->ticker->doIntroTransitions = false;
            //    this->tickerInAnim->startDelay = 500;
                this->score->queueMovie("ticker", Score::NEW_ANIMATION_ONLY);
                this->window->switchTimerOnOff(true);
            }

            if(this->backList.size() >=1 )//如果链表中节点多于两个
			{
				this->backList.removeLast();//剔除当前节点
			}
			if(this->backList.isEmpty() )//无节点,则跳到跟节点
			{
				itemSelected(ROOT,Colors::rootMenuName);
			}			
			else//还有节点剩余,即为需要跳到的点
			{		
				BackPoint point=this->backList.takeLast();
				itemSelected(point.type,point.ID);
			}

        break; }
 

上面这些基本把该做的大部分事情都弄了,剩下的问题就是如何显示每个部分的文字和图示了,由于qtdemo涉及到qhelpEngine我们其实不需要使用的,所以要去掉它,方法是,屏蔽头文件,然后慢慢找出依赖的语句,一句句删干净就好了。很暴力,但也很简单。当然删完,就要自己把要显示的数据呈现出来了。以下两个类做的事情差不多,都是读取文字和图片,然后显示,但作用的位置不一样,Exmpleconten作用于叶子节点,Menucontent作用于目录节点。所以合并起来说。

Examplecontent

Memucontent

只有几个函数比较中要,分别是loadDescription(载入文字描述,显示出来),getImage(读取图片),getDescription(读取文字描述),createConten(设置显示的方式布局和字体之类的)。

我们采用的方法比较简单,就是从文件中读取跟按钮名字同名的文件,文字就从 ./data/(按钮名).txt文件读入,图片优先从XML节点image属性指定的路径读入,如果没有再从./image/(按钮名).png读入。然后显示。所以比较简单,仅仅支持单一图片,所以如果你需要进一步扩展可能需要对程序了解更多才行。代码如下:

 

Main  不用理会

 

这个程序的使用十分简单,仅需根据你要编辑的内容结构,生成一个 ./xml/example.xml 文件,然后在./data/ 下根据按钮名字写好描述性的文字,规则如前所述。执行程序即可,扩展起来也很方便,但其缺点是,节点名字不能重合,除非他们使用相同的描述文字或者图示。不让后面的节点信息会覆盖前面的,我的处理方法是,给节点加” ”空格以区分以及尽量不让节点重名。(这部分内容需要参考MenuManager::readInfoAboutExample方法,他根据节点名从XML中读入属性,所以如何实现可以重名的结构我也做过尝试但失败了)

 

如果你没有耐心看以上部分,我也整理了个可用的代码,提供大家学习交流。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics