So far, we've created maps and scenes, added layers, learned about geometry and symbology, and so on, but now we need to learn about displaying and interacting with the map or scene. Adding information such as graphics has already been discussed, but what if you want to see the mouse's coordinates in latitude/longitude? What if you have lots of layers and want to have overlays (map tips) for each one of them that are customized for the layer? How do you show a legend, so users can discern what is on the map? Lastly, how do you show your location on the map or scene? All of these are questions that production-level apps need to address so that users can quickly understand what is being presented and where they are. In this chapter, we will discuss the following topics:
Being able to interact with the layers in the map is a very important capability that your users will expect. Otherwise, the map is just a pretty picture. To provide this capability in a non-MVVM app would simply require adding a click event to the code-behind file of MainWindow.cs
. Another feature that your user might expect is the ability to see coordinates on the map as they move the mouse. But how do we accomplish this with MVVM in such a way that we can move our code from MainWindow.cs
to another place?
Well, you already know the answer. We create a UserControl
task with ViewModel
and have it display the coordinates in the XAML code of UserControl
. Let's implement this:
Chapter6
. Copy the following folders: Behavior
, Models
, Services
, and ViewModels
. Install MVVM Light and Json.Net
, and add a reference to System.Windows.Interactivity
(4.5). When you install MVVM Light, a ViewModel
directory will be created. Delete it. Update App.xaml
with a reference to the Chapter6.ViewModels
folder. Update all other references and the using
statements. Copy the XAML code from MainWindow.xaml
in Chapter3a
to the MainWindows.xaml
file in your new app. Make sure the app builds.UserControls
. Add a new UserControl (WPF)
item to this app. Name it CoordinateDisplayUserControl.xaml
.ViewModel
class in the ViewModels
folder and name it CoordinateDisplayViewModel.cs
.using
statements to CoordinateDisplayViewModel.cs
:using GalaSoft.MvvmLight.Messaging; using Esri.ArcGISRuntime.Controls; using Esri.ArcGISRuntime.Geometry; using System;
MouseMove
event handler, press Tab twice to create it:Messenger.Default.Register<Esri.ArcGISRuntime.Controls.MapView>(this, (mapView) => { this.mapView = mapView; this.mapView.MouseMove += mapView_MouseMove; });
MouseMove
event handler (mapView_mouseMove
), add the following lines:if (this.mapView.Extent == null) return; System.Windows.Point screenPoint = e.GetPosition(this.mapView); MapPoint mapPoint = this.mapView.ScreenToLocation(screenPoint); if (this.mapView.WrapAround) mapPoint = GeometryEngine.NormalizeCentralMeridian(mapPoint) as MapPoint; this.Latitude = Math.Round(mapPoint.Y, 4); this.Longitude = Math.Round(mapPoint.X, 4);
As you can see, we are taking the Screen
coordinate and converting it to MapPoint
using ScreenToLocation
. If WrapAround
is turned on, we use the GeometryEngine
class to normalize the MapPoint
class. Finally, we get the coordinates and store them in two properties so that we can bind to them.
public double Latitude { set { this.latitude = value; RaisePropertyChanged("Latitude"); } get { return this.latitude; } } public double Longitude { set { this.longitude = value; RaisePropertyChanged("Longitude"); } get { return this.longitude; } }
ViewModel
class to the locator (ViewModelLocator.cs
):SimpleIoc.Default.Register<CoordinateDisplayViewModel>();
CoordinateDisplayUserContro.xaml
and enter the following code:<UserControl x:Class="Chapter6.UserControls.CoordinateDisplayUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:Chapter6.ViewModels" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="500"> <UserControl.Resources> <vm:CoordinateDisplayViewModel x:Key="Locator" d:IsDataSource="True"/> </UserControl.Resources> <Grid DataContext="{Binding CoordinateDisplayViewModel, Source={StaticResource Locator}}"> <StackPanel Orientation="Horizontal"> <Border Background="Transparent" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="30" Padding="20"> <Border.Effect> <DropShadowEffect/> </Border.Effect> <StackPanel > <StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="Latitude" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Center" Text="Latitude: " Width="80" TextWrapping="Wrap" FontWeight="Bold" /> <TextBlock x:Name="LatitudeValue" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Center" TextAlignment="Right" Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Source={StaticResource Locator}}" Width="80" FontWeight="Bold" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="Longitude" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Center" Text="Longitude: " TextWrapping="Wrap" FontWeight="Bold" /> <TextBlock x:Name="LongitudeValue" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Bottom" TextAlignment="Right" Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Source={StaticResource Locator}}" Width="95" FontWeight="Bold" /> </StackPanel> </StackPanel> </StackPanel> </Border> </StackPanel> </Grid> </UserControl>
Note that the resource of UserControl
has been set to the locator and that the Grid
tag has been set to CoordinateDisplayViewModel
. Other than that, the two TextBlock
properties are binding to the ViewModel
class' properties (Latitude
and Longitude
).
MainWindow.xaml
file by first adding a using
statement to MainWindow.xaml
like this:xmlns:uc="clr-namespace:Chapter6.UserControls"
</MapView>
tag like this:<uc:CoordinateDisplayUserControl VerticalAlignment="Top"></uc:CoordinateDisplayUserControl>
If you recall, in Chapter 1, Introduction to ArcGIS Runtime, we did something similar with the map scale but that was with the code-behind file. This time, we did it with a more MVVM-friendly approach. Of course, we could refactor this code to pass off the coordinates to a factory service, but for the sake of brevity we left this out.
While we used a UserControl
task with a ViewModel
class to solve this issue, we could have used other approaches too. We could have used Behavior
, TargetedTriggerAction
, or ChangePropertyAction
. Look them up in Microsoft's documentation, give them a try, and see which one you prefer.