Flight Tracker - 用于绘制飞行路线的应用程序






3.24/5 (16投票s)
.NET Compact Framework 图形和 XML 功能的有趣探索。
引言
这个程序与其说是一个实际有用的应用程序,不如说是对 Compact Framework 的一种探索。我的第一个目标是在摆脱通常用来演示功能和技术的“Hello World”类型应用程序的同时获得一些乐趣。在我十几岁的时候,我有一个朋友痴迷于航空公司的航班时刻表。他会花无数个小时用手绘制航空公司航线的地图。它们没有多少实用性(除非你是航空业分析师),但它们看起来很酷。
我决定创建一个 PocketPC 解决方案来绘制航空公司地图。“Flight Tracker”,正如我给它命名的那样,将包括输入和维护单个机场、航空公司和航班数据的能力。我将地图区域限制在北美,并且只输入了西南航空公司航班时刻表的一部分数据。一个全面的数据库将花费数月的数据输入时间,或者需要购买商业航班数据库。这个项目将需要使用基本的 .NET 窗体控件,以及 Graphics
接口和 .NET XML 类来进行数据存储。
管理航班数据
第一个任务是编译和管理绘制航班地图所需的数据。我创建了一个类 FlightData.vb
来包含所有与数据相关的结构、数组和方法。我更喜欢使用结构和结构数组来保存程序数据,因为这样可以很容易地在代码中看到你在做什么。这种安排在内存和处理器使用方面也非常高效。我想将数据存储在开放的、跨平台的格式中,所以我决定尝试 .NET 中的 XML 类。
System.XML.XmlDocument
类使得加载和保存 XML 文件内容变得容易。你只需实例化 XmlDocument
对象,然后调用其 Load 或 Save 方法并指定所需的文件名。数据存储在 XmlDocument
中的 XmlNode
和 XmlAttribute
对象中。每个 XmlNode
包含子 XmlNode
集合和 XmlAttribute
集合。我在每个结构定义中都包含了一个方法,用于从 XmlNode
对象加载新结构成员的值,以及一个方法用于返回一个包含成员值的 XmlNode
。这里我包含了最简单的结构来演示这种配置。
Public Structure AirlineStruct
Dim Airline As String
Public Sub New(ByRef XmlNode As Xml.XmlNode)
Dim xAttribute As Xml.XmlAttribute
For Each xAttribute In XmlNode.Attributes
Select Case xAttribute.Name
Case "Airline"
Airline = xAttribute.Value
End Select
Next
End Sub
Public Function GetXmlNode(ByRef Doc As Xml.XmlDocument) _
As Xml.XmlNode
Dim Node As Xml.XmlNode = Doc.CreateNode( _
Xml.XmlNodeType.Element, "AIRLINE", "")
Node.Attributes.Append(GetXmlAttribute(Doc, "Airline", Airline))
Return Node
End Function
End Structure
XmlNode
和 XmlAttribute
类的一个有趣的限制是,它们只能由 XmlDocument
对象实例化。在我的情况下,这有点烦人,因为我不得不将 XmlDocument
的指针传递给我的结构的 GetXmlNode
方法,仅仅是为了这个目的。
有了这些方法,保存类的 D数据就包括创建一个 XmlDocument
,并为类数组中存储的每个结构添加一个 XmlNode
。当所有 XmlNodes 都添加完毕后,只需调用 XmlDocument
的 Save
方法。
Public Sub Save()
Dim xDoc As New Xml.XmlDocument
Try
xDoc.AppendChild(xDoc.CreateElement("FlightData"))
Dim Flight As FlightStruct
For Each Flight In Flights
xDoc.FirstChild.AppendChild(Flight.GetXmlNode(xDoc))
Next
Dim Airline As AirlineStruct
For Each Airline In Airlines
xDoc.FirstChild.AppendChild(Airline.GetXmlNode(xDoc))
Next
Dim Airport As AirportStruct
For Each Airport In Airports
xDoc.FirstChild.AppendChild(Airport.GetXmlNode(xDoc))
Next
xDoc.Save("\Program Files\Flight Tracker\FlightData.xml")
HasUnsavedChanges = False
Catch ex As Exception
MsgBox("Error saving XML data file.", _
MsgBoxStyle.Exclamation, "File Error")
End Try
End Sub
请注意,通常情况下,XmlDocument
的所有其他节点都位于文档的第一个子节点内,当你组装新文件的内容时,你需要自己创建并添加它。
加载很容易通过从文件加载 XmlDocument
来完成,然后依次遍历子节点集合,将每个节点加载到适当的结构类型中,并将该结构添加到其对应的数组中。
Public Sub Load()
Dim xDoc As New Xml.XmlDocument
Try
Dim Flight As FlightStruct
Dim Airline As AirlineStruct
Dim Airport As AirportStruct
ReDim Flights(-1)
ReDim Airlines(-1)
ReDim Airports(-1)
xDoc.Load("\Program Files\Flight Tracker\FlightData.xml")
Dim Root As Xml.XmlNode = xDoc.FirstChild
Dim Child As Xml.XmlNode
For Each Child In Root.ChildNodes
Select Case Child.Name
Case "FLIGHT"
Flight = New FlightStruct(Child)
ReDim Preserve Flights(UBound(Flights) + 1)
Flights(UBound(Flights)) = Flight
Case "AIRLINE"
Airline = New AirlineStruct(Child)
ReDim Preserve Airlines(UBound(Airlines) + 1)
Airlines(UBound(Airlines)) = Airline
Case "AIRPORT"
Airport = New AirportStruct(Child)
ReDim Preserve Airports(UBound(Airports) + 1)
Airports(UBound(Airports)) = Airport
End Select
Next
HasUnsavedChanges = False
Catch ex As Exception
MsgBox("Error loading XML data file.", _
MsgBoxStyle.Exclamation, "File Error")
End Try
End Sub
程序的数据管理部分的其余部分是一个带有 ListView
控件的选项卡式对话框,用于航班、航空公司和机场,以及用于创建、编辑、删除和排序列表元素的按钮和菜单。
绘制航班地图
我首先需要的是地图的背景图像。我在 CorelDraw 8 的剪贴画 CD 上找到了一幅简单的北美大陆轮廓图(我认为它是免费的,但我找不到任何文档来验证这一点)。我更改了地图的配色方案,并将其转换为 240 x 274 的 JPG。一切都很顺利,直到我尝试在我的 iPAQ 3765(我工作时用于开发的设备)上运行程序。程序开始加载……然后以 ArgumentException
退出!我尝试在运行时动态加载图像,但仍然收到相同的错误消息。在花费了几个小时在互联网上搜索答案后,我终于在 OpenNetCF.org 论坛上找到了以下帖子:16bpp Bitmap allways an error - Terry Mohre。帖子中提到,“16bpp 总是会引发‘ArgumentException’并无法实例化 Bitmap 类”。我检查了我的 JPG,果然,它是 16 位的。我将其转换为 24 位,然后就可以顺利加载了。
接下来,当我开始填充地图的 Paint 事件处理程序时,我意识到 .NET 桌面中所有熟悉的 Graphics
功能在 Compact Framework 中都被精简成了很小的子集。我再次在我的网络浏览器中查找,看看有什么可用的解决方案可以替代 GDI+ 的双缓冲和抗锯齿功能。我在 MSDN 上很快找到了一个关于双缓冲技术的可靠示例:How to Create a Microsoft .NET Compact Framework-based Image Button - Alex Yakhnin。该技术的第一部分是覆盖 .NET Control
类中的 OnPaintBackground
事件处理程序。由于这个事件处理程序是一个受保护的类成员,你必须创建一个派生类来进行重写。这个新类将在程序中需要双缓冲绘图表面的任何地方使用。
Public Class MapCanvas
Inherits System.Windows.Forms.Control
Protected Overrides Sub OnPaintBackground(ByVal e As _
System.Windows.Forms.PaintEventArgs)
'do nothing
End Sub
End Class
虽然这个类在编译后的程序中工作正常,但我必须说它在开发环境中造成了混乱。也许这与 Compact Framework 不支持通常用于此类事情的 UserControl
类的事实有关?因此,我不得不不断删除和重新创建我的窗体中的画布对象。有时我甚至需要重启 Visual Studio 才能使其识别 MapCanvas 是有效对象。
双缓冲技术的第二部分是先在屏幕外位图上绘制所有内容,然后将位图复制到 MapCanvas
对象上。这由机场位置选择对话框的 Paint 处理程序进行了说明。
Private Sub Canvas_Paint(ByVal sender As Object, ByVal e _
As System.Windows.Forms.PaintEventArgs) Handles Canvas.Paint
Dim bg As Graphics = Graphics.FromImage(bmBuffer)
bg.DrawImage(pbMap.Image, 0, 0)
bg.DrawLine(Pen, 0, YPos, 240, YPos)
bg.DrawLine(Pen, XPos, 0, XPos, 272)
bg.Dispose()
e.Graphics.DrawImage(bmBuffer, 0, 0)
End Sub
通过在窗体级别维护图形对象,可以让 Paint 处理程序运行得更快……
Private Brush As New SolidBrush(Color.Blue)
Private Pen As New Pen(Color.Blue)
Private bmBuffer As Bitmap
至于抗锯齿,PocketPC 的 GDI 似乎没有任何支持。我敢肯定可能有一些第三方库可以提供与桌面版 GDI+ 相当的功能,但我没有花时间去寻找它们。
一旦我的双缓冲代码到位,我就处理了航班数据类的内容,将其填充到一个 RectangleF
对象数组中,其中每个对象代表一条单独的航线。一个相应的整数数组用于存储航向信息,该信息决定了矩形的哪个角是起点和终点。为了在地图上动画显示航线,使用了一个 Timer
来在连续的屏幕刷新中将矩形的大小从 0 递增到 100%。计时器和矩形是一种快速粗糙的方法,但它们的效果相当好。
使用程序
该程序易于使用。只需将 .NET CF 可执行文件复制到你的 PocketPC(你必须将其放在“\Program Files\Flight Tracker”下。如果需要,请创建该文件夹),以及 FlightData.xml 文件。程序将首先显示机场位置地图。点击 **Show/Flights** 查看动画显示的航空公司航线图。点击 **Tools/Edit Flight Data** 将打开选项卡式对话框,你可以在其中为你喜欢的航空公司添加航班信息!