Visually Located

XAML and GIS

Managing Extents within the ArcGIS WPF/Silverlight/etc. APIs

Managing extents within the ArcGIS .Net client API’s is pretty simple. Esri has an example on the resources page. I implemented one for the ArcFM Silverlight SDK sample. Oddly enough, they are quite similar. I don’t remember copying theirs, but you never know. Like most things we as developers do, after a couple of years you look back and wonder “What was I thinking?” I look back at my original code and wonder why did I implement the Extents class the way that I did. I originally used one List to hold all of the Extents. This made some of the code pretty ugly. I’ve created a different implementation that utilizes two stacks. I have one class that manages all of the extents:

   1: public class Extents
   2: {
   3:     private readonly Stack<Envelope> _backStack = new Stack<Envelope>();
   4:     private readonly Stack<Envelope> _forwardStack = new Stack<Envelope>();
   5:  
   6:     public bool HasPreviousExtent
   7:     {
   8:         get { return _backStack.Count > 0; }
   9:     }
  10:  
  11:     public bool HasNextExtent
  12:     {
  13:         get { return _forwardStack.Count > 0; }
  14:     }
  15:  
  16:     public Envelope CurrentExtent { get; set; }
  17:  
  18:     public Envelope PreviousExtent
  19:     {
  20:         get
  21:         {
  22:             if (HasPreviousExtent)
  23:             {
  24:                 _forwardStack.Push(CurrentExtent);
  25:                 CurrentExtent = _backStack.Pop();
  26:                 return CurrentExtent;
  27:             }
  28:             return null;
  29:         }
  30:     }
  31:  
  32:     public Envelope NextExtent
  33:     {
  34:         get
  35:         {
  36:             if (HasNextExtent)
  37:             {
  38:                 _backStack.Push(CurrentExtent);
  39:                 CurrentExtent = _forwardStack.Pop();
  40:                 return CurrentExtent;
  41:             }
  42:             return null;
  43:         }
  44:     }
  45:  
  46:     public void Add(Envelope extent)
  47:     {
  48:         if (extent == null) return;
  49:  
  50:         if (CurrentExtent != null)
  51:         {
  52:             _backStack.Push(CurrentExtent);
  53:         }
  54:         CurrentExtent = extent;
  55:  
  56:         // If a new extent is added, then anything in our forward stack needs to be removed
  57:         _forwardStack.Clear();
  58:     }
  59: }

I’m a big fan of MVVM, so I’ll use a ViewModel to access the Extents. I’ll also be using the DelegateCommand from Microsoft.

   1: public class ViewModel
   2: {
   3:     public ViewModel()
   4:     {
   5:         Extents = new Extents();
   6:         PreviousExtent = new DelegateCommand(MovePreviousExtent, extent => Extents.HasPreviousExtent);
   7:         NextExtent = new DelegateCommand(MoveNextExtent, extent => Extents.HasNextExtent);
   8:         IsNewExtent = true;
   9:     }
  10:  
  11:     private Map _map;
  12:  
  13:     public Map Map
  14:     {
  15:         get { return _map; }
  16:         set
  17:         {
  18:             UnsubscribeMapEvents(_map);
  19:             SubscribeMapEvents(value);
  20:             _map = value; 
  21:         }
  22:     }
  23:  
  24:     public DelegateCommand PreviousExtent { get; private set; }
  25:     public DelegateCommand NextExtent { get; private set; }
  26:  
  27:     private bool IsNewExtent { get; set; }
  28:     private Extents Extents { get; set; }
  29:  
  30:     private void MovePreviousExtent(object obj)
  31:     {
  32:         // This extent should not be put onto the Extents stack.
  33:         IsNewExtent = false;
  34:         Map.ZoomTo(Extents.PreviousExtent);
  35:     }
  36:     
  37:     private void MoveNextExtent(object obj)
  38:     {
  39:         // This extent should not be put onto the Extents stack.
  40:         IsNewExtent = false;
  41:         Map.ZoomTo(Extents.NextExtent);
  42:     }
  43:  
  44:     private void UnsubscribeMapEvents(Map map)
  45:     {
  46:         if (map == null) return;
  47:         map.ExtentChanged -= Map_ExtentChanged;
  48:     }
  49:  
  50:     private void SubscribeMapEvents(Map map)
  51:     {
  52:         if (map == null) return;
  53:         map.ExtentChanged += Map_ExtentChanged;
  54:     }
  55:  
  56:     private void Map_ExtentChanged(object sender, ExtentEventArgs e)
  57:     {
  58:         // Only add the extent if it is "new" ie: Not a Previous or Next extent
  59:         if (IsNewExtent)
  60:         {
  61:             Extents.Add(e.NewExtent);
  62:         }
  63:         IsNewExtent = true;
  64:         PreviousExtent.RaiseCanExecuteChanged();
  65:         NextExtent.RaiseCanExecuteChanged();
  66:     }
  67: }

The joy of these classes is that they can be used within all of the .Net Client API’s! This can even be used within the new ArcGIS Runtime (currently in Beta). Using these within our application is now a breeze! Here is the xaml for a WPF Window

   1: <Window x:Class="ExtentNavigationApp.MainWindow"
   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:         xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
   5:         Title="MainWindow" Height="350" Width="525">
   6:     <Grid x:Name="LayoutRoot" Background="White">
   7:         <esri:Map x:Name="Map" >
   8:             <esri:ArcGISTiledMapServiceLayer Url="http://server.arcfmsolution.com/ArcGIS/rest/services/Landbase/MapServer"/>
   9:             <esri:ArcGISTiledMapServiceLayer Url="http://server.arcfmsolution.com/ArcGIS/rest/services/Electric/MapServer"/>
  10:         </esri:Map>
  11:         <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
  12:             <Button Content="Previous Extent" Command="{Binding PreviousExtent}" Height="25" Width="100"/>
  13:             <Button Content="Next Extent" Command="{Binding NextExtent}" Height="25" Width="100"/>
  14:         </StackPanel>
  15:     </Grid>
  16: </Window>

And the code behind:

   1: public partial class MainWindow : Window
   2: {
   3:     public MainWindow()
   4:     {
   5:         InitializeComponent();
   6:         DataContext = new ViewModel() { Map = Map };
   7:     }
   8: }
blog comments powered by Disqus