关于内存DC的使用(一)

在windows下搞图形界面的设计难免要使用到内存DC,将所有的绘制工作先绘制在内存DC上,然活一次性拷贝到屏幕DC上。可消除一些图形的闪烁问题,当然还有其他的用处,比如简单游戏中的象素碰撞检测等等。
1,创建兼容DC
CDC m_MenDC;

CDC m_MenDC2;

CDC m_MenDCMap;

CDC m_MenDCMask;
//这个是一个要创建的兼容位图

CBitmap m_Bitmap1;
m_MenDC.CreateCompatibleDC(GetDC());

m_MenDCMap.CreateCompatibleDC(GetDC());

m_MenDCMask.CreateCompatibleDC(GetDC());

m_MenDC2.CreateCompatibleDC(GetDC());
m_Bitmap1.CreateCompatibleBitmap(GetDC(),1024,768);
2,为兼容DC选入一张位图,或兼容位图。
m_MenDC.SelectObject(m_hbmpBK);

m_MenDC2.SelectObject(m_Bitmap1);
3,好了,现在可以在兼容DC上做绘制工作了,
m_btnReturn.DrawButton(m_MenDC); //绘制按钮

m_btnUp.DrawButton(m_MenDC);

m_btnLeft.DrawButton(m_MenDC);

m_btnRight.DrawButton(m_MenDC);

m_btnDown.DrawButton(m_MenDC);

m_btnBack.DrawButton(m_MenDC);

m_btnAnew.DrawButton(m_MenDC);

m_btnFull.DrawButton(m_MenDC);
m_MenDC2.BitBlt(0,0,1024,768,&m_MenDC,0,0,SRCCOPY);

// 绘制背景 DrawPath(&m_MenDCMap); //绘制路径

if(m_bFullView)

{
SetStretchBltMode(m_MenDC2.GetSafeHdc(),HALFTONE );

m_MenDC2.StretchBlt(m_rtMap.left,m_rtMap.top,m_rtMap.Width(),m_rtM ap.Height(), &m_MenDCMap,0,0,m_iMapWidth,m_iMapHeight,SRCCOPY); //绘制平面图,查看全图

}

else

{

m_MenDC2.BitBlt(m_rtMap.left,m_rtMap.top,m_rtMap.Width(),m_rtMap.Height(), &m_MenDCMap,m_xOffset,m_yOffset,SRCCOPY); //绘制平面图

}
4,绘制结果的显示,将这些东西拷到屏幕DC上,所谓的双缓冲就是把所有的绘制工作都做在一个内存DC上。 // 最后一次拷到屏幕DC上,只能有一次 dc.BitBlt(0,0,1024,768,&m_MenDC2,0,0,SRCCOPY); 这里我所强调的“一次”;是不要同时将几个DC的内容都拷到屏幕DC上,这样没有起到双缓冲的效果。如果你搞了很多个内存DC,想把这些东西都显示出来,那你应该先把这多个内存DC的内容同时拷到另外一个内存DC上,再把这个内存DC的内容拷到屏幕DC上。
5,注意的一点:
m_hbmpBK=HBITMAP)::LoadImageAfxGetInstanceHandle),path+"Bk4.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
m_MenDC.SelectObject(m_hbmpBK);
m_MenDC.绘制工作。
在内存DC上所做的绘制工作实际上都是绘制在你选入的那张位图m_hbmpBK 上。就是说m_hbmpBK 句柄所指向的位图内容改变了,如果以后你在其他的地方在用这个m_hbmpBK 的时候,你会发现这已经不是你想象中的Bk4.bmp了,^_^。如果想再用原图,必须的重新从硬盘导入。
还要注意的一点是,一个位图句柄不可同时选入到两个内存DC中。内存DC对于选入的位图具有排他性,如果你m_MenDC.SelectObject(m_hbmpBK);而m_hbmpBK已经被其他的内存DC选用了话,那刚才这句m_MenDC.SelectObject(m_hbmpBK);是毫无意义的。
在网上看到了许多的关于装位图装载到离屏表面的文章,但是都是使用了WIN32函数,虽然有效,但不是很通用。如果我们要装载其它的格式的文件使用不了WIN32函数,不就无能为力了吗?于是我想直接操作文件,直接读取位图文件的数据到离屏表面。网上还是有这样的文章的,但是很少,并且没有过多的说明。其实,装载文件到离屏表面也很简单,不需要什么算法知识就可以完成的。主要的方法是(以256色位图为准):

1、将位图文件的颜色表和图像数据读入内存。

2、创建前后表面,离屏表面。

3、根据颜色表创建调色板。

4、将位图数据传输到离屏表面。

5、利用后表面的BLTFAST将图像输出到后表面。

6、翻转表面,将图像显示到屏幕。

先声明几个变量:

BITMAPFILEHEADER bmfh; //位图文件头

BITMAPINFOHEADER bmih; //位图信息头

RGBQUAD rgb[256]; //颜色表

首先,我了解了一下位图文件的结构,其实也是很简单的。

1、文件头: typedef struct tagBITMAPFILEHEADER

{

UINT bfType; //文件标志

DWORD bfSize; //文件大小

UINT bfReserved1,bfReserved2;

DWORD bfOffBits; //数据偏移

}BITMAPFILEHEADER;

bfType:是位图的文件标志,为"BM"。当你从文件中读出放到一个变量中时,它是"MB",即0x4d42。在内存中,存放是高位在前的。所以在磁盘上是"BM",读到内存就为"MB"了,不信?你一个字节一个字节的读,将这两个字节存到两个字符型变量char ch1,ch2;中,你会发现ch1==’B’,ch2==’M’。

bfSize:位图文件的大小。等于位图文件头+信息头+颜色表+位数据。以字节为单位即:
sizeof(bmfh)+sizeof(bmih)+sizeof(RGBQUAD)*256+bmih.biSizeImage

bfOffBits:位图数据偏移.如果你想直接读取位图的数据.使用 fseek(fil_ptr,bmfh.bfOffBits,SEEK_SET); 这样,可以直接将文件指针指向位图数据开始的地方。

2、信息头: typedef struct tagBITMAPINFOHEADER

{

DWORD biSize; //信息头大小。40字节
LONG biWidth,biHeight; //位图实际宽、高度。

WORD biPlanes; //

WORD biBitCount; //位图每像素的位数。

DWORD biCompression; //

DWORD biSizeImage; //位数据的大小(字节)

LONG biXPelsPerMeter,biYPelsPerMeter; //

DWORD biClrUsed; //

DWORD biClrImprotant; //

}BITMAPINFOHEADER;

介绍一些重要的部分:

biSize:信息头大小,即此结构的大小。为40字节。即sizeof(bmih)。

biWidth,biHeight:位图的像素宽、高。

biBitCount:位图每像素的位数。指定了这个位图文件的颜色深度。在这个例子中是8。

biSizeImage:位图数据区的大小。 这里有需要注意的:位图数据每行是以4字节增充的,如果是一个256色的位图。它的像素占一字节。如果图像宽度为80像素,则图像每行为80字节,如果图像宽度为79像素,则磁盘上的位图文件仍然是80字节。(78,77像素每行也为80字节)。图像每行76,75,74,73像素,则它在文件中占76个字节。因此在从磁盘读出数据时要知道每行的字节数,这里我使用 bytperlin=bmih.biSizeImage/bmih.biHeight; 从磁盘的位图文件中读取数据。 首先是位图文件头,信息头。使用如下语句便可: fread(&bmfh,sizeof(bmfh),1,fil_ptr);

fread(&bmih,sizeof(bmih),1,fil_ptr);

这时两个结构bmfh,bmih就存放了我们需要的一些关于位图的信息了。 再读入颜色表:

RGBQUAD* prgb; prgb=(RGBQUAD*)malloc(sizeof(RGBQUAD)*bmih.biBitCount); //注意,在使用指针之前一定要给它分配内存空间,否则的话程序会退出。

fread(prgb,sizeof(RGBQUAD),1<<BMIH.BIBITCOUNT,FIL_PTR); 这样,我们需要的颜色值放入了动态分配的内存空间了。但是在DD程序中设置调色板的话需要的是PALETTEENTRY结构数组,因此我们要得到PALETTEENTRY结构数据的值。

PALETTEENTRY*ppal= (PALETTEENTRY*)
malloc(sizeof(PALETTEENTRY)*bmih.biBitCount);
for(int i=0;i<(1<<BMIH.BIBITCOUNT);I++)

{
ppal[i].peRed=prgb[i].rgbRed;

ppal[i].peGreen=prgb[i].rgbGreen;

ppal[i].peBlue=prgb[i].rgbBlue;

ppal[i].peFlag=0;

} 之后我们就可以通过这个结构来取得LPDIRECTDRAWPALETTE接口指针。之后为前表面设置调色板(在后面的程序中)。 最后是读入位图位数据了,每个像素是1个字节。位图图像数据的大小在信息头中已经给定了:bmih.biSizeImage 这个大小包括实际的图像数据和为了每行达到4字节而扩充的字节数。

我们用如下方法读入: BYTE* pbuffer=(BYTE*)malloc(sizeof(BYTE)*bmih.biSizeImage); //注意,一定要分配内存空间,否则程序会退出!!!!!!!

fread(pbuffer,sizeof(BYTE),bmih.biSizeImage,fil_ptr); 这样,位图中的数据我们全部读入了。 文件头,信息头只是给我们提供了一个参数,方便我们读入位图数据。 颜色表用于设置调色板。(后面有程序)。 最后是将内存中的图像数据传入到离屏表面中了:

DDSURFACEDESC2 ddsd; ZeroMemory(&ddsd,sizeof(ddsd)); ddsd.dwSize=sizeof(ddsd); //注意一定要初始化这个结构,否则程序会退出!!

lpDDS_Off->Lock(NULL,&ddsd,DDLOCK_WAIT|DDLOCK_WRITEONLY,NULL);

(BYTE*)lpSurf=(BYTE*)ddsd.lpSurface; //取离屏表面指针。

//将内存数据复制到离屏表面中

lpDDS_Off->UnLock(NULL); 这样,全部的事情完成了,想要输出离屏表面的数据到后表面,使用后表面的BltFast 就可以了。 下面是具体的程序片段:

//给指定的表面设置调色板

int Create_Palette(LPDIRECTDRAW7 lpDD,LPDIRECTDRAWSURFACE7& lpDDS_Front,LPDIRECTDRAWPALETTE& lpDDP,char* filnam)

{ RGBQUAD rgbquad[256];

PALETTEENTRY pal[256];

if(filnam=="")

return 0; //从文件中读入调色板索引
FILE* fil_ptr;

fil_ptr=fopen(filnam,"rb");

if(fil_ptr==NULL)

return 0;

fseek(fil_ptr,sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER),SEEK_SET);

fread(rgbquad,sizeof(RGBQUAD),256,fil_ptr);

fclose(filnam);

for(int i=0;i<256;i++)

{

pal[i].peBlue=rgbquad[i].rgbBlue;

pal[i].peGreen=rgbquad[i].rgbGreen;

pal[i].peRed=rgbquad[i].rgbRed;

pal[i].peFlags=PC_NOCOLLAPSE;

}

lpDD->CreatePalette(DDPCAPS_8BIT,pal,&lpDDP,NULL);

lpDDS_Front->SetPalette(lpDDP); return 1;

} //装载位图数据,在这个函数中调用创建离屏表面的函数

(Init_Off) int LoadData_8(char* filnam)

{

if(filnam=="") return 0;

FILE* fil_ptr;

fil_ptr=fopen(filnam,"rb");

if(fil_ptr==NULL)

return 0;

BITMAPFILEHEADER bmfh;

BITMAPINFOHEADER bmih;

fread(&bmfh,sizeof(bmfh),1,fil_ptr);

fread(&bmih,sizeof(bmih),1,fil_ptr);

Init_Off(lpDD,lpDDS_Off,bmih.biWidth,bmih.biHeight);

int bytperlin=bmih.biSizeImage/bmih.biHeight; //位图数据每行字节数 BYTE* pbuffer=NULL; //////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
pbuffer=(BYTE*)malloc(sizeof(BYTE)*bmih.biSizeImage);

fseek(fil_ptr,bmfh.bfOffBits,SEEK_SET);

fread(pbuffer,sizeof(BYTE),bmih.biSizeImage,fil_ptr);

fclose(fil_ptr);

DDSURFACEDESC2 ddsd;

memset(&ddsd,0,sizeof(ddsd));

ddsd.dwSize=sizeof(ddsd);

lpDDS_Off->Lock(NULL,&ddsd,DDLOCK_WAIT|DDLOCK_WRITEONLY,NULL);

BYTE* pSurf=(BYTE*)ddsd.lpSurface;

BYTE* pData=pbuffer;

pData+=bmih.biSizeImage; //指针已经出了数据区,回一位,就是数据区.

for(int row=0;row<BMIH.BIHEIGHT;ROW++)

{

pData-=bytperlin;

memcpy(pSurf,pData,bmih.biWidth);

pSurf+=ddsd.lPitch; //表面从第一行依次向后定位

}

lpDDS_Off->Unlock(NULL);

return 1;

}

//给表面设置透明色

int SetColorKey8()

{

DDSURFACEDESC2 ddsd;

ZeroMemory(&ddsd,sizeof(ddsd));

ddsd.dwSize=sizeof(ddsd);

lpDDS_Off->Lock(NULL,&ddsd,DDLOCK_WAIT|DDLOCK_READONLY,NULL);
大家都知道内存DC是一种提高绘图效率,避免屏幕闪烁的好办法,几乎所有的绘图都必须使用到内存DC,可是在MSDN里一切都不是那么明显. 为了为以后的游戏制作更加顺利,我研究了一下内存DC的标准用法,然后贴在这里也是一个备忘. 其实所有的内存DC的原理都像下图所示:
Class CMyView

{

public:

//内存DC(主DC,临时DC(用来装载位图))

CDC *m_PrimaryMemDC; CDC *m_TempMemDC;

//位图对象

CBitmap *m_bgMap;

//背景

CBitmap *m_bitMap;

//人物

CBitmap *m_tempMap;

//临时

CRect m_rect;

}
然后便是在类的构造函数中建立对象,代码一目了然,我就不多说了.
CMemDCTestView::CMemDCTestView()

{

m_TempMemDC = new CDC;

m_PrimaryMemDC = new CDC;

m_bitMap = new CBitmap;

m_tempMap = new CBitmap;

m_bgMap = new CBitmap;

}
之后在OnCreate函数中初始化这些对象(注意不是构造函数,因为构造的时候还没有DC),OnCrate函数默认在View类中是没有的,所以你要 手动加入.
CMyView::OnCrate(……)

{

CClientDC dc(this); //创建与内存兼容的DC

m_PrimaryMemDC->CreateCompatibleDC(&dc);

m_TempMemDC->CreateCompatibleDC(&dc);

//创建兼容位图

m_tempMap->CreateCompatibleBitmap(&dc,1024,768);

//装载位图

HBITMAP bitmap = (HBITMAP)::LoadImage(NULL,"pic\\bgmap.bmp", IMAGE_BITMAP,1024,768, LR_LOADFROMFILE);

m_bgMap->Attach(bitmap);

bitmap = (HBITMAP)::LoadImage(NULL,"pic\\AppExit_enu.bmp",IMAGE_BITMAP,61,67, LR_LOADFROMFILE); m_bitMap->Attach(bitmap);

//载入位图

m_PrimaryMemDC->SelectObject(m_tempMap);

return 0;

}
我们看到这里主要做的工作是创建和窗口DC相兼容的内存DC,然后是载入位图. 提醒一点就是创建兼容位图,然后把他载入内存DC中这个过程的理解,我是这么理解的只有你的DC中已经有了一张图片,然后才能够往上面贴图片.就像我们画 画的时候先要有一张白纸,然后才能画画. 还是拿画画做例子,再解释另外一个东西,使用SelectObject函数可以看成是换画纸,而BitBlt函数我们就可以看作是把其他画纸上的东西复印 到现在的这个画纸上,所以你的DC必须先有画纸才能用BitBlt.这也就是为什么我刚才试了很多次都不能BitBlt成功的原因. 再往下看,当然到了最关键的OnDraw函数了。
void CMemDCTestView::OnDraw(CDC* pDC)

{

CMemDCTestDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);
GetClientRect(&m_rect);

BITMAP map;

m_bgMap->GetBitmap(&map);

m_TempMemDC->SelectObject(m_bgMap);

m_PrimaryMemDC->BitBlt(0,0,map.bmWidth,map.bmHeight,m_TempMemDC,0,0,SRCCOPY);

m_bitMap->GetBitmap(&map);

m_TempMemDC->SelectObject(m_bitMap);

m_PrimaryMemDC->BitBlt(0,0,map.bmWidth,map.bmHeight,m_TempMemDC,0,0,SRCCOPY);

pDC->BitBlt(0,0,m_rect.Width(),m_rect.Height),m_PrimaryMemDC,0,0,SRCCOPY);

}
在这个函数里我们首先定义了一个数据结构BITMAP通过CBitmap::GetBitmap来得到图像的相关信息(主要是长和宽),之后我们把要载入 的第一幅位图载入暂存DC1(m_TempMemDC)中,之后再把它贴到暂存DC2(m_PrimaryMemDC)上。(注意这时的暂存DC2已经有 一个默认的位图选入了,否则贴图操作不能成功)下面的步骤的重复的贴第二张图片,以此类推,最后把暂存DC2的东西贴到窗口DC上,我猜这个窗口DC一定 在程序创建的时候已经选入一张位图了(不然无法BitBlt)。 好了,这就是内存DC绘图的标准用法,只是繁琐,并不是很难,大家用的时候一定要细心再细心,特别是对于选择默认位图的部分,很容易遗漏。
void CPaintView::OnDraw(CDC* pDC)

{

CMyPaintDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc); //下面是在内存区进行绘图,然后一起输出到屏幕 CDC cdcMem; cdcMem.CreateCompatibleDC(pDC); //产生内存区

CBitmap bitmap; RECT rect; GetClientRect(&rect); int x=pDC->GetDeviceCaps(HORZRES);
//得到设备以像素为单位的宽度

int y=pDC->GetDeviceCaps(VERTRES);
//得到设备以像素为单位的高度

bitmap.CreateCompatibleBitmap(pDC,rect.right,rect.bottom);
//产生位图内存区

cdcMem.SelectObject(&bitmap);

cdcMem.FillSolidRect(0,0,rect.right,rect.bottom,RGB(255,255,0));//填充背景色

pDoc->ShowAllShapes(&cdcMem);//画图,是我自已定义的画图函数

pDC->BitBlt(0,0,x,y,&cdcMem,0,0,SRCCOPY);//输出到屏幕

}
位图是一种图形化对象,用于在设备环境里创建、绘制、操纵和接收图片。从[开始按钮]上的小Winodws标志到标题栏上的[关闭]按钮,位图在Windows里无处不在。位图可以看作是一种由像素数组构成的图片,这些像素可以在屏幕上进行绘制。和所有图片一样,位图有自己的高度和宽度。也提供方法来判断位图使用什么颜色。最后,位图也是一个描述位图中每个像素的位(bits)数组。
习惯上,Windows下的位图被划分成两种类型:设备相关位图(DDBs)和设备无关位图(DIBs)。DDBs是一种和具体DC的特性有紧密关系的位图,不容易在有不同特性的DC上绘制。DIBs则相反,它与具体设备无关,因此需要携带足够的信息以便于在任何设备上准确的绘制。

DirectUI的初步分析

来源:DirectUI的初步分析

最近由于项目的需要学习了一下DirectUI方面的东西,主要借鉴的是一个国外程序员写的代码(见引用一),看了后发现它更多的是探讨一种实现的可能性和思路,和实际应用还是有距离的,不过其实现还是很有意思的。在写此小结的时候又发现国内一个程序员将这个代码部分移植到WINCE下的代码(见引用二),因为平台的差异性要完全开发一个WINCE下的实际代码还是需要时间的。
由于本人GUI开发做得少,工作中有关这方面的东西主要是提供思路和方法,学习DirectUI的主要目的是为了更新知识学习思路,文章中难免出现错误。

一、核心
1、CWindowWnd: 窗口对象类(窗口实例对象父类)
2、CDialogBuilder: 创建控件类,分析脚本并用递归方式(_Parse函数)创建所有控件实例
3、CPaintManagerUI: 窗口消息及图形绘制管理器类
4、CGUIRenderEngineUI: 图形渲染引擎类,在离屏DC中生成最终显示的图形,可根据需要扩展多种图形效果显示。
5、INotifyUI: 事件通知抽象类
6、IMessageFilterUI: 消息过滤抽象类

二、控件
CControlUI: 控件管理抽象父类,父类INotifyUI
1、button
CButtonUI: 按钮控件
COptionUI: 选择按钮控件

2、combox
CSingleLinePickUI:
CDropDownUI: 下拉控件,父类另有CContainerUI和IListOwnerUI

3、decoration
CTitleShadowUI: 阴影效果
CListHeaderShadowUI
CSeparatorLineUI
CFadedLineUI

4、edit
CSingleLineEditUI: 单行编辑框控件
CMultiLineEditUI: 多行编辑框控件

5、label
CLabelPanelUI: 可设置背景色和文字色的静态标签控件
CGreyTextHeaderUI

6、list
第一种:
CListUI: 列表控件,包含以下几个子控件
(1)CListHeaderItemUI: 列表头
(2)CListExpandElementUI: 列表项
第二种:用法不明
CListHeaderUI: 列表头
CListElementUI: 列表项,父类另有IListItemUI
CListLabelElementUI: 列表项,父类CListElementUI
CListTextElementUI: 列表项
CListFooterUI: 列表尾

7、panel
CTextPanelUI: 父类CLabelPanelUI
CTaskPanelUI:
CNavigatorPanelUI: 导航面板,父类另有IListOwnerUI,包含CNavigatorButtonUI子控件
CSearchTitlePanelUI:
CImagePanelUI: 图片显示
CWarningPanelUI: 警告提示,父类CTextPanelUI
CPaddingPanelUI: 填充栏

8、tab
CTabFolderUI: 父类另有CContainerUI和IListOwnerUI
CTabPageUI: 父类另有CContainerUI

9、toolbar
CToolbarUI: 工具栏,包含以下几个子控件
(1)CToolButtonUI: 图形按钮
(2)CToolSeparatorUI: 分隔符
(3)CToolGripperUI:  gripper

10、title
CToolbarTitlePanelUI:

11、statusbar
CStatusbarUI: 状态栏,父类另有CContainerUI

12、anim
CAnimJobUI: 动画显示类

13、ActiveX
CActiveXUI:

三、容器:
CContainerUI: 容器类,父类CControlUI和IContainerUI。可以认为容器是特殊的控件(见上面控件类关于父类的说明),其目的之一是具有容器特性的控件可以容纳其它控件,这样可以方便的实现控件的叠加;目的之二实际的窗口只有一个,对于叠加的控件必须要进行层次管理才能正确绘图和事件分发。另外可参见引用三
1、画布: CCanvasUI(父类CContainerUI),可绘制背景色、画线、贴图
CWindowCanvasUI: 父类CCanvasUI
CControlCanvasUI: 父类CCanvasUI
CWhiteCanvasUI: 父类CCanvasUI
CDialogCanvasUI: 父类CCanvasUI
CTabFolderCanvasUI: 父类CCanvasUI
2、布局: 管理不同层次的控件
CDialogLayoutUI: 父类CContainerUI
CVerticalLayoutUI: 父类CContainerUI
CHorizontalLayoutUI: 父类CContainerUI
CTileLayoutUI: 父类CContainerUI

四、通用
1、script
CMarkup
CMarkupNode

2、language
CUIUtility

3、multi-thread
CriticalSection
AutoCriticalSection
CMutex
CAutoMutex
CEvent
CAutoEvent
CManualEvent

五、主要数据成员
1、CPaintManagerUI
CControlUI* m_pRoot: 如果控件是叠加的则存放最下层的控件对象,否则存放第一个创建的控件对象
CControlUI* m_pFocus: 存放获得焦点的控件对象指针
CControlUI* m_pEventHover: 存放当前有鼠标移进移出事件的控件对象指针
CControlUI* m_pEventClick: 存放当前有点击事件的控件对象指针
CControlUI* m_pEventKey: 存放当前有按键事件的控件对象指针
CStdPtrArray m_aNotifiers: 记录所有需要事件通知的窗口,根据窗口名称调用相应的消息处理函数
CStdPtrArray m_aNameHash: 保存控件对象指针hash表(用控件名称生成hash值)
CStdPtrArray m_aPostPaint: panel的fade效果
CStdPtrArray m_aMessageFilters: 保存需要进行消息过滤的控件或功能(如动画类)
CStdPtrArray m_aDelayedCleanup:
CStdPtrArray m_aPreMessages: 预处理消息
HWND m_hWndPaint: 控件布局窗口句柄
HDC m_hDcPaint: 控件布局窗口设备DC
HDC m_hDcOffscreen: 离屏内存DC
HBITMAP m_hbmpOffscreen: 离屏内存DC相关联HBITMAP

2、CControlUI
CPaintManagerUI* m_pManager: 窗口消息或绘图管理器
CControlUI* m_pParent: 逻辑上的父窗口(控件)对象指针
CStdString m_sName: 控件标识
CStdString m_sText: 控件显示标题或显示脚本字符串
CStdString m_sToolTip: 控件的Tip信息

3、CContainerUI
CStdPtrArray m_items: 同一层的控件对象或控件对象的子对象,例如canvas上放置的按钮、combox由edit和list两个子对象组成,其它还有tab等。具体见CDropDownUI、CTabFolderUI、CNavigatorPanelUI三个类定义

4、CDialogLayoutUI
CStdValArray m_aModes: 用于存放在Layout上绝对坐标转成相对坐标(CDialogLayoutUI::RecalcArea)的控件对象(指针、大小、模式),目的是否为了让布局上的控件随布局变化而变化,能够正确绘图???

六、控件属性
待完成

七、脚本例子
<Dialog>
  <WindowCanvas pos=/"0,0,600,800/">
  <DialogLayout pos=/"0,0,600,800/">
    <Button pos=/"390, 30, 490, 58/" text=/"OK/" name=/"ok/"/>
  </DialogLayout>
  </WindowCanvas>
</Dialog>

八、绘图及事件处理
1、绘图
STEP01. CWindowWnd::__WndProc: 主窗口程序
STEP02. pThis->HandleMessage: pThis是布局窗口对象指针,并与布局窗口绑定(SetWindowLongPtr)
STEP03. m_pm.MessageHandler: m_pm为CPaintManagerUI唯一实例对象
STEP04. CPaintManagerUI::MessageHandler: 处理WM_PAINT
STEP05. m_pRoot->DoPaint: m_pRoot为最下层的控件对象(在本例中为CWindowCanvasUI控件,对应脚本中的WindowCanvas)
STEP06. CCanvasUI::DoPaint: 往画布上绘制背景色、边角弧形、水印等。
STEP07. CContainerUI::DoPaint: 在最下层具有容器特性的控件(CWindowCanvasUI控件)上画容器内所有控件(控件实例对象保存在m_items中)
STEP08. pControl->DoPaint: pControl为控件对象实例之一,利用多态性来调用不同控件的绘图方法
STEP09. CButtonUI::DoPaint: 按钮(对应脚本中Button)绘图方法,有下面两种方法
i)文字方法: CGUIRenderEngineUI::DPaintButton
ii)图片方法: CGUIRenderEngineUI::DoPaintBitmap
STEP10. 新一轮消息循环

2、事件
STEP01. CWindowWnd::__WndProc:
STEP02. pThis->HandleMessage:
STEP03. m_pm.MessageHandler:
STEP04. CPaintManagerUI::MessageHandler: 处理WM_LBUTTONDOWN
STEP05. CPaintManagerUI::FindControl: 根据鼠标坐标查找相应控件对象
STEP06. m_pRoot->FindControl:
STEP07. CContainerUI::FindControl: 在最下层具有容器特性的控件(CWindowCanvasUI控件)容器内查找相应控件对象
STEP08. CControlUI::FindControl: 在m_items中查找相对应的控件对象
STEP09. pControl->Event: pControl为控件对象实例之一,利用多态性来调用不同控件的事件方法
STEP10. CPaintManagerUI::MessageHandler: 处理WM_LBUTTONUP
STEP11. m_pEventClick->Event: 利用多态性来调用不同控件的事件方法(m_pEventClick说明见"主要数据成员")
STEP12. CButtonUI::Event: 按钮(对应脚本中Button)事件方法
STEP13. CButtonUI::Activate:
STEP14. m_pManager->SendNotify: 传递控件对象指针和触发事件(文本方式)
STEP15. CPaintManagerUI::SendNotify: 注意以下两点实现是完成控制和业务分离的关键
i)利用重载特性调用注册的监听对象(窗口)的消息处理函数Notify(监听对象保存在m_aNotifiers中)
for( int i = 0; i < m_aNotifiers.GetSize(); ++i )
{
    static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
}
ii)布局窗口CStartPageWnd的消息处理,宏定义展开后实际就是重载的Notify函数
DIRECT_BEGIN_NOTIFYMAP(CStartPageWnd)
    PROCESS_BUTTON_CLICK(_T("ok"),OnOk)
    。。。
DIRECT_END_NOTIFYMAP(CStandardPageWnd)
STEP16. CStartPageWnd::OnOk: 控件消息处理函数,此处可以加入具体的事务逻辑处理
STEP17. 新一轮消息循环

3、消息定义(文本)
"click"、"changed"、"link"、"browse"、"itemclick"、"itemselect"、"dropdown"、"itemactivate"、"headerdragging"、"headerclick"、"headerdragged"、"itemexpand"、"itemcollapse"、"windowinit"、"killfocus"、"setfocus"、"timer"

九、疑问
1、Edit、Combox的下拉列表部分、ScrollBar、Tooltip控件是创建的实际窗口,这个与DirectUI思路还是有差别的
2、实例中有创建一个不进行消息处理的窗口(CFrameWindowWnd),然后又创建了一个窗口(CStandardPageWnd)用于具体的控件布局。但是我用一个窗口也能实现,原作者为什么这样还不清楚
3、控件是用文本形式来做标识的,消息类型是文本形式,是否改成数值型比较好

十、引用
引用一: http://www.viksoe.dk/code/windowless1.htm
引用二: http://directui.googlecode.com/
引用三: http://www.cnblogs.com/cutepig/archive/2010/06/14/1758204.html

POPCAPFramework(SexyApp)对中文的支持

注意项目编译选项:使用MBCS和_USE_WIDE_STRING 设置了这两个选项,所有默认的std::string都是本地编码的多字节字符串,SexyString是utf16编码的宽字符字符串 SexyApp内部对中文的处理是针对wstring的,因此可以认为基本没有问题,只需要修改显示相关代码和字符串的入口代码即可,举几个例子 1. 字体 默认的SysFont用的是ANSI_CHARSET,会导致无论创建什么中文字体都是宋体,需要将ANSI_CHARSET改为GB2312_CHARSET,这样设置的字体才有效 2. 中文输入 比如EditWidget中,处理paste的时候,调用的是 SexyString aBaseString = StringToSexyString(mWidgetManager->mApp->GetClipboard()); 这是有问题,需要将GetClipboard的结果用MultiByteToWideString转换为wstring 此外,EditWidget对于默认输入的字符有一个uTheChar<=range的判断,而range的最大值是255,对于中文显然不适用,去掉即可。 3. 其它类似问题,按照以上方案修改即可

编译popcapframework

来源:编译popcapframework

一、下载popcapframework:

http://downloads.sourceforge.net/project/popcapframework/PopCap%20Games%20Framework%20v1.3/PopCap%20Framework/PopCap_Framework_v1.3.zip

二、解压到G:/PopCap_Framework_v1.3

三、打开G:/PopCap_Framework_v1.3/osframework/source/SexyAppFramework/SexyAppBase VS2005.vcproj

四、修改EditWidget.cpp 

(((unsigned int)theChar >= (unsigned int)(L’?)) && ((unsigned int)theChar <= (unsigned int)(L’’))) ||

(L’?) 改成(L’?’)

五、Debug – Incremental 改成Debug

六、下载http://resource.gameres.com/dx9csdk.rar 解压到G:/PopCap_Framework_v1.3/dx9csdk

七、Tools ->Options ->Projects and Solutions ->Include files 加入G:/PopCap_Framework_v1.3/dx9csdk/Include

    Tools ->Options ->Projects and Solutions ->Library files 加入G:/PopCap_Framework_v1.3/dx9csdk/Lib/x86

八、编译,在G:/PopCap_Framework_v1.3/osframework/source/SexyAppFramework/Debug 目录下生成 SexyAppFramework.lib 改名成SexyAppFramework_D.lib

九、将Debug 模式改成Release 模式,重新编译得到Release 版的SexyAppFramework.lib

十、将SexyAppFramework.lib 和SexyAppFramework_D.lib 剪切到G:/PopCap_Framework_v1.3/osframework/source/SexyAppFramework

注意:Release 编译选项用 /MT,Debug 用 /MTD。引用此库的程序也必须依此设置。

对DUILIB重构的一些看法

来源:对DUILIB重构的一些看法

经过一段时间的思考和对游戏引擎HGE和OGRE的学习,我觉得duilib要参照游戏开发的方法来做些重构:在功能层次划分上大体可分为主框架、窗口管理器、事件管理器、渲染处理、逻辑处理、资源管理、时间系统、脚本、GUI,以下是具体内容
1、主框架:负责创建唯一的物理窗口、处理系统消息处理生成输入事件,同时它也是一个主循环应该具有起停功能
2、窗口管理器:所有的窗口都是virtual的,涉及到窗口间的切换以及通信的时候能够很方便的进行管理,同样定义窗口切换的动画时也是很好扩展的
3、事件管理器:对输入事件、逻辑事件进行统一管理。输入事件一般就是由系统消息转换而成的,而逻辑事件一般是逻辑处理产生的。事件通知机制采用类似C#的托管机制,由事件管理器分发到注册的监听者(窗口和控件)
4、渲染处理
(1)做成一种帧回调的方式,这样能够根据实际情况调整刷新的频率。
(2)帧可以拆分成逻辑处理帧和界面渲染帧在不同线程中处理实现完全分离,逻辑帧是对数据及渲染对象状态改变等进行处理,而渲染帧是进行实际的渲染,在处理完一帧结束的时候同步一下。
(3)简单数据的处理可借鉴MFC的DDX机制。比如音量调节,在逻辑处理修改音量这个数据后会触发视图的刷新。
5、逻辑处理:处理数据和事件状态切换、通知渲染处理
(1)简单的逻辑处理直接在事件处理函数中完成
(2)复杂的逻辑处理考虑用有限状态机的方式
(3)逻辑处理和渲染处理是需要有一个同步机制的
6、资源管理器:对图片、文字、声音进行统一管理
(1)在PopCap_Framework这个小游戏开发框架中有一个ResourceGen工具,可以生成资源配置文件包含的只是ID及路径,这样有一个好处就是可以动态的改变图片及文字
(2)资源预读线程。这在资源够多主框架刚起来时对效率的提升还是有帮助的。
(3)高速缓存:防止过多的资源占用大量的内存
7、脚本:如果界面和逻辑分离得比较彻底,那么逻辑处理用脚本来做应该是比较容易了。
8、GUI:duilib现有的控件容器部分其实只是GUI,已经做得比较完善了。不同的是由渲染线程来处理这些图形单元,如果能进一步抽象到点、线、面就更好了。
9、渲染器:这一部分最好抽象出来,一个好处就是具体的渲染模块如GDI、GDI+、D3D、OPENGL可以用插件的方式注册。
10、时间系统:这部分到底需不需要还没想明白。
11、日志记录
12、调试:比如说内存泄漏监控、回收、异常处理机制等等
以上这些纯粹是个人的一些想法,因为游戏不管是逻辑处理还是渲染处理都是要比普通的程序要更复杂的,这些想法实际开发中是否有效还是个未知数。另外强力推荐一本游戏开发的书籍:

<Game Coding Complete 3rd Edition>,建议直接看英文版,我是受益良多吧。