Esri Mobile 10 SDK, WPF and MVVM

We are going to be building off the project we created from my previous blog on ‘Esri Mobile 10 SDK and WPF Beginner’s Tutorial’. In that tutorial we created a map, added basic navigation and set up syncing. In this blog we are going to add another common GIS tool, selecting features and displaying results but following the MVVM design pattern.

MVVM stands for the Model-View-ViewModel design pattern commonly used in WPF/Silverlight applications. Like MVP and MVC, it strives to separate the user interface (UI) or View from the code behind doing the heavy lifting (querying, updating data, so on). UI design is becoming its own discipline and often the View will be designed by someone else working in Expression Blend (EB). For more information on MVVM, check out http://en.wikipedia.org/wiki/Model_View_ViewModel.

In this blog I am going to dive in and give a brief introduction and examples using the Esri Mobile 10 SDK with the following concepts:

  • Model
  • View
  • ViewModel
  • Bindings
  • Commands
  • Storyboards
  • Event Triggers

In your project add a Model, View and ViewModel folder. There are, like everything, multiple ways to implement MVVM as well as debates on where to put certain things. The implementation of MVVM design pattern I will use is a slightly easier variation of this one by Josh Smith- http://joshsmithonwpf.wordpress.com/advanced-mvvm/.  At the end of this tutorial, you should have a class diagram that resembles the following:

First let’s set up which layer we are going to select. In my example here, I am going to use the Schools layer that you can pull off the ESRI Data & Maps DVD. I clipped the schools by the State of Texas and named this layer tx_schools. (You can use whatever layer you have in your cache or pull this layer out as well. If you use your own layer, you will have to also create your own Model and modify the ViewModels covered here accordingly.)

In the Settings.xml file, I add the tag <selectLayer>tx_schools</selectLayer>. We will come back later in this blog to parse this out. Let’s now switch to setting up the View. Add a UserControl to the View folder and name it ResultsControlView . Size as you would like. Add a DataGrid control to your UserControl and name it dataGridResults. For my example, I want three of those fields to be displayed in my datagrid so I add three columns like so:

<DataGrid AutoGenerateColumns="False" Height="225" HorizontalAlignment="Left" Margin="10" Name="dataGridResults" VerticalAlignment="Top" Width="475" ItemsSource="{Binding SchoolList}" SelectedItem="{Binding SelectedItem}"><DataGrid.Columns><DataGridTextColumn Binding="{Binding SchoolName}" Header="School Name" FontWeight="Bold"/><DataGridTextColumn Binding="{Binding LabelFlag}" Header="Label Flag"/><DataGridTextColumn Binding="{Binding StCtyFips}" Header="FIPS"/></DataGrid.Columns></DataGrid>

A couple thinks to point out in this snippet from our xaml is the {Binding } term. Like I mentioned early we are going to use xaml’s ability to bind values from our ViewModel and Model to our View. All the view needs to know is what these properties will be named. My DataGrid source will be bound to a collection called SchoolList. The DataGrid SelectedItem is bound to my property SelectedItem. Each of the columns are bound to three properties coming from my school Model. When we create our ViewModel and Model things will start to make more sense.

Switch back to MainWindow.xaml and let’s add our new control. First add reference to Window header tag to our View namespace xmlns:view=”clr-namespace:WpfMobileTuturialMVVM.View”.  Now we can add our control in the xaml like so with some minor styling (you can change as you feel fit):

<view:ResultsControlView x:Name="resultsView" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10" Visibility="Hidden"/>

Next let’s create our Model. Before we get started I want to introduce INotifyPropertyChanged. This interface is very important for bound properties. This is the mechanism that lets the View bound properties know if changes have occurred back in Model or ViewModel automatically. Pretty sweet right? Before we create the Model for our schools Layer we will create a base class that sets up the INotifyPropertyChanged. Add a new class to Model folder and call it EntityBase. And insert following code:

public class EntityBase:INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        protected void FirePropertyChange(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }

Now right click Model folder and add new class called SchoolModel that implements our EntityBase. The reason for the separation is because more than likely in a real world solution you will have multiple models. And there is no point in re-implementing the same code across all of them. Add the following code snippet to SchoolModel:

public class SchoolModel:EntityBase
    {
        public SchoolModel() { }
        #region members
        private string _schoolName;
        private string _stCtyFips;
        private int _labelFlag;
        private Geometry _schoolGeometry;
        #endregion
        #region properties
        public string SchoolName
        {
            get { return _schoolName; }
            set
            {
                _schoolName = value;
                base.FirePropertyChange("SchoolName");
            }
        }
        public string StCtyFips
        {
            get { return _stCtyFips; }
            set
            {
                _stCtyFips = value;
                base.FirePropertyChange("StCtyFips");
            }
        }
        public int LabelFlag
        {
            get { return _labelFlag; }
            set
            {
                _labelFlag = value;
                base.FirePropertyChange("LabelFlag");
            }
        }
        public Geometry SchoolGeometry
        {
            get { return _schoolGeometry; }
            set
            {
                _schoolGeometry = value;
                base.FirePropertyChange("SchoolGeometry");
            }
        }
        #endregion
    }

Let’s walk through this now. If you have worked in other design patterns you will notice this looks very similar to what are termed Domains but with utilization of the INotifyPropertyChanged method we defined in EntityBase (If you are a stickler you can separate this class into Domain and Model classes). If you look at the layer in ArcCatalog you will notice that I did not include all the fields. Typically I have the Model reflect every field in the actual layer, but for this tutorial I am cutting some corners. But you see where those property names we used in View are now defined here in the Model.  But we need the middle piece to bind them together so time to move onto the ViewModel.

First off let’s set up a base class for all the ViewModels that will implement INotifyPropertyChanged. Add a new class to ViewModel folder and name it ViewModelBase. Add the following code snippet:

#region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
        #region Protected Methods
        /// <summary>
        /// Fires the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The name of the changed property.</param>
        protected void RaisePropertyChangedEvent(string propertyName)
        {
            if (PropertyChanged != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                PropertyChanged(this, e);
            }
        }
        #endregion

For each View, we will have its own ViewModel. The master View is MainWindow and contains the ResultControlView. We are going to follow this pattern and create a MainWindowViewModel class and ResultsViewModel class in the ViewModel folder. Both this classes implement ViewModelBase. MainWindowViewModel will contain the reference to the ResultsViewModel and will pass calls that require changes to ResultsControllView bound properties to ResultsViewModel. Let us start setting up MainWindowViewModel:

public MainWindowViewModel() 
        {
            _resultsViewModel = new ResultsViewModel();
        }
        #region members
        ESRI.ArcGIS.Mobile.WPF.NavigationMode m_lastNavigatioMode;
        private ESRI.ArcGIS.Mobile.WPF.Map _map;
        private MobileCache _mobileCache;
        GraphicLayer selectionLayer;
        EnvelopeSketchTool envelopeSketchTool;
        private ResultsViewModel _resultsViewModel;
        #endregion
        #region properties
        public string FeatureLayerName { get; set; }
        public ESRI.ArcGIS.Mobile.WPF.Map Map
        {
            get { return _map; }
            set
            {
                _map = value;            
            }
        }
        public MobileCache MobileCache
        {
            get { return _mobileCache; }
            set
            {
                _mobileCache = value;
            }
        }
        public ResultsViewModel MyResultsViewModel
        {
            get { return _resultsViewModel; }
            set
            {
                _resultsViewModel = value;
                base.RaisePropertyChangedEvent("MyResultsViewModel");
            }
        }
        #endregion

Before we set the binding between the Views and ViewModels let us set up ResultsViewModel with following code snippet:

public ResultsViewModel() { }

        #region members
        private ObservableCollection<SchoolModel> _schoolList;
        private SchoolModel _selectedItem;
        private MobileCache _mobileCache;
        private ESRI.ArcGIS.Mobile.WPF.Map _map;
        private string _featureLayerName;
        private GraphicLayer glSelected;
        private FeatureDataTable selectedDataTable;
        private FeatureLayer featureLayer = null;
        #endregion
        #region properties
        public ObservableCollection<SchoolModel> SchoolList
        {
            get { return _schoolList; }
            set
            {
                _schoolList = value;
                base.RaisePropertyChangedEvent("SchoolList");
            }
        }
        public SchoolModel SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                _selectedItem = value;
                base.RaisePropertyChangedEvent("SelectedItem");                
            }
        }
        public string FeatureLayerName
        {
            get { return _featureLayerName; }
            set { _featureLayerName = value; }
        }
        public MobileCache MobileCache
        {
            get { return _mobileCache; }
            set { _mobileCache = value; }
        }
        public ESRI.ArcGIS.Mobile.WPF.Map Map
        {
            get { return _map; }
            set { _map = value; }
        }
        #endregion

For now, we are going to focus on SchoolList and SelectedItem. Notice how SelectedItem is an instance of our ShcoolModel and SchoolList is a collection of those Models. This collection will represent the selection set. And this collection, ObservableCollection, is especially cool because it has built in notification about items getting added, removed, or updated. So, we don’t have to do anything special to handle any of that. Pretty cool.

Let’s connect up the Views with the ViewModels by switching to MainWindow.xaml.cs and update with following code snippet:

public MainWindow()
        {
            InitializeComponent();
            _mainWindowVM = new MainWindowViewModel();
            this.DataContext = _mainWindowVM;
        }
        private MainWindowViewModel _mainWindowVM;

In the method, Windows_Loaded add the following to the very bottom of the method:

_mainWindowVM.Map = map1;
_mainWindowVM.MobileCache = mobileCache;

Switch to MainWindow.xaml and add DataContext=”{Binding MyResultsViewModel}” to our ResultsControlView control.

What this did was bind instance of MainWindowViewModel to MainWindow.xaml by setting the DataContect. Since MainWindowViewModel exposes instance of ResultsViewModel, we then bound that to our ResultsControlView by also setting the DataContext. If we dive back into our ResultsControlView, we have that DataGrid that we set the source to SchoolList. This is exposed now through the bound instance of ResultsViewModel. If you are getting lost that is OK. Binding and how it tickles down to the children controls was new to me when I started as well.

So, let’s switch back to ResultsViewModel and add the methods for performing the spatial selection and populating our SchoolList so it shows up in our DataGrid on ResultsControlView.

#region methods
        public void SpatialQuerySchools(Envelope queryEnvelope)
        {
            //reset eveything
            SchoolList = new ObservableCollection<SchoolModel>();
            SelectedItem = new SchoolModel();
            //set up spatial queryfilter
            QueryFilter queryFilter = new QueryFilter();
            queryFilter.Geometry = queryEnvelope;
            queryFilter.GeometricRelationship = GeometricRelationshipType.Intersect;
            LoadSchoolResult(queryFilter);           
        }
        private void LoadSchoolResult(QueryFilter queryFilter)
        {
            //get feature layer
            if (featureLayer == null) featureLayer = getFeatureLayer();
            //if still null
            if (featureLayer == null)
            {
                MessageBox.Show("Could not find feature layer " + FeatureLayerName + " in map cache", "Search Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            selectedDataTable = featureLayer.GetDataTable(queryFilter);
            FeatureDataReader selectDataReader = featureLayer.GetDataReader(queryFilter);
            //set up graphic layer
            _map.MapGraphicLayers.Remove(glSelected);
            //create selected graphic layer
            glSelected = new GraphicLayer();
            //create brush for fill
            System.Windows.Media.SolidColorBrush myBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Aqua);
            // Create a Pen to add to the GeometryDrawing created above.
            System.Windows.Media.Pen myPen = new System.Windows.Media.Pen();
            myPen.Thickness = 3;
            myPen.Brush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Aqua);
            glSelected.Fill = myBrush;
            glSelected.DrawingPen = myPen;
            glSelected.SelectionFill = myBrush;
            glSelected.SelectionPen = myPen;
            glSelected.DrawVertexes = false;
            _map.MapGraphicLayers.Add(glSelected);
            ObservableCollection<SchoolModel> schools = new ObservableCollection<SchoolModel>();
            try
            {
                if (selectedDataTable != null && selectedDataTable.Rows.Count > 0)
                {
                    FeatureDataRow featureRow;
                    for (int i = 0; i < selectedDataTable.Rows.Count; i++)
                    {

                        featureRow = selectedDataTable[i];
                        // work with retrieved records
                        SchoolModel school = new SchoolModel();
                        //set values
                        school.SchoolName = featureRow["NAME"].ToString().Trim();
                        school.StCtyFips = featureRow["STCTYFIPS"].ToString().Trim();
                        if (featureRow["LABEL_FLAG"] != DBNull.Value)
                            school.LabelFlag = Convert.ToInt32(featureRow["LABEL_FLAG"]);
                        //set spatial geometry
                        Geometry m_geometry = featureRow.Geometry;
                        ESRI.ArcGIS.Mobile.Geometries.Multipoint mapPoint = m_geometry as ESRI.ArcGIS.Mobile.Geometries.Multipoint;
                        school.SchoolGeometry = m_geometry;
                        //add to graphic
                        glSelected.GeometryBag.Add(m_geometry);
                        //add to list
                        schools.Add(school);
                    }
                    SchoolList = schools;
                }
                selectedDataTable.Dispose();
            }
            catch (Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Error in SignSupport Query", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
                //throw;
            }
        }
        private FeatureLayer getFeatureLayer()
        {
            //get access to map layer 
            FeatureLayer flayer = MobileCache.Layers[FeatureLayerName] as FeatureLayer;
            return flayer;
        }
        #endregion

The SpatialQuerySchools method will accept the selection envelope. The LoadSchoolResults gets a reference to our selected layer and sets up the graphic layer that will show the highlight features on map. It then runs the query and loops through results to first add them to a new instance of a SchoolModel which are then added to the ObservableCollection SchoolList as well as to the graphic layer.

Switching back to MainWindow.xaml, add two buttons like so for starting and clearing map selection:

<Button Content="Select Schools" Height="24" HorizontalAlignment="Left" Margin="10,52,0,0" Name="btnSelect" Opacity="0.6" VerticalAlignment="Top" Width="100" Command="{Binding SelectingSchoolsCommand}"/>
        <Button Content="Clear Selection" Height="24" HorizontalAlignment="Left" Margin="10,82,0,0" Name="btnClear" Opacity="0.6" VerticalAlignment="Top" Width="100" />

Notice in btnSelect instead of using an event (like Click or MouseDown) we are using a bound Command. This is another cool feature of WPF that sets the MainWindowViewModel to handle the event. Now let’s switch to MainWindowViewModel and set up our SelectingShoolsCommand with the following private class create inside the MainWindowViewModel:

private class SelectSchoolsCommand : ICommand
        {
            #region ICommand Members
            readonly MainWindowViewModel _mv;
            public SelectSchoolsCommand(MainWindowViewModel mv)
            {
                _mv = mv;
            }
            public bool CanExecute(object paramenter)
            {
                return true;
            }
            public event EventHandler CanExecuteChanged;
            public void Execute(object paramenter)
            {
                _mv.setupSelectTool();
            }
            #endregion
        }

Notice how this class is initiated with a reference to the instance of MainWindowViewModel. Next, add in this code snippet to create instance of this command that is exposed and hence bindable:

#region commands
        private ICommand _selectingSchoolsCommand;
        public ICommand SelectingSchoolsCommand
        {
            get
            {
                if (_selectingSchoolsCommand == null)
                    _selectingSchoolsCommand = new SelectSchoolsCommand(this);
                return _selectingSchoolsCommand;
            }
        }
        #endregion

Before I show the code for the setupSelectTool, let’s move IsDesignMode and getAppPath from MainWindow.xaml.cs to MainWindowViewModel because we will be making calls to it from our ViewModel. You will have to update any references to getAppPath in MainWindow.xaml.cs to _mainWindowVM.getAppPath() or you will get compile errors. Now we can move on by adding following code snippet to MainWindowViewModel:

private void setupSelectTool()
        {                        
            if (selectionLayer != null)
                Map.MapGraphicLayers.Remove(selectionLayer);
            //setup selection layer
            selectionLayer = new GraphicLayer();
            selectionLayer.DrawVertexes = false;
            Map.MapGraphicLayers.Add(selectionLayer);
            selectionLayer.GeometryBag.Add(new ESRI.ArcGIS.Mobile.Geometries.Polygon());
            selectionLayer.SelectedGeometry = selectionLayer.GeometryBag[0];
            m_lastNavigatioMode = Map.CurrentNavigationMode;
            Map.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.None;
            envelopeSketchTool = new EnvelopeSketchTool();
            envelopeSketchTool.AttachToSketchGraphicLayer(selectionLayer);
            envelopeSketchTool.SketchCompleted += doneSketch;
        }
        private void doneSketch(object sender, EventArgs e)
        {
            Envelope env = envelopeSketchTool.Envelope;
            if (env.XMax == env.XMin && env.YMax == env.YMax)
            {
                env.XMax += 25;
                env.XMin -= 25;
                env.YMax += 25;
                env.YMin -= 25;
            }
            //leave on until collapse or pan turn on
            if (envelopeSketchTool != null)
            {
                Map.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.Pan;
                if (envelopeSketchTool != null)
                    envelopeSketchTool.DetachFromSketchGraphicLayer();
            }
            //check if need to setup ResultsViewModel
            if (MyResultsViewModel.Map == null)
                MyResultsViewModel.Map = _map;
            if (MyResultsViewModel.MobileCache == null)
                MyResultsViewModel.MobileCache = MobileCache;
            if (MyResultsViewModel.FeatureLayerName == null || MyResultsViewModel.FeatureLayerName.Length < 1)
            {
                string xmlPath = getAppPath() + "Settings.xml";
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlPath);
                XmlNodeList elemList;
                elemList = xmlDoc.GetElementsByTagName("selectLayer");
                MyResultsViewModel.FeatureLayerName = elemList[0].InnerXml;
            }
            MyResultsViewModel.SpatialQuerySchools(env);
        }

In MainWindow.xaml.cs Windows_Loaded, we set the Map and MobileCache properties so in setupSelectTool and doneSketch you see we are making changes to the map that will be reflected when you interact with the application. setupSelectTool sets up the graphic that will handle the select draw rectangle affect on the map. It also sets the SketchCompleted event to doneSketch. doneSketch first checks if this a single click selection and makes a default envelope. It then checks if ResultsViewModel map, mobilecache and FeatureLayerName have been set- if not- sets them. FeatureLayerName is the value we set in the selectedLayer tag in Settings.xml. The final thing is it calls the SpatialQuerySchools method from the instance of ResultsViewModel.

Ok, I know your head must be spinning with all this code. Go ahead and compile, but do not run- you just want to see if you have any errors. The last thing we need to add in is how the ResultsViewControl opens and closes.  And I am going to show you through using Storyboards and Event Triggers in MainWindow.xaml. Add the following between the Window header tag and your Grid tag in xaml:

<Window.Resources>
        <Storyboard x:Key="OpenResults">
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="resultsView">
                <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
        <Storyboard x:Key="CloseResults">
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="resultsView">
                <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="ButtonBase.Click" SourceName="btnSelect">
            <BeginStoryboard x:Name="OpenResults_BeginStoryboard1" Storyboard="{StaticResource OpenResults}"/>
        </EventTrigger>
        <EventTrigger RoutedEvent="ButtonBase.Click" SourceName="btnClear">
            <BeginStoryboard x:Name="CloseResults_BeginStoryboard" Storyboard="{StaticResource CloseResults}"/>
        </EventTrigger>
    </Window.Triggers>

If we look at the Storyboards, OpenResults and CloseResults just set the visibility value on our instance of ResultsControlView we named resultsView. The triggers wait for a click event and depending on from which button (btnSelect or btnClear) it will tell which Storyboard to run. This demonstrates how you can control simple UI/View behavior right in xaml and not in xaml.cs. And this is a very simple example. You can have very elaborate Storyboards or use style triggers with multi data triggers.

Now go ahead and compile and run. Play with the select tools and notice how bindings trickle down to populating the datagrid with your selection.

Congratulations- you survived your first MVVM project. I throw a lot of new terms/concepts out there. Googling on any one of them will return a wealth of information for you to explore further. If you are really feeling ambitious, go back and refractor the rest of the code into MVVM. Happy coding.

Advertisements

Esri Mobile 10 SDK and WPF Beginner’s Tutorial

WPF stands for Windows Presentation Framework and we are going to see how quick it is to utilize Esri’s Mobile 10 SDK’s new ability to work in WPF. In this blog I am not going to go into depth about WPF except to say it is Microsoft’s (MS) equivalent to Adobe AIR. Say goodbye to WinForms and welcome to a whole range of slick animations, affects and all that other jazzy stuff you now expect from any Silverlight or Flex web applications. But with this blog we are just going to focus on the nuts and bolts of quickly standing up a mobile application designed for a tablet that runs a Windows operating system. Plus I will point out some gotchas I stumbled across along the way.

Prerequisites:

  • MS Visual Studio 2008 sp1 or higher (I personally prefer 2010- more built in features I make use of)
  • Esri Mobile 10 SDK (make sure and run the uninstaller application first to identify any conflicts with prior installed versions of ArcGIS products)
  • ArcGIS 10 (optional- but need if you want to use geoprocessing tools to create mobile caches)
  • Created mobile caches
  • Running mobile map service (unless you want to skip the sync part)

Map Tutorial Application

Once the prerequisites are installed, open Visual Studio and create a new project.  In the new project dialog, select the WPF Windows Application template. For this tutorial, I chose to go down the path of C# (sorry you VB.NET programmers- but just Google a translator. None of the code snippets will be too difficult to translate). Name your new project WpfMobileTutorial (or whatever you want) and click OK. When your new project solution opens you will see two files- App.xaml and MainWindow.xaml. Expanding them and you see their corresponding cs files. For this tutorial we will ignore the App.xaml file. The only thing I will point out is in App.xaml you can set what xaml file in your project is the start up window-  StartupUri=”MainWindow.xaml”

Now let’s pull in our Mobile SDK. Right click References folder and select to Add References. Add ESRI.ArcGIS.Mobile. If you had used Mobile 9.3.1 SDK or earlier you are probably used to going to the Toolbox window and seeing your Esri controls like map. There is a bug in the current release of this SDK and you will not see them in Toolbox but don’t despair- there are there. We just need to add them the old fashion way before we had fancy dandy IDEs that did all that work for us. First add the reference to the SDK to the Window header tag like so:

<Window x:Class="WpfMobileTuturial.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:esrimap="clr-namespace:ESRI.ArcGIS.Mobile.WPF;assembly=ESRI.ArcGIS.Mobile"  
        Title="MainWindow" Height="450" Width="650">

Add the following line inside your Grid container.

<Grid>
<esrimap:Map x:Name="map1"  />
</Grid>

You could compile and launch but nothing fun will happen- we need to add the cache to the map. Now, I have done Flex development and I love the config files they use so things can be changed without recompiling. So, I mimic that by adding an xml file to my project that I call Settings.xml. You can setup how you want but this is the format I typically use to store my cache information:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <cacheLocations>
    <operationalCacheLocation>C:\Caches\OperationalCache\MobileCache</operationalCacheLocation>
    <baseMapCacheLocation>C:\Caches\BaseMapCache\Layers</baseMapCacheLocation>
  </cacheLocations>
  <mobileServiceInfo>
    <mobileServiceURL>http://serverTutorial/ArcGIS/services/Panhandle/tutorial/MapServer/MobileServer</mobileServiceURL>    
  </mobileServiceInfo>
</configuration>

For now let’s focus on what is between the cacheLocations tags. Most mobile applications will have separate caches. One for the stuff they plan to edit and sync- operationCacheLocation tag. And the base stuff used for reference and not typically synced- baseMapCacheLocation tag.  OK- back in MainWindow.xaml add the following event to the Window header tag Loaded=”Window_Loaded”. Switch over to MainWindow.xaml.cs and add the following code:

MobileCache mobileCache;
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {            
                       string xmlPath = getAppPath() + "Settings.xml";
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(xmlPath);
            XmlNodeList elemList;
        }
        public static bool IsDesignMode()
        {
            return (System.Diagnostics.Debugger.IsAttached);
        }
        public static string getAppPath()//make sure return with trailing \
        {
            bool check = IsDesignMode();
            if (check)
            {
                return @"C:\Projects\WpfMobileTuturial\WpfMobileTuturial\";
            }
            else
            {
                return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"\";
            }
        }
      private MobileCache getMobileCache(String cachePath)
        {
            MobileCache m_mobileCache = new MobileCache();
            m_mobileCache.StoragePath = cachePath;
            m_mobileCache.Open();
            return m_mobileCache;
        }

Let’s go over what is going on here. First, if you had done mobile development prior to 10 MobileServices was replaced with MobileCache. But be warned- it is not an exact switch out between them. So, I would try and forgot how MobileServices worked in 9.3.1 and treat MobileCache as something new you are learning. The helper methods getAppPath() and IsDesignMode() just do some of the leg work of finding where Settings.xml is- if debugging I did go ahead and hardcode the path but if it is installed I just pull out it of Assembly.  I will discuss getMobilCache later. Let’s now add the code to our Window_Loaded method for adding the caches after the line XmlNodeList elemList;

elemList = xmlDoc.GetElementsByTagName("baseMapCacheLocation");
            string streetCache = elemList[0].InnerXml;
            if (streetCache != null && streetCache.Length > 0)
            {
                //rasterdata- local Tile Map Cache
                TileCacheMapLayer mpLayer = new TileCacheMapLayer(streetCache);
                mpLayer.Name = "Base Tile Cache";
                mpLayer.Open();
                map1.MapLayers.Add(mpLayer);
            }
            elemList = xmlDoc.GetElementsByTagName("operationalCacheLocation");
            string localCache = elemList[0].InnerXml;
            if (localCache != null && localCache.Length > 0)
            {
                mobileCache = getMobileCache(localCache);
                //update the cache
                elemList = xmlDoc.GetElementsByTagName("mobileServiceURL");
                string mapurl = elemList[0].InnerXml;
                int index = 0;
                //map1.MapLayers.AddRange(mobileCache);
                //this method AddRange doesn't always behave as expected so here is my work around- make a renderable mobilcachemaplayer for each layer
                //and then add to WPF map
                ReadOnlyLayerCollection clc = mobileCache.Layers;
                int iCnt = mobileCache.Layers.Count;
                for (int i = 0; i < iCnt; i++)
                {
                    Layer layer = mobileCache.Layers[i];
                    MobileCacheMapLayer mobileCacheMapLayer = new MobileCacheMapLayer((MobileCacheLayer)layer);
                    map1.MapLayers.Insert(index, mobileCacheMapLayer);
                    index++;
                }               
            }

So, all this code snippet does is first get the value from our tag baseMapCacheLocation and uses one of the new features of 10;using Tile Caches created from a Tile Map Service. You just need to copy _alllayers folder, conf.cdi and conf.xml locally and just have your Settings.xml store where they  are saved locally.

The next part adds the operational layers to the MobileCache. And now you see us utilize the getMobileCache helper method. Just pass in the path location grabbed out of the Settings.xml and all this method does is initialize the mobile cache. Next we add these layers to the map. Here you will notice some comments in the code. I have had very inconsistent luck with using the map’s method AddRange. To be safe I always do the long way of casting the layers into MobileCacheMapLayer and add that to the map. Feel free to do whichever.

At this point you can compile and run and see a new window open showing your cache data..

Cool. Now let’s add some navigation. In MainWindow, drag three button controls onto your window and position where you would like them. Here you could get real fancy and add in some wicked style/animation affects. What I will do is just change text to Pan, Zoom In, and Zoom Out respectfully. I know, lame, but you can stop here and play with styling and if you have Expression Blend it is even easier. Here is a cool one to try- http://www.codeproject.com/KB/WPF/glassbuttons.aspx.

But back to the boring stuff let’s add the tiny bit of code snippets for adding in basic navigation:

private void btnPan_Click(object sender, RoutedEventArgs e)
        {
            map1.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.Pan;
        }
        private void btnZoomIn_Click(object sender, RoutedEventArgs e)
        {
            map1.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.ZoomIn;
        }
        private void btnZoomOut_Click(object sender, RoutedEventArgs e)
        {
            map1.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.ZoomOut;
        }

And hooking our WPF button controls to our methods add Click events to each of your buttons and point to corresponding method (e.g. Click=”btnPan_Click”). Now compile and run and you can navigate around your map by panning and zooming in and out.

Since this is a mobile application the last thing I will cover here is synchronization- especially since the process changed in 10. Let’s add a fourth button to our application. Again, I am not going to utilize any wicked cool affects and just change the text to ‘Sync’.  And put the following method in MainWindow.xaml.cs:

private void btnSync_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                string xmlPath = getAppPath() + "Settings.xml";

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlPath);

                XmlNodeList elemList;

                elemList = xmlDoc.GetElementsByTagName("mobileServiceURL");
                string url = elemList[0].InnerXml;

                MobileServiceConnection mobileServiceConnection1 = new MobileServiceConnection();
                //link the mobile classes to put the data into the display 

                mobileServiceConnection1.Url = url;

                ReadOnlyLayerCollection clc = mobileCache.Layers;
                int iCnt = mobileCache.Layers.Count;
                for (int i = 0; i < iCnt; i++)
                {
                    //synch just be feature test
                    FeatureLayer featureLayer = mobileCache.Layers[i] as FeatureLayer;
                    FeatureLayerSyncAgent featLayerSync = new FeatureLayerSyncAgent(featureLayer, mobileServiceConnection1);
                    FeatureLayerSyncResults featLayerResults = new FeatureLayerSyncResults();
                    featLayerResults = featLayerSync.Synchronize() as FeatureLayerSyncResults;
                    IDictionary<int, string> synErrors = featLayerResults.UploadedFeaturesErrors;
                    if (synErrors.Count > 0)
                    {
                        foreach (KeyValuePair<int, string> kvp in synErrors)
                        {
                            int v1 = kvp.Key;
                            string v2 = kvp.Value;
                            System.Windows.MessageBox.Show("Error uploading " + featureLayer.Name + ". Contact administrator. " + "Key: " + v1.ToString() + " Value: " + v2);
                        }
                    }
                    if (featLayerResults.Exception != null)
                    {
                        Exception e = featLayerResults.Exception;
                        if (e.Message != null && e.Message.Length > 0)
                        {
                            System.Windows.MessageBox.Show("Error uploading " + featureLayer.Name + ". Contact administrator. " + "Message: " + e.Message);
                        }
                    }
                }
                return;
            }
            catch (Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Error in OpenCacheConnection", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
                return;
            }

Looking at the code you now see the mobileServiceURL tag come into play. The URL to the mobile map service I also like to put in the Settings.xml. This, makes moving things to another environment much easier. Once we pull out the URL we assign to MobileServiceConnection. Now there was something I noticed about the sync process that annoyed me- it will run and not return any errors automatically. You actually have to dive into either FeatureLayerSyncResults or MobileCacheResults. In the above code snippet I actually show how to loop through cache and sync each layer. Then pull out if there were any errors in UploadedFeaturesErrors or in Exception. Makes debugging sync issues so much easier. So, go ahead and compile again and if you have your mobile service set up go ahead and sync. If there are any errors you will get an alert stating the problem.

So, that ends the tutorial for getting up and running with Mobile 10 and WPF. I will be following this with a blog extending this tutorial to start incorporating aspects of the design framework MVVM and utilizing WPF binding capabilities. Also, if you would like to see how cool WPF can look check out our project showcase (http://showcase.gisinc.com/) and look for the Panhandle Energy Pilot Patrol Application or City of Appleton Sign Retro-reflectivity Solution projects.

Realizing I am an Old Dawg: Thoughts from a CTO of a Technology Company

So about 15 years ago I am sitting in the hockey locker room after a game drinking a beer. The guy sitting next to me is in his mid-fifties. I think back to the game as I say, “Hey nice goal tonight, sure hope I can play like that when I’m your age”.  In my mind I was being respectful and complimentary. Now 15 years later some smart-ass 35 year old kid is sitting next to me and I’ll be damned if he doesn’t say the same thing to me (of course I see more of a smirk on his face then I had on mine).  I can tell you it doesn’t sound as good on the receiving end as I had meant it on the delivery side. What I really heard the kid saying was, “aren’t you about ready to hang up your skates?”  Strangely enough, as my hair continues to thin and grey, I find a similar pattern occurring in my day to day role as the CTO. The younger ones, which is pretty much everyone else in the company now, full of energy and ideas and impatient to get the latest and greatest technology, always want to know why we aren’t doing this or why we aren’t doing that. The culmination of these on- and off-ice events have started me thinking a little deeper about what it means to become an Old Dawg and how my expectations and perspectives have changed along the way. This introspection has made me focus on the following question: am I slowing the technology development and adoption in the company or I am truly guiding the technology direction of the company through seasoned experience? Allow me to share my thoughts as I wrestle with this question.

I remember, as I was preparing to start my doctoral research, sitting in my advisors office passionately trying to convince him that I HAD to go to a GIS training class being held at Texas A&M to learn the “state of the art” technology in GIS.  He was somewhat dubious but I think looking back, he did not want to curb my enthusiasm and agreed. The training was being run by the National Park Service on a home grown system called SAGIS.  Anyone ever hear of that one?  I never did again either.  Now I did learn a good bit about the underlying processes in line command GIS, but as for SAGIS itself, it was a waste of time.  I started to learn two things from this (this lesson was repeated several times in my career); first, I might want to put a bit of a governor on my own excitement when making decisions like that, and second, to pay attention to your advisors’ words and even nonverbal signals. They have been there and usually truly do understand.

Another lesson I have picked up on the way is that the experts don’t always know what they are talking about (a central theme in the book The Black Swan by Nassim Taleb, which is why it resonated with me).  Some of you might remember when ESRI released MOIMS, followed shortly thereafter by ArcViewIMS, and then finally IMS.  Despite all of the experts touting how MOIMS was going to take the industry by storm because it was “just simple vb” so your existing programming staff could develop with it, it never got a foothold. The pitch for ArcViewIMS was that “your GIS Analysts could publish internet applications.” Alas, that did not stick either, but not without significant investment by the organizations I was working with at the time.  Now IMS did hit the mark and to this day seems to provide some capabilities and ease of use that ESRI is struggling to match with the AGS API technologies – damn those map services. Technologically there really wasn’t any reason the first two systems didn’t take hold. Looking back I think that it was more of a disconnect between the user base and the technology itself (a primarily desktop and client-server community base struggling with a developing internet solution). None of this was predicted by the industry experts.

Gartner Research published the Hype Cycle concept in 1995, which charts out the progression of an emerging technology through its lifespan. http://www.ask-force.org/web/Discourse/Linden-HypeCycle-2003.pdf  I think this chart helps put into context the experiences I have described. As a new technology hits the street the community gets over-excited and pressure to try it or adopt it is high – especially by the techno-curious. The problem is that if you get caught at the peak of the hype and it turns out it is all hype and the technology is no good, for whatever reason, you have lost money, time, and credibility in one fell swoop.

So as I have progressed through my career evolving from the techno-curious guy getting caught on the false upward slope, through the skeptical road-worn warrior that appears to be the curmudgeon that likes to say “no”, I have subconsciously developed a mental attitude towards new technology that I think became clear to me as I read The Black Swan. Taleb describes the mindset of a skeptical empiricist which to simplify for me means, believe the facts just don’t trust them.  So a couple of concepts in that short definition are key. Facts have to be truly validated as facts and not opinion of the over-enthused. Belief and Trust come from understanding where the facts have been derived.  You can believe that a piece of information was actually truly derived under the circumstances, but can you trust that the same information would be valid if the circumstances were repeated or slightly changed.  And what would be the impact if the answer is no.

This Empirical Skeptic concept is at the heart of why you might have been asked by your leadership team to provide requirements and/or develop a business case for the technology you are recommending.  They are trying to ensure a logical thought process has been followed, the available information has been vetted appropriately, and the benefits of success and risk of failure have been thought through. These requests, at times, may seem like a wet blanket thrown over the fire of enthusiasm; I can assure you that is not the intent.

The reality is that leading a technology company presents some real challenges in balancing technology adoption with fiscal constraints and employee satisfaction. While we want to be constantly evolving to keep pace with technology and keep employees intellectually challenged and satisfied, we must balance that with making full use of the technology investments we have made.  I can’t tell you how many times I have championed implementation of a specific technology based on recommendations from a techno-curious employee, only to be told (by the same employee) within days of implementation that there is this new great tool out there we need to be using instead.

So let me go back to my hockey metaphor to wrap up my thoughts around all of this.  As I have gotten older, I have transformed how I am a productive team member on the ice. I am not the one scoring the goals, in fact just the opposite; I am a defenseman where my current skills are more appropriate.  One of the most important roles as a defender is to make the first break out pass – get the puck to the skills guys leaving your own zone at full speed. That’s how I see my role as a CTO, get the technology to the guys that know how to use it best. But to do this you have to be able to see the entire ice so you don’t throw a pass up the middle right to an opposing forward, or perhaps worse, right to your guy just as he is about to get crushed by the other team’s defender.  I am responsible on the ice and at the office for setting up my teammates for success – not failure.

 

 

 

 

 

 

No, not me. This is Charles Schulz (Peanuts Cartoonist) who played competitive hockey until his death at 78.

GISi ArcGIS for Local Government Initiative

As we finish the final touches of GISi’s State & Local Government Business Plan, I couldn’t help but reveal one of our goals as we are planning to launch a campaign this week at the 2011 Esri International User Conference and we already have a success story (Effingham County, GA). One of our goals is to align with Esri’s ArcGIS for Local Government initiatives.  The strategies we have laid out will help our customers successfully implement GIS in cities, counties, and other local authorities.  This is a relatively new concept; the ArcGIS for Local Government includes a series maps and applications built on a common information model that are designed to work together across various departments.  For more information, visit:http://resources.arcgis.com/content/local-government/about

Our objective is to help cities and counties deliver value to their organizations by applying their geographic information to support daily government activities – that is, to run their operations more efficiently, to communicate more effectively, to save money, to engage with their citizens, and to understand, plan, and make improvements in their communities.

The strategies & actions to accomplish this goal are designed to be focused on the local government industry verticals and work more closely with Esri to build complimentary applications that are based on the ArcGIS System and use the ArcGIS for Local Government information model.  We are also focusing on implementation services of the ArcGIS System, services to install, configure, and extend ArcGIS for Local Government (maps and applications, information model, etc.).

The GISi team is looking forward to being a big part of the Esri ArcGIS for Local Government Initiative.