Sunday, December 4, 2011

STUDY : USING the ArcGis API for Microsoft Silverlight/WPF to create an ArcGis Database edit solution.

This is my test for how you can use the ArcGis Silverlight API to create powerful  web editing applications. In the past blogs I already outlined how I started this project. The most important before starting of a Silverlight project of this size is to make clear how the architecture of the project should look.

To have an architecture that is modular to keeps track of the rapid changes in the ArcGis Silverlight API’s, you must use a framework and / or pattern that can help you in this challenge.

To be able to write good Silverlight application, Microsoft developed the pattern MVVM (Model View ViewModel). Without this pattern you will soon create complex software modules that will be difficult to maintain. The software development cost is only a fraction of the TCO (Total Cost of Ownership). To reduce the TCO it is important that the software product is highly maintainable.

To help the developer using MVVM, different frameworks has been publically available. One important available framework is PRISM 4.0 developed  by Microsoft.  The framework simplify the use of the pattern MVVM in Silverlight applications. In my Silverlight application I used PRISM 4.0 with MEF as the container library.

PRISM 4.0 has support for different container libraries like MEF and UNITY. Other container libraries can also be used with PRISM 4.0. I decided to use MEF because I like the implementation based on the use of class and method attributes through the use of reflection.

The use of container libraries serves for the implementation of IOC (inversion of control) and dependency injection. The latter is used to expose different GIS functions in the ViewModel classes.

 The architecture of the application has the following layout :

 Application architecture

 Module Architecture



To maintain a flexible application, the configuration is maintained in XML files as done by the Silverlight ArcGis viewer. To create a general approach of the map editor, all configurations are contained in a folder. The folder serves as parameter for starting the map editor. The URL for starting the application is:

 http://<<website>>/<virtual map>>/WebmapTestPage.aspx?Application=Demo

 or



In the current version I only support the languages English and Dutch.

Some limited testing can be done with the Url below:


You can download the whole demo application through the link below. To be able to run the application you need to deploy the contents towards a virtual map of your Internet Information Server. The application can run on a local PC with the Microsoft Internet Information Server.
Deploy alse the crossdomain and clientaccesspolicy files on the Internet Information Server.
 

For the user manual I restrict myself to a quick guide covering most of the exposed tools and menus. Below is the link to the pdf file.





Saturday, September 3, 2011

MAKE ARCGIS SILVERLIGHT CONTROLS MVVM ENABLED - PART VI

Introduction


Before tackling the more advanced ArcGis Silverlight controls as the Editor, I will first look how we can enhance the ArcGis Silverlight controls so that they fit into the MVVM pattern.

Sometimes You will encounter ArcGis controls or classes with properties that cannot be set from within your ViewModel. When you try it, you will probably end up with a null pointer exception. The reason of this is that in order to have properties bound to you ViewModel, these properties must be inherit from the dependency property object. This is not always the case for the properties of an ArcGis Silverlight class, probably due technical reasons. The next article explains how we can enhance the class so that we can set non dependency properties from within the ViewModel pattern.

The dependency issue


If you have a property to be set, but you cannot do it from the ViewModel, you can add a new property to the class that inherit from the dependency object. You can use this property to set the value for a property that is not inherit from the dependency object. Let’s take an example of how we can do this by using the MeasureAction class from the ArcGis Sivlerlight API.

When you look at the examples from ESRI, you can use the measure action as

 <Button Content="Measure"  >
     <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
         <esri:MeasureAction                                 
           AreaUnit="SquareMiles"
           DisplayTotals="{Binding Totals}"
           DistanceUnit="{Binding Distance}"
           MapUnits="Meters"
           MeasureMode="Polygon"                                  
           FillSymbol="{StaticResource DefaultFillSymbol}"
           TargetName="MyMap"/>
        </i:EventTrigger>
     </i:Interaction.Triggers>
 </Button>

 When we try to replace the property TargetName by the following expression :

             TargetName = “{Binding MyMap}”

You will get a null pointer exception, and that is because the property ‘TargetName’ is different implemented as the other properties. You can see this in the ESRI documentation shown next.




































You can see that properties as AreaUnit, MapUnits inherit from the dependency objet. When you look at the TargetName, this is inherited from an other object, in the case of the MeasureAction it is a Microsoft defined property. ESRI could solved it by implementing the map control by an own property inherit from the dependency object as this is the case for the Legend control. But for some technical reason this was not done.

To solve the problem,  simply add a new property ‘MapMeasure’ to the MeasureAction  that will contain the map control.

The map dependency property


Create a new class in the project with the MeasureAction that implement the dependency project. The code below show how this can  be done.

using System.Windows;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Actions;

namespace MyToolbar.Helper
{
  public class MapContent
  {
    public static readonly DependencyProperty MapMeasureProperty =
  DependencyProperty.RegisterAttached("MapMeasure"typeof(Map),
  typeof(MapContent), new PropertyMetadata(OnMapMeasureChanged));

    public static Map GetMapMeasure(DependencyObject depObject)
    {
      return (Map)depObject.GetValue(MapMeasureProperty);
    }

    public static void SetMapMeasure(DependencyObject depObject, Map value)
    {
      depObject.SetValue(MapMeasureProperty, value);
    }

    private static void OnMapMeasureChanged(DependencyObject depObject,
      DependencyPropertyChangedEventArgs e)
    {
      MeasureAction measureAction = depObject as MeasureAction;
      measureAction.TargetObject = GetMapMeasure(measureAction);
    }
  }
}

This is a typical implementation of a dependency property. The only code that you has to add is the code that is in the method On<property>Changed. In the case of the MeasureAction, the TargetObject is initialized with the map control. You can now in all modules activate the measure tool.

In the real world the XAML code will now look like  

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Click"
            <esri:MeasureAction                                 
                                    AreaUnit="SquareMeters"
                                    DisplayTotals="True"
                                    DistanceUnit="Meters"
                                    MapUnits="Meters"
                                    MeasureMode="Polygon"                                  
                                    FillSymbol="{StaticResource DefaultFillSymbol}"
                                    mapMeasure:MapContent.MapMeasure="{Binding MapControl}"
                        />
            </i:EventTrigger>
</i:Interaction.Triggers>

As you can see, now we have made the MeasureAction ViewModel enabled.

When at a given time the ArcGis Silverlight API add a new property ‘Map’ to the MeasureAction, you only has to change the property in the XAML, no code change is required. This is another example of the power of using the MVVM pattern.

Monday, August 22, 2011

LOGIN PATTERN USING MVVM AND PRISM IN COMBINATION WITH MAPSERVICES PART V

Introduction


Before tackling more complex functionality as web editing and “GeoProcessing”, we will introduce a login pattern for use with ArcGis Silverlight API.
When using the Silverlight API of ESRI with maps, having a consistent display with a login screen on a single web page can be painful. As long as the user has not been authenticated you can not display the map content. However due to the asynchronous operation of the map control, once you set the layer contents, the map becomes displayed. In the solution outlined hereafter we will respect the asynchronous character of Silverlight.
In PRISM you can create a dependencies of modules. The idea is to create a login module that will be depended from for all other modules.  

Authentication Framework


The authentication framework consists of two components:
  • Authentication module.
  • Authentication service.
The authentication module will handle all the input related to the login procedure. The module will use the authentication interface to save and mark the successful login.
The authentication service is the engine of the login procedure. The interface is responsible for the authentication and as service will also provide functionality to other modules when information is needed about function authorizations for the user logged in.
In case no authentication is required, you can use a dummy module that sets the authentication to true.  Doing this, you can later, by simple modifying the module catalog creating a login procedure without modifying your code.
Another situation is when the application runs in a trusted network environment. In this case you can use the user credentials as in an authentication module. This can be done through a WCF service that retrieves the user credentials through the instruction:
WindowsIdentity winIdent = (WindowsIdentity)HttpContext.Current.User.Identity;
Next we will treat in more detail the case of doing authentication through a login form.

Authentication Module


The authentication Module will consists of the visual aspects of the login procedure. To restrict the user interface, the login view consists of a ChildWindow initiated from the authentication module class.

The authentication module is responsible for sending a message to other modules of the successful login of a user. In case of not successful the startup op de Silverlight application is aborted. By using the HTTPRequest you could close the web page.

The login view consists of a username and password as input, a textbox that contains error messages and buttons to login or cancel the login process.

 


To close the ChildWindow, we must put code behind the two buttons. For the login button this means:

private void OKButton_Click(object sender, RoutedEventArgs e){
 if (authenticationViewModel.LogonCheck())
  this.DialogResult = true;
}


The window is created in the module class.

public void Initialize()
{
  logonEvent = 

   eventAggregator.GetEvent<CompositePresentationEvent<LogonData>>();
  ChildWindow childView = 

   container.GetExportedValue<AuthenticationChild>();
   childView.Closed += ChildView_Closed;
   childView.Show();
}


Control is handed back to the module class as soon as the login view is closed. To signal the success of the login, the module uses the EventAggregator service to publish a successful login. The modules that depend on the login process can then take action to complete there process. This is the case with the map module, which will wait to initialize the layer property until the login has been completed successful.

private void ChildView_Closed(object sender, EventArgs e)
{
  if (authentication.IsLoggedOn())
  {
   LogonData logonData = new LogonData() { LoggedOn = true };
   logonEvent.Publish(logonData);
  }
}


On the other module site (map module) you need to handle the successful login event by subscribing to the event:


if (eventAggregator != null)
  eventAggregator.GetEvent<CompositePresentationEvent<LogonData>> ().Subscribe(OnLogonChanged);

public void OnLogonChanged(LogonData logonData)
{
  configuration.SetFinishedEvent(new EventHandler

  (ConfigurationInitialised));
  configuration.GetConfiguration();
}


This was initially done in the module constructor when no login was required.

Authentication Service


This must be the engine behind the authentication process. By creating an authentication service, every module has access to information proper at the user logged in. As with other services, the functionality is exposed by means of an interface that was injected in the different module classes or view models that need authentication data. Typical information that will we maintained by this service is:

  • UserName and user detail information (full name, email, …).
  • Groups belonging to.
  • User credentials (Create, Read, Update and Delete for certain data).


The bootstrapper creates the authentication service and makes it available through the container:

private Authentication authentication = new Authentication();

this.Container.ComposeExportedValue<IAuthentication>(this.authentication);


The interface IAuthentication could be similar than the one showed below, and should be created in a common Silverlight project:

public interface IAuthentication {
  Boolean IsLoggedOn();
  string UserName();
  Boolean IsActionAllowed(string operation);
  Boolean IsActionAllowed(CRUD crud, string table);
  Boolean LogOn(string username, string password);
}
Data concerning the user logged in, are maintained in the authentication class and make accessible through the authentication interface:

private string userName;
private bool loggedOn = false;

public bool IsLoggedOn()
{
  return loggedOn;
}

public bool LogOn(string username, string password)
{
  boolean logOnOK = false;
…             

  // Validate through web service
  return logOnOK;
}

public string UserName()
{
  return userName;
}


 
By using dependency injection, view model and other classes have access to authorization data. Typical in the Authentication class, the LogOn method will access a web service for doing the authentication of the user. Because this is an asynchronous process, callback functionality must be foreseen to let the user wait until validation has been completed. This is a similar pattern we used a couple of times in the Gis operations.

In case no authentication process is required, you can create a dummy module that publishes a successful login.


Wednesday, August 10, 2011

CREATION OF COMMANDS AND TOOLS ADVANCED - PART IV

Introduction

Before we will move to more specific GIS functionality as geoprocessing and feature editing, I will first introduce some more complex commands and tools. In the real world there is a much richer set of tools required. In this part we will add some more advanced tools.

The first tool I will illustrate is the use of an inspect tool. Clicking on the map, we want to display the information of the feature.

 A second pattern I demonstrate is the use of the right click on the map to trigger some action. This pattern can be used to display a context menu or perform certain GIS action at the clicked location. This will illustrate the use of attached properties in combination with event triggers on the map control.

Inspect  feature action


At the toolbar, we add a new button ‘Info’ that will start a drawing operation for a point. At the end of the drawing action we display a new window with the attribute information of the feature.

The main issue here is how we will initiate a new window displaying once the information is found. Using the MVVM pattern there is little information about this kind of design patterns. Using the rules of MVVM, you cannot create a child window from within the ViewModel because it is stated that the ViewModel must not having acknowledge about the view.

The solution I created was the use of a new service ‘DialogManager’ that will initiate a child window displaying the information required by our tool. Communication between de ViewModel and the dialog service happens through the EventAggregator service. Later I will illustrate another technique for calling a dialog window from a ViewModel.

First we create a MVVM command that will initiate the spatial query. At the clicked event, the drawing is initiated.

private void OnInfoCommandClicked(object arg)
{
  currentCommand = "Info";
  gisOperations.SetCompleteDrawEvent(DrawComplete);
  gisOperations.SetDrawMode(DrawMode.Point);
}

When the drawing is complete, we start a spatial query in the same way we already executed an attribute query, but this time only geometry is passed to the GIS service. The return is a list of features.

// Info request (spatial query)
gisOperations.SetFinishedEvent(HandleResultQuery);
gisOperations.AttributeQueryTask_Async("""", gisOperations.GetBaseMapUrl(), 9, "", args.Geometry);

After obtaining the result objects, the results are published to the dialog service for displaying.

FeatureSet results = e.Results;
if (e.Results.Features != null && e.Results.Features.Count > 0)
  {
    List<SearchResult> searchResults = new List<SearchResult>();
    foreach (var item in results)
    {  
      SearchResult searchResult = new SearchResult()
             { AttributeValues = item.Attributes, 
               LayerName = "", 
               Geometry = item.Geometry };
      searchResults.Add(searchResult);
     }
     resultEvent.Publish(searchResults);

Now the dialog service comes in action. During the creation of the DialogManager in the bootstrapper, the subscription has been activated during the constructor of the service.

public DialogManager()
{
  this.MyEventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
  if (this.MyEventAggregator != null)
    this.MyEventAggregator.GetEvent<CompositePresentationEvent<List<SearchResult>>>()
    .Subscribe(ShowResultsDialog);
}

When the information arrives at the subscriber service, a new view is initiated for displaying the results. Doing this, the action in the ViewModel does not create directly another view and we can use a ViewModel concept for handling the display of the view.

public void ShowResultsDialog(System.Collections.Generic.List<SearchResult> results)
{
  ResultsView resultsView = new ResultsView();
  ResultsViewModel resultsViewModel = new ResultsViewModel();
  resultsViewModel.SetResults(results);
  resultsView.DataContext = resultsViewModel;
  resultsView.OverlayOpacity = 0;
  resultsView.Show();
}

The use of a dialog manager allows the handling of different requests for displaying information coming from a GIS operation using the MVVM pattern. For large projects a split of the dialog manager in different parts for dynamic loading could be useful.

This pattern is only useful when there is no post processing required at the OK button of the window dialog.  In some later part I will illustrate how some additional processing can be done when the OK button of the dialog window is pressed.

Zoom at a location through right click.


I will now add a new functionality to the map control. Here I  use MVVM attached properties to the map control and using an event trigger to capture the right mouse click event. To implement the right click I have to take into account the following situation:

·         The map control does not have an event defined for the right click.

·         Microsoft adds in Silverlight applications a standard right click event handler for allowing the configuration of the Silverlight.

The ESRI Silverlight API already delivers an out of the box behavior for the map control that keeps the extent when changing the size of the browser window.

To  have our own right click event on the map control, we need to make our right click event triggered  before Microsoft could take control for displaying the Silverlight configuration button. 

To handle the right click I add a trigger to the map control. This trigger is acting on the right mouse click down event. This event happens before the right mouse click up event and occurs before the right mouse click up event which is handled by Microsoft for the configuration of the Silverlight plug-in on the desktop.

<i:Interaction.Triggers>
  <i:EventTrigger EventName="MouseRightButtonDown" >
    <maphelper:RightClickMenu Command="{Binding HandleRightClick}" CommandParameter=""/>
  </i:EventTrigger>
</i:Interaction.Triggers>

First I create a MVVM command in the constructor of the ViewModel of the map control that will handle the processing for the right click event.

HandleRightClick = new DelegateCommand<object>(this.OnRightMouseClicked, this.CanRightMouseClicked);

In this command the final processing happens based on the location clicked. I then simply create an extent based on the point selected by adding an offset and perform a zoom operation.

private void OnRightMouseClicked(object arg)
{
  MapPoint point = arg as MapPoint;
  Envelope extent = new Envelope(point.X - 10000, point.Y - 10000, point.X + 10000, point.Y + 10000);
  gisOperations.ZoomTo(extent);
  return;
}

To finish the job, I implement a class derived from the TriggerAction class. When implementing the trigger action you need to override the following methods

  • Invoke
  • Onattached
I only use here the Invoke method. This method does the real work when the mouse right click is done.

In case of the map, I attached properties that contain the command and optional the command parameter and invoke parameter. In the example the parameters are not used.

public class RightClickMenu : TriggerAction<FrameworkElement>
{
  // Definition of dependency properties that could be usefull
  public static readonly DependencyProperty CommandParameterProperty =
    DependencyProperty.Register("CommandParameter", typeof(object), typeof(RightClickMenu), null);

  public static readonly DependencyProperty CommandProperty =
    DependencyProperty.Register("Command", typeof(ICommand), typeof(RightClickMenu), null);

  public static readonly DependencyProperty InvokeParameterProperty = DependencyProperty.Register(
   "InvokeParameter", typeof(object), typeof(RightClickMenu), null);

The invoke method is implemented as follow:

protected override void Invoke(object parameter)
{
  // Verify if it is a map
  MouseButtonEventArgs eventArgs = parameter as MouseButtonEventArgs;
  eventArgs.Handled = true;
  Map map = this.AssociatedObject as Map;
  if (map != null)
  {
    MapPoint mapPoint = map.ScreenToMap(eventArgs.GetPosition(map));
    ICommand command = this.ResolveCommand();
    if ((command != null) && command.CanExecute(this.CommandParameter))
    {
      command.Execute(mapPoint);
    }
  }
  return;
}

To eliminate the display of the Silverlight notice you must set the handling of the right mouse click event to ‘handled’. This stops the Microsoft handling of the right mouse up event.

  eventArgs.Handled = true;

A more general approach was done here to retrieve the MVVM command linked to the right mouse click. This approach allows the triggering of a command defined on another control, ex a button. This logic can be found in the ResolveCommand method.

private ICommand ResolveCommand()
{
  ICommand command = null;
  if (this.Command != null)
  {
    return this.Command;
  }
  // In the case the command is not defined within the triggger and you need to activate it, 
  // search is done for the context containing the command.
  var frameworkElement = this.AssociatedObject as FrameworkElement;
  if (frameworkElement != null)
  {
    object dataContext = frameworkElement.DataContext;
    if (dataContext != null)
    {
      PropertyInfo commandPropertyInfo = dataContext
         .GetType()
         .GetProperties(BindingFlags.Public | BindingFlags.Instance)
         .FirstOrDefault(
            p =>
            typeof(ICommand).IsAssignableFrom(p.PropertyType) &&
            string.Equals(p.Name, this.CommandName, StringComparison.Ordinal)
         );
      if (commandPropertyInfo != null)
      {
        command = (ICommand)commandPropertyInfo.GetValue(dataContext, null);
      }
    }
  }
  return command;
}

The above pattern can also be used to display a context menu instead of a direct action. Using the pattern of a dialog manager, you can display a menu with the needed action on the location clicked.


Tuesday, August 2, 2011

CREATION OF COMMANDS AND TOOLS - PART III

Creation of a Toolbar

To be able to illustrate the handling of ArcGis commands and tools, we will start from a toolbar. We create a new module that contain our new toolbar. We create a new module ToolbarModule with some buttons, the module is created in the same way as the TocModule.

The new visual layout of the application will be:

Region 3 contains the toolbar, region 4 is a spare region that we will fill with a view when needed.



GIS SERVICE


To make the ArcGis plumbing simple, we create a GIS operation class that will handle all low-level ArcGis functionality. Doing this, in most cases we do not need a Model for accessing data.

Because it is defined as a service, all use of the service is done by means of a shared interface IGisOperations. To make it available for all projects, it is put in our common Helper projects. The service is available through dependency injection in the ViewModel.

The interface could looks like this:

public delegate void MyResultsHandler(object sender, ResultsEventArgs e);
public delegate void MyDrawCompleteHandeler(object sender,ESRI.ArcGIS.Client.DrawEventArgs args);
public interface IGisOperations
{
  Map GetMa p();
  string GetBaseMapUrl();
  void SetMap(Map map,string baseMap);
  void AttributeQueryTask_Async(string whereValue, string whereField, string url,
  int layerID, string fieldType);
  void AttributeQueryTask_ExecuteCompleted(object sender,
  ESRI.ArcGIS.Client.Tasks.QueryEventArgs args);
  void AttributeQueryTask_Failed(object sender, TaskFailedEventArgs args);
  void SetFinishedEvent(MyResultsHandler finishedOperation);
  void ResetFinishedEvent(MyResultsHandler finishedOperation);
  void SetCompleteDrawEvent(MyDrawCompleteHandeler drawComplete);
  void ResetCompleteDrawEvent(MyDrawCompleteHandeler drawComplete);
  void ZoomTo(ESRI.ArcGIS.Client.Geometry.Geometry geometry);
  MarkerSymbol GetSelectionMarkerSymbol();
  LineSymbol GetSelectionLineSymbol();
  FillSymbol GetSelectionFillSymbol();
  void SetSelectionMarkerSymbol(MarkerSymbol symbol);
  void SetSelectionLineSymbol(LineSymbol symbol);
  void SetSelectionFillSymbol(FillSymbol symbol);
  void SetDrawMode(DrawMode drawMode);
  void MapZoom(string action, Geometry geometry);
}


Because all server Gis operations are asynchroon, you must always implement an event handling for the callback functionality.
All these methods have to be implemented in the class GisOperation. An instance of this class will be created in the bootstrapper. By means of the dependency injection, the interface will be made available to all classes that need some form of GIS processing. Almost always this will be done in the ViewModel of the modules.

In some cases I could be wise to split this class in different parts as not always all GIS functionality is required in an application. In this manner you could reduce the size of the xap file.

protected override void ConfigureContainer()
{
 
base.ConfigureContainer();  
  this.Container.ComposeExportedValue<IConfiguration>

   (this.configuration);
  
this.Container.ComposeExportedValue<IGisOperations>
   (this.gisOperations);
 
this.Container.ComposeExportedValue<CompositionContainer>
   (this.Container);
}


As you can see, the GisOperation service  works exactly as the configuration service.

CREATION OF A COMMAND


Use of ICommand


As for now there was little code behind the views. We must keep it like that, even when buttons and actions on these buttons must be handled. To make this possible, the MVVM architecture makes use of command classes that implement the ICommand interface.

To simplify the creation of commands, the PRISM framework adds a factory to simplify the creation of commands by means of the Delegate Command class.

The general structure of the creation of a command becomes:

ICommand MyCommand =
  new DelegateCommand<object>(Execute, CanExecute);

The delegates has the signature:
void Execute(object arg)
bool CanExecute(object arg)

Some controls derived from ButtonBase like Button has a command as property, enabling the use of the Command property and CommandArgument directly in xaml.

<Button Margin="0,0,0,0" Name="btnQuery"
  Command="{Binding QueryCommand}">
  <Image Source ="../Resources/i_search.png" />
</Button>


So to make actions happen we simply bind the command property to a property of the ViewModel. Together with the creation of the commands in the constructor of the ModelView we can bind an action to a button without putting an event handling in the code behind of our view.

This result in the following code in the ViewModel:
private ICommand _queryCommand;
public ICommand QueryCommand
{
  get { return _queryCommand; }
  set
  {
   _queryCommand = value;
   this.RaisePropertyChanged(() => this.QueryCommand);
  }
}
private ICommand _zoomOutCommand;
public ICommand ZoomOutCommand
{
  get { return _zoomOutCommand; }
  set
  {
   _zoomOutCommand = value;
   this.RaisePropertyChanged(() => this.ZoomOutCommand);
  }
}
private ICommand _zoomInCommand;
public ICommand ZoomInCommand
{
  get { return _zoomInCommand; }
  set
  {
_  zoomInCommand = value;
   this.RaisePropertyChanged(() => this.ZoomInCommand);
  }
}
[ImportingConstructor]
public ToolbarViewModel(IRegionManager regionManager,CompositionContainer container,ILoggerFacade loggerFacade)
{
  this.QueryCommand = new DelegateCommand<object>(
   this.OnQueryCommandClicked, this.CanQueryCommandClicked);
  this.ZoomInCommand = new DelegateCommand<object>(
   this.OnZoomInCommandClicked, this.CanZoomInCommandClicked);
  this.ZoomOutCommand = new DelegateCommand<object>(
   this.OnZoomOutCommandClicked, this.CanZoomOutCommandClicked);
  this._regionManager = regionManager;
  this._container = container;
}

Create command with input form


Implement an attribute query.

Here we will illustrate the creation of a command that initiate an input form where we can enter some data. When the OK button is pressed, an attribute query is done using methods coming from the IGisOperations interface.

In our views folder we add a new UserControl ‘LocationInput’ that contains two textboxes:
       Country and city

And two buttons:

      Ok and Cancel.

This view is dynamically displayed in a reserved region using code below.
private void OnQueryCommandClicked(object arg)
{
  IRegion region = _regionManager.Regions["Region4"];
  var inputView = _container.GetExportedValue<LocationInput>();
  region.Add(inputView, "LocationInputView");
  region.Activate(inputView);
}


After we create the commands for the Ok and Cancel button, the processing can be put in the ViewModel.

As we already saw before with the configuration, doing GIS operation results in an asynchronously process. Therefore in our GisOperation class we added an event handler that has to be initialized by the calling method so control will be returned after the GIS operation is finished.

First step:       

  • Set the callback when operation is finished.
  • Call the requested operation.
private void OnOKClicked(object arg)
{
  string where = string.Empty;
  if (this.Country.Length > 0)
   where = "COUNTRY='" + this.Country + "'";
  if (this.City.Length > 0)
  {
   if (where.Length > 0)
    where += " and ";
   where = "NAME='" + this.City + "'";
  }
  StartAttributeQuery(where, "", 9, "C");
}
private void StartAttributeQuery(string whereValue, string whereField,
int layerID, string fieldType)
{
  gisOperations.SetFinishedEvent(new MyResultsHandler(HandleResultQuery));
  gisOperations.AttributeQueryTask_Async(whereValue, whereField,
  gisOperations.GetBaseMapUrl(), layerID, fieldType);
}


Second step:

  • Clear the callback handler setting.
  • Process the result.
private void HandleResultQuery(object sender, ResultsEventArgs e)
{
  gisOperations.ResetFinishedEvent(new MyResultsHandler(HandleResultQuery));
  if (e != null)
  {
   FeatureSet results = e.Results;
   if (e.Results.Features != null && e.Results.Features.Count > 0)
   {
     // Zoom to result
     gisOperations.ZoomTo(e.Results.Features[0].Geometry);
     // Display graphics
     GraphicsLayer graphicsLayer =
     gisOperations.GetMap().Layers["Selections"] as GraphicsLayer;
     graphicsLayer.ClearGraphics();
     foreach (Graphic feature in results.Features)
     {
       if (feature.Geometry.GetType() == typeof(MapPoint))
       {                                         
         feature.Symbol =
         gisOperations.GetSelectionMarkerSymbol();
       }
       else if (feature.Geometry.GetType() ==
          typeof(ESRI.ArcGIS.Client.Geometry.Polyline))
       {
         feature.Symbol = gisOperations.GetSelectionLineSymbol();
       }
       else if (feature.Geometry.GetType() ==
         typeof(ESRI.ArcGIS.Client.Geometry.Polygon))
       {
         feature.Symbol = gisOperations.GetSelectionFillSymbol();
       }
       graphicsLayer.Graphics.Insert(0, feature);
    }
   }
  }
}

To simplify the job of rendering, in the map view I defined a resource containing the different rendering symbols. When the map gets initialized, I add code so that the different symbols are saved in the GisOperation class and can be used later.

This initialization can be achieved with this code:

// Initialise symbols
_gisOperations.SetSelectionLineSymbol(
   mapView.LayoutRoot.Resources["SelectLineSymbol"] as LineSymbol);
_gisOperations.SetSelectionMarkerSymbol(
   mapView.LayoutRoot.Resources["SelectMarkerSymbol"] as MarkerSymbol);
_gisOperations.SetSelectionFillSymbol(
   mapView.LayoutRoot.Resources["SelectFillSymbol"] as FillSymbol

CREATION OF A TOOL


In our toolbar I add two buttons ‘zoom in’ and ‘zoom out’. These two buttons illustrate a typical ESRI tool. A tool consists of a draw action and at the end of the draw, a processing.

In our case we will draw a rectangle and at the end of the drawing we will take the requested zoom action.

As in the case of the query tool, the two buttons will use an implementation of ICommand to expose the action to the button.

The main difference with the query command is that we must activate the draw functionality once the button is pressed.

In out GIS service we will add the following methods defined in the interface that is exposed:
void SetCompleteDrawEvent(MyDrawCompleteHandeler drawComplete);void ResetCompleteDrawEvent(MyDrawCompleteHandeler drawComplete);
void SetDrawMode(DrawMode drawMode);


The first two will allow us to handle the end processing of a draw operation. The last method set the kind of draw operation that is required. In case of the zoom action this will be a rectangle.
Because the draw object is associated with the map, it is already initialized during the setting of the map object.

Setting the draw mode active, looks like that:
public void SetDrawMode(DrawMode drawMode){
  if (mapDraw != null)
  {
   mapDraw.DrawMode = drawMode;
   mapDraw.IsEnabled = (mapDraw.DrawMode != DrawMode.None);
   mapDraw.DrawComplete += MapDrawSurface_DrawComplete;
  }
}


When the drawing is complete, the action that is triggered will be responsible to call the handler provided by the calling tool.  Here this is the eventhandler OnDrawComplete that has been set by the calling tool.

private void MapDrawSurface_DrawComplete(object sender, ESRI.ArcGIS.Client.DrawEventArgs args)
{
  mapDraw.DrawMode = DrawMode.None;
  mapDraw.IsEnabled = false;
  OnDrawComplete(args);
}



With this in place, all is now ready to start the drawing process by the tool. The actual action of the tool on the GIS must also be implemented in the GIS operation service. In our case this will be a zoom in and zoom out action on the map.

A possible implementation is highlighted below :
public void MapZoom(string action, ESRI.ArcGIS.Client.Geometry.Geometry geometry)
{
  if (action.Equals("ZoomIn"))
  {
    _mapView.ZoomTo(geometry as Envelope);
  }
  else if (action.Equals("ZoomOut"))
  {
    Envelope currentExtent = _mapView.Extent;
    Envelope zoomBoxExtent = geometry as Envelope;
    MapPoint zoomBoxCenter = zoomBoxExtent.GetCenter();
    double whRatioCurrent = currentExtent.Width / currentExtent.Height;
    double whRatioZoomBox = zoomBoxExtent.Width / zoomBoxExtent.Height;
    Envelope newEnv = null;
    if (whRatioZoomBox > whRatioCurrent)
    // use width
    {
      double mapWidthPixels = _mapView.Width;
      double multiplier = currentExtent.Width / zoomBoxExtent.Width;
      double newWidthMapUnits = currentExtent.Width * multiplier;
      newEnv = new Envelope(
          new MapPoint(zoomBoxCenter.X - (newWidthMapUnits / 2), zoomBoxCenter.Y),
          new MapPoint(zoomBoxCenter.X + (newWidthMapUnits / 2), zoomBoxCenter.Y));
    }
    else
    // use height
    {
      double mapHeightPixels = _mapView.Height;
      double multiplier = currentExtent.Height / zoomBoxExtent.Height;
      double newHeightMapUnits = currentExtent.Height * multiplier;
      newEnv = new Envelope(
         new MapPoint(zoomBoxCenter.X, zoomBoxCenter.Y - (newHeightMapUnits / 2)),
         new MapPoint(zoomBoxCenter.X, zoomBoxCenter.Y + (newHeightMapUnits / 2)));
    }
    if (newEnv != null)
      _mapView.ZoomTo(newEnv);
   }
  }
}

Now we can add the necessary code in the command event of the ViewModel to trigger the action and to do execute the GIS operation at the end of the drawing.

For the zoom in button when clicked, set the draw complete handler and start drawing a rectangle.
private void OnZoomInCommandClicked(object arg)
{
  currentCommand = "ZoomIn";
  gisOperations.SetCompleteDrawEvent(new MyDrawCompleteHandeler(DrawComplete));
  gisOperations.SetDrawMode(DrawMode.Rectangle) ;
}


When the draw is complete, the following we do the job:

private void DrawComplete(object sender, ESRI.ArcGIS.Client.DrawEventArgs args)
{
  gisOperations.MapZoom(currentCommand, args.Geometry);
  currentCommand = string.Empty;
}


As you can see, all the processing is done in the ViewModel, leaving a clean View with almost no code behind even if the view has a lot of functionality.
The ESRI map control will handle changes to the view by itself, there is no MVVM functionality involved in this.