2011年3月20日星期日

ArcGIS API for Windows Phone开发实例(4):点击查看超市信息

  本文内容:Silverlight的自定义UserControl,NavigationService(页面导航),WP的Application Bar,ArcGIS API的ElementLayer。
  上一节中,已经完成了程序的准备工作,利用FeatureLayer来显示超市位置,接下来的几篇文章中我们就来依次实现程序的四个功能点:

  • 点击查看某个超市的详细信息;
  • 按空间范围查看某几个店面的销售总额;
  • 按时间的方式动态查看每个店面的营业情况;
  • 对某个店面的近期营业情况做出具体分析。

  第一个功能在GIS中叫做Identify。这个功能的作用是,当使用者激活该功能后,点击地图上的某个超市图标,会弹出一个类似气泡的小窗口(InfoWindow)显示超市名称,点击这个气泡后,程序可导航到另一个页面来显示该超市的详细信息。
  由于很多功能都是利用单击地图的方式来实现,为了不让这些功能混淆,我将程序中的四个功能分别做成不同的工具(封装成不同的类),点击某个工具后就激活它,此时地图或其中的控件就响应该工具的功能。
  我们在工程中新建一个文件夹Tools,放置与功能点有关的类;并在其中新建一个Identify.cs的文件,用来实现我们的第一个功能点。

clip_image002

   1: public class Identify
   2:     {
   3:         private bool _isActivated;
   4:         private ElementLayer _elementLayer; //InfoWindow layer 
   5:         public Map _map1 { get; set; }
   6:         public GraphicsLayer GLayer { get; set; } //supermarket layer
   7:         public bool IsActivated
   8:         {
   9:             get { return _isActivated; }
  10:             set
  11:             {
  12:                 if (_isActivated != value)
  13:                 {
  14:                     _isActivated = value;
  15:                     if (value)
  16:                     {
  17:                         if(_elementLayer ==null)
  18:                             _ elementLayer = new ElementLayer();
  19:                         if (!_map1.Layers.Contains(_elementLayer))
  20:                             _map1.Layers.Add(_elementLayer);
  21:                         GLayer.MouseLeftButtonDown += GLayer_MouseLeftButtonDown;
  22:                     }
  23:                     else
  24:                     {
  25:                         if (_map1.Layers.Contains(_elementLayer))
  26:                             _map1.Layers.Remove(_elementLayer);
  27:                         GLayer.MouseLeftButtonDown -= GLayer_MouseLeftButtonDown;
  28:                         if (GLayer.SelectedGraphics.Count() > 0)
  29:                             GLayer.SelectedGraphics.ToList()[0].UnSelect();
  30:                         _ elementLayer.Children.Clear();
  31:                     }
  32:                 }
  33:             }
  34:         }
  35:         
  36:         public Identify(Map map,GraphicsLayer glayer)
  37:         {
  38:             _map1 = map;
  39:             GLayer = glayer;
  40:         }
  41:     }

在这个类(Identify工具)的构造函数中,有两个参数:一个Map控件和一个GraphicsLayer,分别是我们程序中的Map控件和超市图层FeatureLayer(继承自GraphicsLayer),因此我们可以在Identify工具中控制它们的行为。
  IsActived属性控制着这个工具的状态,如果IsActived=ture,则该工具处于激活状态,此时地图和超市图层响应Identify工具规定的行为;如果IsActived=false,则代表程序不使用该工具,做一些清理工作。其他工具也是如此,可以用代码来控制某一时刻只能有一个工具处于激活状态。
  另外我们还可以看到,Identify这个类中还有一个ElementLayer类型的_Elayer属性。ElementLayer是ArcGIS API for Windows Phone/Silverlight/WPF中的一种图层类型,主要用来承载Silverlight中的原生对象(UIElement),比较关键的一点是,ElementLayer中的元素会随着地图范围的变化而变化(缩放/平移),而不用自己去处理这些UIElement的地理坐标。所以在这里我们就用ElementLayer来放置我们的气泡(InfoWindow)。
  来看一下Identify的主要功能:



   1: void GLayer_MouseLeftButtonDown(object sender, GraphicMouseButtonEventArgs e)
   2:         {
   3:             if (GLayer.SelectedGraphics.Count() > 0)
   4:                 GLayer.SelectedGraphics.ToList()[0].UnSelect();
   5:  
   6:             Graphic g = e.Graphic;
   7:             _ELayer.Children.Clear();//remove other infowindow
   8:             InfoWindow infoWindow = new InfoWindow(_ELayer, e.Graphic);
   9:             ESRI.ArcGIS.Client.ElementLayer.SetEnvelope(infoWindow, new Envelope((e.Graphic.Geometry as MapPoint), (e.Graphic.Geometry as MapPoint)));
  10:             _ELayer.Children.Add(infoWindow);
  11:  
  12:             e.Graphic.Select();
  13:         }


  GraphicsLayer(超市图层)的MouseButtonDown事件可以保证,只有当点击到某个Graphic(超市)后,才会触发此事件,而点击空白地方是不会触发此事件的。我们在这里并需要做任何查询操作,就可以通过被点击Graphic的Attributes属性获得该超市的所有信息,因为所有超市的属性信息(还记得OutFields属性的“*”吗)在FeatureLayer初始化的时候,已经被传送到了客户端(手机上)。点击某个超市后,屏幕上会出现一个气泡(InfoWindow类)显示超市店名。

clip_image002[5]

  这个类是我们自定义的一个UserControl,可以很方便的给其中加入额外的功能,比如点击了这个InfoWindow后,将程序导航到超市详细信息的页面。关于这个InfoWindow类详细代码以及在线例子,可以在这里查看。此外2.1以后版本的API中也提供了InfoWindow的ToolKit,有兴趣的同学可以在这里查看
  Windows Phone程序中,由于屏幕尺寸原因,如果需要另外显示比较多的内容,则需要将程序导航到一个新的页面中,比如这里我们需要显示超市的详细信息。

clip_image004

  所以在InfoWindow本身的单击事件中,用下面的代码将程序导航到显示超市详细信息的页面(Attributes.xaml)。



   1: private void Canvas_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
   2:         {
   3:             //add clicked graphic to app level, so attributes.xaml could access it
   4:             App app = Application.Current as App;
   5:             if (app.AppParameters.ContainsKey("IdentifyGraphic"))
   6:                 app.AppParameters["IdentifyGraphic"] = Graphic;
   7:             else
   8:                 app.AppParameters.Add("IdentifyGraphic", Graphic);
   9:             (app.AppParameters["MainPage"] as PhoneApplicationPage).NavigationService.Navigate(new Uri("/Tools/Attributes.xaml", UriKind.Relative));
  10:         }


  其中重要的类是NavigationService,每个Windows Phone的页面(PhoneApplicationPage)都有这样一个属性。在多页面的Silverlight程序中也使用此类来导航。虽然Navigate方法可以在页面之间传递参数(类似asp.net),但仅限于string类型,因此我们的超市属性信息集合不好处理。我在App.xaml.cs文件里的Application类中,定义一个全局的Dictionary,用于存储我们需要用到的全局变量。



   1: //used to store variables which need to pass from one page to another
   2:         public Dictionary<string, object> AppParameters;


  先将被点击的Graphic存入程序的Silverlight的全局变量AppParameters中,然后在Attributes.xaml页面就能取到。Attributes.xaml中用ListBox控件来显示超市信息,由于Windows Phone基于Silverlight 3,而Graphic的属性信息Attributes是Dictionary集合,dictionary binding在Silverlight 4中才支持,所以这里我们依然需要使用到DictionaryConverter来在绑定过程中进行转换。Attributes.xaml页面:



   1: <!--ContentPanel - place additional content here-->
   2:         <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
   3:             <ListBox x:Name="listbox" Margin="0,0,-12,0" ItemsSource="{Binding}">
   4:                 <ListBox.ItemTemplate>
   5:                     <DataTemplate>
   6:                         <StackPanel Margin="0,0,0,17" Width="432">
   7:                             <Canvas>
   8:                                 <Rectangle Fill="#FF83919D" Canvas.Left="0" Canvas.Top="0" Height="31" Width="800"/>
   9:                                 <TextBlock Text="店名"
  10:                         TextWrapping="Wrap" Margin="12,-4,4,5" FontSize="26" Foreground="White"/>
  11:                             </Canvas>
  12:                             <TextBlock Text="{Binding Path=Attributes, Converter={StaticResource MyDictionaryConverter}, ConverterParameter=Name}" 
  13:                             TextWrapping="Wrap" FontSize="30" Margin="12,28,12,3" Foreground="#FF188DC6"/>
  14:                             <Canvas>
  15:                                 <Rectangle Fill="#FF83919D" Canvas.Left="0" Canvas.Top="0" Height="31" Width="800"/>
  16:                                 <TextBlock Text="地址"
  17:                         TextWrapping="Wrap" Margin="12,-4,4,5" FontSize="26" Foreground="White"/>
  18:                             </Canvas>
  19:                             <TextBlock Text="{Binding Path=Attributes, Converter={StaticResource MyDictionaryConverter}, ConverterParameter=Address}" 
  20:                             TextWrapping="Wrap" FontSize="30" Margin="12,28,12,3" Foreground="#FF188DC6"/>
  21:                             。。。。。。。。。。。。
  22:                         </StackPanel>
  23:                     </DataTemplate>
  24:                 </ListBox.ItemTemplate>
  25:             </ListBox>
  26:         </Grid>

  Attributes.xaml.cs文件:



   1: public partial class Attributes : PhoneApplicationPage
   2:     {
   3:         private Graphic _graphic;
   4:         public Attributes()
   5:         {
   6:             InitializeComponent();
   7:  
   8:             _graphic = (Application.Current as App).AppParameters["IdentifyGraphic"] as Graphic;
   9:             GraphicCollection gc = new GraphicCollection();
  10:             gc.Add(_graphic);
  11:             listbox.ItemsSource = gc;
  12:         }
  13:     }


  完成了整个Identify功能后,在MainPage.xaml页面中,我们在Application Bar中添加四个按钮,点击某个按钮后,就激活相应的工具。比如在点击了Identify功能按钮后,我们需要将Identify工具的IsActived属性设为true,将其他工具的IsActived属性设为false。最后别忘了,在超市图层FeatureLayer初始化时(Update事件),实例化我们的四个工具:



   1: public partial class MainPage : PhoneApplicationPage
   2:     {
   3:         private Identify _OpIdentify;
   4:         private SpatialQuery _OpSpatialQuery;
   5:         private TimeQuery _OpTimeQuery;
   6:         private Analysis _OpAnalysis;
   7:         private FeatureLayer _FLayer;
   8:  
   9:         // Constructor
  10:         public MainPage()
  11:         {
  12:             InitializeComponent();
  13:             _FLayer = map1.Layers["BusinessLayer"] as FeatureLayer;
  14:             _FLayer.UpdateCompleted += (s, a) =>
  15:             {
  16:                 //Add MainPage to app level, so other pages can navigates from it.
  17:                 App app = Application.Current as App;
  18:                 if (!app.AppParameters.ContainsKey("MainPage"))
  19:                     app.AppParameters.Add("MainPage", this);             
  20:                 _OpIdentify = new Identify(map1, _FLayer);
  21:                 _OpSpatialQuery = new SpatialQuery(map1, BusyIndicator);
  22:                 _OpTimeQuery = new TimeQuery(map1);
  23:                 _OpAnalysis = new Analysis(map1,this,BusyIndicator);
  24:             };
  25:         }
  26:     }

  下一节中,我们来实现按空间范围查看某几个店面的销售总额的功能,也就是SpatialQuery这个类。

参考资料:

Windows Phone中的Application Bar和Application Menu:
http://msdn.microsoft.com/en-us/library/ff431801(v=VS.92).aspx

Windows Phone中的页面和导航框架:
http://msdn.microsoft.com/en-us/library/ff402536(v=vs.92).aspx

Silverlight中自定义UserControl:
http://www.cnblogs.com/Terrylee/archive/2008/03/08/Silverlight2-step-by-step-part10-using-user-controls.html

ArcGIS API for Windows Phone/Silverlight/WPF中的ElmentLayer:
http://help.arcgis.com/en/webapi/silverlight/samples/start.htm#ElementLayer

没有评论:

发表评论