在WorldWindow定制控件是从Control类派生出来的,需要自己操纵GDI+绘制所需要的界面效果,这种自定义控件比较耗费精力,需要比较深厚的GDI+和DirectX 3D开发功底。(区别于用户控件UserControl,也叫组合控件,大多是对.NET工具箱提供的默认基础控件进行的组合和轻量级的改变,实现起来比较简单)。为了明确定制控件Control和用户控件UserControl之间的体系关系,此处给出了Control类继承层次结构(源自MSDN)以及WorldWindow类的类图:在WorldWindow中成员较多,此处仅就比较重要的成员作详细分析:
需要特别说明的是:在WorldWindow中星球球体的渲染和经纬网格的渲染时分别绘制的,星球由类World表示,经纬网格由类LatLongGrid表示。
WorldWindow定制控件的主要字段、方法和属性有:
private Device m_Device3d;//定制窗口绑定的绘制设备
private PresentParameters m_presentParams;//Direct3D绘制呈现参数
private DrawArgs drawArgs; //绘制参数
private World m_World; //定制窗口中需要绘制的星球
private Cache m_Cache;// //定制窗口中需要绘制的星球的缓冲区
private Thread m_WorkerThread;//后台工作线程
private bool showDiagnosticInfo; //是否显示诊断信息
private string _caption = "";//标题
private long lastFpsUpdateTime; //最后一次Fps的更新时间
private int frameCounter; //帧数
private float fps;//每秒渲染的帧速率
private string saveScreenShotFilePath; //保存截屏文件路径
private ImageFileFormat saveScreenShotImageFileFormat = ImageFileFormat.Bmp; //保存截屏文件格式
private bool m_WorkerThreadRunning;//判断工作线程是否正在运行
private LayerManagerButton layerManagerButton; //图层管理器按钮
private MenuBar _menuBar = new MenuBar(MenuAnchor.Top, 90);
private bool m_isRenderDisabled; // True when WW isn't active - CPU saver
private bool isMouseDragging; //是否正在用鼠标拖拽场景
private Point mouseDownStartPosition = Point.Empty; //鼠标按下时的起始位置
private bool renderWireFrame; //是否以线框模式渲染
private System.Timers.Timer m_FpsTimer = new System.Timers.Timer(250);
public void Goto( WorldWind.Net.WorldWindUri uri )转到URI连接。
public void GotoLatLon(double latitude, double longitude, double heading, double altitude, double perpendicularViewRange, double tilt)移动到指定的经纬度位置,内部实际是通过调用this.drawArgs.WorldCamera.SetPosition(latitude, longitude, heading, altitude, tilt);函数实现具体功能。
public void SaveScreenshot(string filePath)对当前场景截屏保存为图片文件。
public void OnApplicationIdle(object sender, EventArgs e)星球的消息循环,在WorldWind.cs文件的主函数static void Main(string[] args)中被调用。其内部调用了WorldWindow类的Render()函数。
private static bool IsAppStillIdle判断程序是否空闲,如果控件返回真。
protected override void OnPaint(PaintEventArgs e),任何继承自Control类的定制控件均需要重载Control类的函数,当窗口需要重新绘制时被调用。
private Device m_Device3d;//绘制场景所需要的DirectX 3D设备对象
private DrawArgs drawArgs;//场景绘制时指定的绘制参数
private World m_World;// WorldWindow定制控件中表示星球球体的对象。星球球体的渲染和经纬网格的渲染时分别绘制的,星球由类World表示,经纬网格由类LatLongGrid表示
private Cache m_Cache;//用于缓存高程数据和纹理数据的本地缓存对象
private Thread m_WorkerThread; //后台工作线程,使用多线程的方式实现高程数据和纹理数据的远程下载,并及时更新应用程序的图形界面(GUI)
public World CurrentWorld;//公开的属性CurrentWorld的Set属性除了完成将星球对象的引用保存在m_World成员中外,还实例化了运动相机MomentumCamera和星球球体的经纬网格LatLongGrid,并且将经纬网格对象添加到星球对象的可渲染对象列表中
public void Render();//该方法首先判断星球对象m_World是否为空、后台工作线程WorkerThread是否为空,并依次作相应的处理;其次,在开始绘制之前调用绘制参数对象drawArgs的相机对象WorldCamera所保留的Update ()方法this.drawArgs.WorldCamera.Update(m_Device3d)更新DirectX 3D设备对象m_Device3d,然后开启绘制标识,设置绘制模式、调用星球对象m_World的渲染函数m_World.Render(this.drawArgs)完成星球主体的渲染、绘制十字光标his.DrawCrossHairs();、统计绘制帧速率、渲染各个自定义的窗体挂件m_RootWidget.Render(drawArgs)和m_NewRootWidget.Render(drawArgs)、实时响应用户的截屏操作SaveScreenShot()、在渲染窗口的右上角绘制位置信息RenderPositionInfo()、完成菜单条的渲染_menuBar.Render(drawArgs)、线型图形的渲染m_FpsGraph.Render(drawArgs)、输出星球对象m_World的屏幕消息字符串
重载鼠标滑轮滚动消息protected override void OnMouseWheel(MouseEventArgs e)
重载键盘按键按下消息protected override void OnKeyDown(KeyEventArgs e)
重载键盘按键弹起消息protected override void OnKeyUp(KeyEventArgs e)
重载键盘按键消息protected override void OnKeyPress(KeyPressEventArgs e)
重载预处理消息方法,在消息循环过程中在消息被丢弃之前事先处理键盘和输入信息public override bool PreProcessMessage(ref Message msg)
紧接着,定义两个比较重要的方法体public bool HandleKeyDown(KeyEventArgs e)和public bool HandleKeyUp(KeyEventArgs e)分别用来处理按键按下和弹起时的消息,其中按键按下的处理方法HandleKeyDown里面基本上包含了WorldWind的全部按键操作控制实现代码
重载了鼠标按下、弹起、双击、移动和离开等五个消息的处理方法:
protected override void OnMouseDown(MouseEventArgs e)
protected override void OnMouseDoubleClick(MouseEventArgs e)
protected override void OnMouseUp(MouseEventArgs e)
protected override void OnMouseMove(MouseEventArgs e)
protected override void OnMouseLeave(EventArgs e)
其中,鼠标按下的消息处理方法中,先记录下当前按下鼠标的位置,然后先让各个自定义的窗体挂件m_RootWidget.OnMouseDown(e)和m_NewRootWidget.OnMouseDown(e)处理,再让菜单条this._menuBar.OnMouseDown(e)作出处理,以便对用户的操作作出实时响应,最后标识用户是左键按下还是右键按下。
鼠标按下的消息处理方法比较重要,里面除了实现先让自定义的窗体挂件和菜单条处理鼠标按下的消息外,还实简单地现了通过光线追踪(Ray Tracing)技术进行拾取检测等操作:
this.drawArgs.WorldCamera.PickingRayIntersection(DrawArgs.LastMousePosition.X,DrawArgs.LastMousePosition.Y,out targetLatitude,out targetLongitude),最后在调用方法his.drawArgs.WorldCamera.PointGoto(targetLatitude, targetLongitude)使星球飞行到用户通过右键单击所想要去的地方去漫游(左键双击表示放大,右键双击表示放大)。
接下来的定义了方法private void InitializeGraphics()和private void OnDeviceReset(object sender, EventArgs e),分别初始化使用DirectX 3D用来渲染图形时的设备状态和当DirectX 3D设备对象m_Device3d需要重置时调用的方法,用户切换渲染窗口和调整渲染窗口都会使设备对象失效,当再次使用时需要重置。不熟悉这一部分内容的读者可以查阅DirectX 3D三维图形绘制的基础知识,此处不再赘述。
最后一个重要的方法是private void WorkerThreadFunc(),即前面提到的后台工作线程的绑定方法体。其内部调用了查询性能计数器方法和星球对象的更新函数:
PerformanceTimer.QueryPerformanceCounter(ref startTicks);
m_World.Update(this.drawArgs);
其中,后者使用多线程的方式实现高程数据和纹理数据的本地调用或远程下载,完成用户所请求的区域的地形数据的绘制和纹理映射,并及时更新应用程序的图形界面(GUI)。详细过程请参照m_World.Update(this.drawArgs)内部实现。