65.9K
CodeProject 正在变化。 阅读更多。
Home

Earth Quake - 一个复合 WPF 灾难监控应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (20投票s)

2012年5月25日

CPOL

16分钟阅读

viewsIcon

66763

downloadIcon

3052

一个使用 Bing Maps 显示世界各地地震的复合 (Prism 4) WPF 应用程序。

Earth Quake

引言

Earth Quake 是一个复合 WPF 应用程序,旨在利用 Bing Maps 显示世界各地所有最近报告的地震。

该应用程序的目标是 .NET 4 运行时,并以 Prism 4 作为其复合框架。Earth Quake 的代码示例和解决方案采用 VB.NET 编写,解决方案适用于 Visual Studio 2010。

本文假定读者对 WPF 有一定了解,并且可能熟悉 Prism 4、MEF 和 MVVM。本文并非关于 MEF 或 MVVM 的文章,但会提供一些简要解释以帮助涵盖这些主题。

Earth Quake 的创建旨在说明以下几点:

  • Prism 复合应用程序开发
    • 区域
    • 视图发现
    • 通过事件聚合和命令进行通信
  • MVVM
  • MEF
  • Bing Maps
  • WPF 和数据绑定

背景

对我来说,WPF、Prism、MEF 和 MVVM 都是纯粹的乐趣。

市面上有许多精彩的文章详细介绍了如何使用这些技术和模式开发应用程序。我不想抢这些文章的风头,因此,请参阅以下链接,其中包含一些对您有帮助的资源,如果您不熟悉它们的话。

我想设计一个小型基础应用程序,涵盖 Prism 和 WPF 的一些功能,希望您能觉得它有用。本文是我希望带来的几篇文章之一。

另外值得一提的是(我本来以为自己添加了但却没有),在开发此应用程序时,我发现了一个概念非常相似的应用程序。该应用程序由 Brian Lagunas 开发,对我帮助很大。最初我使用的是静态图像加上动态放置的点,但 Bing Maps 提供了一个更好的替代方案。示例可以在这里找到:这里

目录

参考文献

Earth Quake 包含一些已包含在项目下载中的引用库。要运行应用程序,需要这些引用。

名称 Path Assemblies 描述
Prism 4 \EarthQuake\Libs\Prism 4\ Microsoft.Practices.Prism.dll
Microsoft.Practices.Prism.Interactivity.dll
Microsoft.Practices.Prism.MefExtensions.dll
Prism 4 复合框架
Bing Maps for WPF \EarthQuake\Libs\Bing Maps\ Microsoft.Maps.MapControl.WPF.dll Bing Maps WPF 控件
Json.Net \EarthQuake\Libs\Json.Net\ Newtonsoft.Json.dll .NET 的 JSON 框架

要使用 MEF,您需要导入以下系统 DLL(MEF 是 .NET 4 运行时的一部分)

System.ComponentModel.Composition

Maps 模块包含对 Microsoft.Expression.Interactions DLL 的引用。这用于在非命令对象上执行命令。如果您拥有 Expression Studio 4 (Blend),那么您将能够正常使用。如果没有,我建议您安装 Blend 或从这里下载 Blend SDK。

Microsoft.Expression.Interactions

Bing Maps

Earth Quake 使用了 Bing Maps 团队最近发布的 WPF 控件。Bing Maps 曾提供 AJAX 等 API,现在有了 WPF 控件。控件程序集随解决方案下载提供,但您可能需要从这里获取 SDK。

Bing Maps API 密钥

虽然开发时不是必需的,但您应该从Bing Maps Portal创建一个 Bing Maps API 密钥。否则,您将看到一个框覆盖在地图上,提示您获取密钥,如下图所示:

Missing Bing Maps API Key

门户网站还提供了一些资源链接以及其他 SDK 下载。

生成/注册密钥后,您稍后会需要它,因为 Bing Maps 控件需要密钥凭据才能移除该消息。

Json.Net

Earth Quake 从美国地质调查局网站下载报告的地震事件。该网站提供各种信息源,其下载格式(CSV、ATOM 等)和注册地震的频率各不相同。Earth Quake 下载 30 天的 JSON 格式数据。因此,我想看看是否有 .NET 框架可以用于执行 lambda 表达式。

Json.Net(由 James Newton-King 开发)就是这样一个框架。它提供了一种方便查询 JSON 数据源的机制。Json.Net 提供以下功能(摘自网站):

  • 灵活的 JSON 序列化器,用于在 .NET 对象和 JSON 之间进行转换
  • LINQ to JSON,用于手动读写 JSON
  • 高性能,比 .NET 内置的 JSON 序列化器更快
  • 生成缩进、易于阅读的 JSON
  • JSON 与 XML 相互转换
  • 支持 .NET 2、.NET 3.5、.NET 4、Silverlight 和 Windows Phone

下面是一个使用该框架的简要示例:

' A demo Json feed. This would be a webclient
' download from a remote location but is a string for a quick demo.
Dim feed As String = "{""Name"": ""Apple"",""Expiry"": 1230422400000,""Price"": 3.99,
                         ""Sizes"": [""Small"",""Medium"",""Large""]}"

' Parse the feed using the Json.Net parser
Dim o As JObject = JObject.Parse(feed)

' Get the name of the apple
Dim name As String = DirectCast(o("Name"), String)

' Get the array of sizes using the JArray object
Dim sizes As JArray = DirectCast(o("Sizes"), JArray)

' Just get the first size in the sizes collection. Collections are zero-index based.
Dim smallest As String = DirectCast(sizes(0), String)

Prism 4

如果您长期以来一直在从事 Silverlight、WPF 甚至 WP7 开发,您可能遇到过“复合应用程序”这个术语。Prism(前身为 Composite Application Guidance for WPF and Silverlight),由 Microsoft Patterns and Practices 团队开发,旨在让开发者轻松创建模块化、解耦的应用程序。

Prism 采用体现重要架构设计原则(如关注点分离和松耦合)的设计模式,帮助您设计和构建使用松耦合组件的应用程序,这些组件可以独立演进,但又易于无缝集成到整个应用程序中。

Earth Quake 使用 Prism 4 框架来展示模块化的示例。要下载该框架并获取更多信息,请参阅以下链接。

地震

如前所述,Earth Quake 从美国地质调查局网站获取报告的地震信息。每个信息源都按事件发生的天数、地震强度和信息可读格式进行细分。

可用的格式有:

  • CSV
  • ATOM
  • RSS
    • 与 ATOM 和 JSON 相比,信息有限。
  • JSON
  • JSON(P)
    • 与 JSON 格式相同,但此数据源被包装在函数调用 `eqfeed_callback` 中。Earth Quake 使用标准的 Json 数据源。
  • KML
    • Google Earth 的 XML 格式,用于地理应用程序。
  • Twitter
  • 电子邮件

架构

解决方案

Earth Quake 是一个基本的复合应用程序。解决方案分为几个不同的项目,每个项目都为整个应用程序提供独特的功能。解决方案布局如下所示:

Solution Layout

  1. Business。业务解决方案文件夹下的共享项目用于存放大部分共享业务逻辑和域类。这些对象不太可能在所有模块之间共享,因为并非所有模块都需要它们。大多数 Prism 应用程序将 Infrastructure 项目作为其全局共享项目,但我喜欢保持事物轻量级,因为这本身可能会成为一个非常庞大的项目。在一个更大的应用程序中,您更有可能为每个模块拥有共享(核心)项目,但 Earth Quake 不是这样。
  2. Core。这些核心项目很可能被大多数(如果不是全部)模块引用。
    1. Infrastructure:Infrastructure 项目包含诸如复合命令、事件负载和接口等元素。这个项目非常重要,很可能会被所有模块引用。
    2. Resources:与 Infrastructure 项目相比,重要性稍低,但这个项目包含诸如主题、图像资源和其他“应用程序特定”样式等元素。
  3. Modules。Modules 是构成应用程序总功能的项目。每个模块都可以直接依赖于 Infrastructure 项目(并且很可能会这样做)。Modules 可能不是整个应用程序解决方案的一部分,因为它们可以从外部开发,例如。以模块化方式开发应用程序的美妙之处在于有机会让开发团队专注于应用程序的各个方面,而不会相互干扰。
  4. Shell。这是用于承载和渲染视图以及实现模块和其他依赖项逻辑的 Shell 或模块主机。Shell 可以是 WPF 或 Silverlight 应用程序,例如。

MVVM

The MVVM Pattern
来源:Microsoft

Earth Quake 在整个应用程序中都使用了 MVVM 模式。市面上有许多出色的 MVVM 框架(例如 MVVM lightCinchJounce 等),但我希望保持演示的简洁性,只涵盖几个主题。仅使用了 Prism 框架和Josh Smith 的 ViewModelBase类。同样,这也不是关于 MVVM 的教程,因为我认为其他人已经对此进行了更详细的阐述。

MEF

Earth Quake 使用 MEF(Managed Extensibility Framework)来解析视图和 ViewModel。每个视图将使用属性注入来连接视图对其相关 ViewModel 的依赖。Earth Quake 还在应用程序的其他部分使用了构造函数注入。

基本上(我说基本上)每个视图和 ViewModel 都有一个导出类属性。通过此属性,MEF 可以帮助解析对象。如果您有一个属性(或构造函数)希望将对象放置在其中,您只需声明一个导入属性。如下所示:

Shell ViewModel。注意“Export”属性。

''' <summary>
''' The Shell ViewModel
''' </summary>
''' <remarks></remarks>
<Export(GetType(ShellViewModel))> _
<PartCreationPolicy(NonShared)> _
Public Class ShellViewModel
Inherits 
ViewModelBase
    .....
End Class

Shell 视图将通过属性注入获取 ViewModel。“Exported” ViewModel 将被“Import”到 Shell 中。注意 ViewModel 属性上的“Import”属性。如下所示:

''' <summary>
''' Sets the ViewModel.
''' </summary>
''' <remarks>
''' This set-only property is annotated with the <see cref="ImportAttribute"/> 
so it is injected by MEF with
''' the appropriate view model.
'''</remarks>
<Import()> _
Private WriteOnly Property ViewModel() As 
ShellViewModel
Set(ByVal value As ShellViewModel)
Me.DataContext = value
End Set
End Property

对于不熟悉 MEF 的人来说,MEF 是 Microsoft 用于解析对象的框架。MEF 有时被称为 DI / IoC 容器,但实际上它的目的并非如此,但有许多相似之处。Prism 还支持 Unity 框架,这是 Microsoft 的 DI / IoC 框架,但本文未涵盖。

Earth Quake

好了,现在来说应用程序。Earth Quake 目前是一个带有单个主视图(地图)的简单用户界面。UI 的其他方面与地图控制和应用程序状态相关。

UI 概述

UI Overview

应用程序分为三个区域:

  • R1:工具栏区域。
  • R2:主地图区域。
  • R3:状态栏区域。

应用程序还有 3 个主要功能:

  1. 导航 - 缩放、居中和定位。
  2. 工具 - 更改地图样式。
  3. 详细信息 - 通过鼠标悬停图钉触发的地震信息。

基本概念是,您可以自由地平移和移动地球,并查看屏幕上可能显示的任何地震点。每次用户将鼠标悬停在地图图钉上时,地震信息都会显示在“Quake details”窗口中。

Shell

Shell 是可以托管与应用程序相关的模块的核心应用程序。Shell 可以是 Silverlight 或 WPF 应用程序。在此实例中,Shell 项目是一个 WPF .NET 4 应用程序。

目前只有一个视图及其相关的 ViewModel。这是一个重要的视图,因为它是核心布局(类似于 ASP.NET 中的 masterpage)视图,包含将注入模块视图的区域。为了让视图/Shell 包含区域,建议导入以下命名空间:

xmlns:inf="clr-namespace:EarthQuake.Infrastructure;assembly=EarthQuake.Infrastructure"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"

“prism”命名空间是对 Microsoft.Practices.Prism 库的引用。此程序集负责所有区域管理。“inf”命名空间通常是对解决方案基础设施项目的引用。虽然不是强制性的,但引用的原因是为了使用来自“WellKnownModuleNames”和“WellKnownRegionNames”(位于基础设施项目中的)的强类型区域和模块名称。

Shell 视图(称为 shell.xaml)通过 Bootstrapper 初始化。Shell 视图包含以下将要被注入的区域:

<!-- ToolBars region -->
<ItemsControl Grid.Row="1" prism:RegionManager.RegionName="{x:Static inf:WellKnownRegionNames.ToolBarRegion}" />

<!-- Map Region -->
<ContentControl Grid.Row="2" prism:RegionManager.RegionName="{x:Static inf:WellKnownRegionNames.MapRegion}" />

<!-- Status Region -->
<ContentControl Grid.Row="3" prism:RegionManager.RegionName="{x:Static inf:WellKnownRegionNames.StatusRegion}" />

这里使用了两种区域容器:ContentControlItemsControl。基本上,ContentControl 用于显示单个视图或 UI 元素。ItemsControl 用于显示多个项目,如按钮。您会注意到 RegionManager.RegionName 属性被设置为来自 WellKnownModuleNames 的强类型名称,如上所述。

Bootstrapper

Shell 的另一方面是提供一种方法来使用模块目录来指定模块,并配置区域适配器或行为。Earth Quake 继承自 MEF Bootstrapper,它来自 Microsoft.Practices.Prism.MefExtensions 程序集。Bootstrapper 创建和初始化 Shell 窗口,并配置与项目相关的模块。请注意,Bootstrapper 类应位于项目的根目录。

Bootstrapper 的 ConfigureAggregateCatalog() 方法负责创建对模块的引用。目前所有模块都在代码中引用,但您可以有一个要监视的模块目录或一个要使用的 XML 配置文件。更常见的是在代码中引用模块,但我发现目录方法确实提供了一种更“松耦合”的场景。

''' <summary>
''' Configures the aggregate catalog.
''' </summary>
''' <remarks></remarks>
Protected Overrides Sub ConfigureAggregateCatalog()
MyBase.ConfigureAggregateCatalog()

'register the modules
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(Bootstrapper).Assembly))
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(StatusModule.StatusModule).Assembly))
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(MapModule.MapModule).Assembly))
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(ToolBarModule.ToolBarModule).Assembly))
End Sub

为了让应用程序初始化 Bootstrapper 并使复合应用程序栩栩如生,需要在 OnStartup 事件中实例化 Bootstrapper。这通常在 Application.xaml 的代码隐藏中完成。

Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
MyBase.OnStartup(e)

AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf AppDomainUnhandledException

Me.ShutdownMode = ShutdownMode.OnMainWindowClose

Try
Dim bootstrapper As New Bootstrapper()
bootstrapper.Run()
Catch ex As Exception
HandleException(ex)
End Try
End Sub

Infrastructure

Infrastructure 是核心应用程序程序集。它应该并且将包含所有将在每个模块之间共享的核心功能。这些功能将是接口、事件负载、复合全局命令和其他关键对象。

Earth Quake 使用了一个 Infrastructure 项目来包含这些重要的功能方面,但还有一个稍不重要的共享库用于存放所有对整个应用程序不太重要的域对象,因为并非所有模块都会引用它。这是为了保持事物轻量级,因为 Infrastructure 项目本身可能会变得过于庞大。在更大的应用程序中,您更有可能看到模块拥有独立的、核心/共享的程序集,用于存放模块依赖的对象,但这在 Earth Quake 中并非如此。

Modules

应用程序中有三个模块,位于解决方案文件夹“Modules”中。Modules 可以是整个解决方案的一部分,也可以是来自卫星程序集(在外部开发或第三方提供,作为可扩展的插件)。为了使模块存在,必须有一个实现 IModuleMicrosoft.Practices.Prism.Modularity 的成员)接口的类。该类将负责将模块视图注册到应用程序区域。

每个模块初始化类(实现 IModule 接口的类)都有一个名为 Initialize 的方法,该方法在模块被实例化在应用程序 Bootstrapper 中时被调用。对于 Earth Quake 中的每个模块,其中的视图都通过区域管理器注册到一个区域。要使用一种称为视图发现的模式来注册视图,将使用 RegisterViewWithRegion 方法。视图发现是一种模式,它将确保在区域自动添加时将视图添加到该区域。另一方面,视图注入是指将视图添加(注入)到一个区域,这通常是手动完成的,并且由于显而易见的原因需要区域存在。

下面的两个示例展示了如何实现这一点。

'View discovery
_regionManager.RegisterViewWithRegion(WellKnownRegionNames.MapRegion, GetType(WorldMap))

'View injection
_regionManager.Regions(WellKnownRegionNames.MapRegion).Add(GetType(Map))

Toolbar

工具栏模块非常简单。它是一个基本的原始工具栏控件,带有两个按钮:刷新和退出。这两个按钮使用两种不同的机制来触发事件。刷新使用事件聚合器向事件订阅者(可能在多个模块中)引发事件。退出按钮使用复合命令来触发任何订阅的委托命令。在此实例中,退出按钮触发了 ExitApplicationCommand(来自 EarthQuake.Infrastructure.Commands.Global 命名空间)。但它实际上是如何退出应用程序的?Shell ViewModel 中埋藏了一个已注册到全局复合命令 ExitApplicationCommand 的委托命令。任何订阅了全局复合命令的委托命令都将收到事件通知,并执行与该命令关联的任何委托方法。

Status Bar

状态栏再次是一个相当简单的状态栏控件。状态栏在很大程度上依赖于事件聚合来显示地图中心位置的纬度和经度。状态栏也以相同的方式获取状态消息。

Map

地图模块是主模块,负责大部分工作。该模块包含一个视图(map.xaml)、一个 ViewModel (MapViewModel) 和一个 Model (MapModel)。

设置 Bing Maps

要设置 Bing Maps,您需要引用 Microsoft.Maps.MapControl.WPF.dll 。然后,您需要在视图中添加该引用。

xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"

由于 Map 图钉使用 Microsoft.Expression.Interactions 程序集通过鼠标进入和离开触发命令,因此您需要在项目中引用此程序集,然后在标记中引用它。

xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

一旦您拥有了地图控件的引用,就可以将其添加到标记中。

<m:Map Name="WorldMap"
	AnimationLevel="Full"
	Center="{Binding MapCentreLocation, Mode=TwoWay}"
	CredentialsProvider="Your map API Key here"
	Mode="{Binding MapMode}"
	ZoomLevel="{Binding ElementName=Zoom, Path=Value, Mode=TwoWay}">
	<m:MapItemsControl ItemsSource="{Binding EarthquakeLocations}" 
	    ItemTemplate="{StaticResource EarthquakeLocationsTemplate}" />
</m:Map>

地图有一些属性,这些属性已绑定到 ViewModel 或页面上的其他控件。请注意,“CredentialsProvider”属性是添加 API 密钥的地方。同样,出于开发目的,这不是必需的,但我建议您获取一个。

要显示地图图钉,您需要设置“MapItemsControl”。您会注意到 MapItemsControl 已绑定到 ViewModel 中的 EarthquakeLocations ObservableCollection 属性。我为图钉设置了 ItemTemplate,如下所示:

<DataTemplate x:Key="EarthquakeLocationsTemplate">
	<m:Pushpin Name="Pin"
		m:MapLayer.Position="{Binding Location}"
		Tag="{Binding}"
		ToolTip="{Binding Title}">
	<i:Interaction.Triggers>
		<i:EventTrigger EventName="MouseEnter">
			<i:InvokeCommandAction Command="{Binding ElementName=WorldMapView, Path=DataContext.PinMouseOverCommand}" CommandParameter="{Binding}" />
		</i:EventTrigger>
		<i:EventTrigger EventName="MouseLeave">
			<i:InvokeCommandAction Command="{Binding ElementName=WorldMapView, Path=DataContext.PinMouseOverCommand}" CommandParameter="" />
		</i:EventTrigger>
	</i:Interaction.Triggers>
	</m:Pushpin>
</DataTemplate>

您会注意到用于事件触发器的别名“i”。此别名指向 Expression interactivity 命名空间。为了弹出 Earthquake details WPF 弹出控件,我使用交互来触发和调用 ViewModel 中的委托命令“PinMouseOverCommand”。基本上,这只是根据您的鼠标位置打开和关闭详细信息窗口。

获取地图数据

如文章开头所述,地震数据来自 USGS 的 JSON 格式数据源。Json.Net 框架用于帮助轻松解析和查询数据源。地图模块包含一个 Earthquake 存储库 USGSRepository。以下展示了如何填充地震列表并将其返回给 ViewModel 进行绑定:

Public Function FindAll() As List(Of [Shared].Earthquake) _
                Implements Infrastructure.IEarthquakeRepository.FindAll
Dim feed As String
Dim feedObject As JObject

Try
Using client As New WebClient
'set header as some site require this to be present
client.Headers.Add("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)")

'download the Json feed
feed = client.DownloadString(New Uri("http://earthquake.usgs.gov/earthquakes/feed/geojson/2.5/month"))

'parse the feed string
feedObject = JObject.Parse(feed)
End Using

Return (From item In feedObject.SelectToken("features")
Select New [Shared].Earthquake(item.SelectToken("id").ToString) With _
       {.Title = item.SelectToken("properties.place").ToString,
.Magnitude = item.SelectToken("properties.mag").ToString,
.Latitude = item.SelectToken("geometry.coordinates[1]"),
.Longitude = item.SelectToken("geometry.coordinates[0]"),
.LinkURL = String.Concat("http://earthquake.usgs.gov", item.SelectToken("properties.url").ToString),
.EventDateTime = UTCDateTimeHelper.UTCTimeConverter(item.SelectToken("properties.time").ToString),
.Source = "USGS"}).ToList
Catch ex As Exception
' consider logging
Return New List(Of [Shared].Earthquake)
End Try
End Function

最后是 ViewModel。ViewModel 有一个基本方法,用于获取此数据并填充可观察集合,以便可以按照纬度和经度坐标定位地图图钉。

''' <summary>
''' Sets the earthquake locations.
''' </summary>
''' <remarks></remarks>
Private Sub SetEarthquakeLocations()
_eventAggregator.GetEvent(Of Infrastructure.Events.Composite.StatusUpdatedEvent).Publish("Updating Quake locations...")

task.Factory.StartNew(Sub()
Dim mapModel As New MapModel(New USGSRepository)

For Each quake In mapModel.GetEarthquakeData
If Not EarthquakeLocations.Contains(quake) Then
Application.Current.Dispatcher.BeginInvoke(Sub(q)
'add the new quake item to the earthquakes collection via the dispatcher.
'this is because the observable collection can not be updated from another thread.
EarthquakeLocations.Add(q)
End Sub, {quake})
End If
Next
End Sub).ContinueWith(Sub(antecedent)
Dim message As String
If antecedent.IsFaulted Then
message = "Error getting quake events"
Else
message = "Idle..."
End If

_eventAggregator.GetEvent(Of Infrastructure.Events.Composite.StatusUpdatedEvent).Publish(message)
End Sub)
End Sub

您会注意到一个事件聚合器正在发布状态消息。状态栏订阅此事件以显示消息。

结论

这只是使用 WPF 构建 Bing Maps 应用程序的初步基础。如果您需要在世界地图上绘制事件或位置,Bing Maps 控件非常棒。在下一篇文章中,我想提供一种显示实时事件的方法,以及如何在应用程序中使用它。我希望本文能有所帮助,尽管许多主题(如 MEF 和 MVVM)并未得到详细讨论。

历史

  • 2012/05/23 - 第一个版本。
© . All rights reserved.