转自:http://www.hailongchang.org/index.php/archives/201
用C++做TerraExplorer开发(四)——实现HUD Layer(1)
前面的链接:
版权所有, 转载请注明:
使用TerraExplorer开发项目,有一个重量级的标杆横亘在我们这些开发者的面前,那就是,很难不让用户拿我们的软件系统与Google Earth做比较,而结果我想大多数情况下都是我们自惭形秽了。从本期开始,我将介绍一个重要话题,使得我们起码在界面上不输Google Earth,甚至超越Google Earth。这就是HUD Layer。
对于HUD Layer的具体概念,大家可以看一下,其实TE里右上角的那个罗盘就是一个典型的HUD。
用过Google Earth的人都知道,它的界面在窗口底部会有一段字符串用来显示鼠标移动的坐标,靠右一点还有一个Google的logo。实际上,实现类似HUD Layer的方法有很多种, 我会在接下来的文章中,介绍三种用来实现HUD Layer的方法。第一次,我们要中规中矩一些,先来看看Manual里面是怎么说的。
也许有人注意到过,在手册_ITerraExplorerEvents下有一个函数:
HRESULT OnDrawHUD()
看看里面的介绍,这个函数每一帧都会被触发,具体时间是在当前帧渲染完成后和HUD Layer被绘制之前。在这个函数内,我们可以使用IRender5::SetHUDLayer()来进行HUD Layer的绘制和更新。 上面的信息讲得很明白,那就是要绘制HUD Layer,我们必须响应OnDrawHUD动作,并且在该动作内要用SetHUDLayer这个函数进行必要的绘制工作。大致原理就是如此,看来关键还在于如何绘制HUD Layer。
SetHUDLayer只有三个参数,第一个是IStream指针,剩下的两个保留,我们只需要简单传个0就可以了。而对于IStream,手册上说” Meta DC saved as a stream. Passing Null clears the current HUD layer.” 也就是说这个Stream保存的是一个Meta DC, 也就是GDI中的元文件(Meta File),如果传递一个NULL,那么所有的HUD Layer都会被清除掉。这里要提醒大家的是,我们所有的绘制动作必须保存在MetaFile中才能显示为正确的HUD Layer。 我在刚接触HUD Layer时就曾经试图将所有动作绘制到位图中,可惜最终无法正确显示。
Meta File是以二进制形式存储的GDI命令的集合。也就是我们所有的绘制动作都以指令的形式保存在元文件中。创建元文件我们使用的是GDI函数CreateEnhMetaFile。需要为该函数传递一个源设备描述表的句柄(HDC)和一个指向矩形范围RECT的指针,具体用法请参阅MSDN。
创建了MetaFile之后,我们会得到该元文件的句柄,然后就可以使用改句柄来做一些绘制工作了,别如BitBlt,TextOut等等GDI提供的绘制函数。但是要注意的是,MetaFile是独立于硬件的,因此创建时传递的RECT指针,我们要在RECT内部使用毫米,而绝大部分GDI函数使用的是像素,因此还需要事先更改一下GDI的映射模式。当然如果习惯于使用像素,比如像我,那大可以不用更改,但必须保证有两个同等大小的RECT,一个用于创建MetaFile,另外一个用于做常规的GDI操作。
有了元文件之后,我们可以使用OleCreatePictureIndirect创建一个IPicutre对象,然后在内存中分配Stream,接着使用IPersistStream的Save方法保存为一个IStream对象。有了IStream的对象,最后一步就是调用SetHUDLayer完成绘制了。
上面的整个过程也许有点复杂,但其实基本步骤就两个:
1: 创建MetaFile保存绘制
2: 将MetaFile转换为IStream。
下面看看核心的代码,我需要在窗口底部居中的位置显示一个字符串”Hello World”,并且在底部靠右显示一幅含有logo的PNG图片,我们知道PNG图片带Alpha通道,因此还可以显示出类似Google logo那样镂空的图片来。