Select to view content in your preferred language

Zoom to extent of feature set using MVVM

1127
7
05-13-2011 12:17 PM
AndrewMurdoch
Regular Contributor
I am using ESRI API 2.1, SL4.

How can I zoom to an envelope created in a viewmodel class using the MVVM framework?
How can I get a reference to the Map object or the Extent property of the Map in the viewmodel class?  It appears that the Extent property can not be set with XAML binding...

Is this done with a DependencyProperty for the Map object?  How do I get that Map reference into the viewmodel?

Thanks for your help!
0 Kudos
7 Replies
linusang
Emerging Contributor
Hi,

For my MVVM app, i chose to put the Map object in my ViewModel instead of the View.
Therefore in Xaml, i bind the ContentControl to it like this:

<ContentControl Content="{Binding Map}"/>

I'm not sure that is the best way of doing it.. and i'm interested to know how other people tackle this problem with MVVM...

thanks
0 Kudos
AndrewMurdoch
Regular Contributor
Thanks icube, that definitely works.
I hadn't really used the ContentControl object before but Map must inherit, I guess.

Now I have the problem of the Map object not resizing to fit the page on resize.  I can hard-code the Height and Width properties of the Map in the view model, but the Map object is no longer tightly coupled to the View.  When the page (and the Grid in the page) resizes, the Map stays the same size.

How would you get a bound ContentControl object to resize on page resize?

Thanks again,
Andrew
0 Kudos
IgressT
Emerging Contributor
Hi,

For my MVVM app, i chose to put the Map object in my ViewModel instead of the View.
Therefore in Xaml, i bind the ContentControl to it like this:

<ContentControl Content="{Binding Map}"/>

I'm not sure that is the best way of doing it.. and i'm interested to know how other people tackle this problem with MVVM...

thanks


This is how I am implementing in my MVVM app (using MVVM lite frame work)
Let me know if there is a better way

XAML
<UserControl x:Class="MvvmLight1.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:esri="clr-namespace:ESRI.ArcGIS.Client;assembly=ESRI.ArcGIS.Client"             
             DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Grid x:Name="LayoutRoot">

        <esri:Map x:Name="Map" Layers="{Binding LayerCollection}" />

        <Button x:Name="addGraphics"
                Content="Add Graphics"
                Height="30"
                Width="75"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Margin="5,5,0,0"
                Command="{Binding AddGrahics}" />        
    </Grid>   
</UserControl>


ViewModel
public class MainViewModel : ViewModelBase
    {
        // Constructor
        public MainViewModel()
        {
            if (IsInDesignMode)
            {
            }
            else
            {
                ArcGISTiledMapServiceLayer arcGISTiledMapServiceLayer = new ArcGISTiledMapServiceLayer();
                arcGISTiledMapServiceLayer.ID = "BaseMap";
                arcGISTiledMapServiceLayer.Url = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";
                arcGISTiledMapServiceLayer.InitializationFailed += (s, e) =>
                    {
                    };
                this.LayerCollection = new LayerCollection();
                this.LayerCollection.Add(arcGISTiledMapServiceLayer);
            }
        }

        // Layers collection
        public const string LayersPropertyName = "LayerCollection";

        private ESRI.ArcGIS.Client.LayerCollection _layerCollection = null;

        public ESRI.ArcGIS.Client.LayerCollection LayerCollection
        {
            get
            {
                return _layerCollection;
            }

            set
            {
                _layerCollection = value;
                RaisePropertyChanged(LayersPropertyName);
            }
        } 

        // Relay command and method
        private RelayCommand _addGrahics;
        public RelayCommand AddGrahics
        {
            get
            {
                if (_addGrahics == null)
                {
                    _addGrahics = new RelayCommand(AddGraphicsRelayMethod);
                }
                return _addGrahics;
            }
        }
        private void AddGraphicsRelayMethod()
        {
            GraphicsLayer graphicsLayer = new GraphicsLayer();
            graphicsLayer.ID = "TestLayer";

            Graphic graphic = new Graphic()
            {
                Geometry = new MapPoint (-8905559, 4163881),
                Symbol = new SimpleMarkerSymbol() { Color = new SolidColorBrush(Colors.Red), Size = 10 }
            };

            graphicsLayer.Graphics.Add(graphic);
            this.LayerCollection.Add(graphicsLayer);
        }

    }
0 Kudos
dotMorten_esri
Esri Notable Contributor
The most common way this has been handled, is using a custom IValueConverter on the map, or an attached property that takes care of handling the binding between the ViewModel extent and the View/Map.Extent.

The reason that this is not directly bindable, is that this is no ordinary property. Ie. you can set it to a specific extent, but because of a likely difference in aspect ratio between the map and the provided extent, this property will be adjusted so it will match the aspect ratio of the map. If it was directly bindable, you would get into some really ugly circular binding events and ultimately max out the stacktrace.
0 Kudos
AndrewMurdoch
Regular Contributor
Can you give any examples or point me in the right direction for "using a custom IValueConverter on the map" or using "an attached property that takes care of handling binding"?  I'm fairly new to Silverlight development and those went over my head.
Thanks,
Andrew
0 Kudos
BrandonCopeland
Emerging Contributor
Can you give any examples or point me in the right direction for... using "an attached property that takes care of handling binding"


Attached Property...
public static class MapExtentHelper
{
    public static readonly DependencyProperty MapZoomGeometryProperty = DependencyProperty.RegisterAttached("MapZoomGeometry", typeof(Geometry), typeof(MapExtentHelper), new PropertyMetadata(new PropertyChangedCallback(OnMapZoomGeometryChanged)));

    public static Geometry GetMapZoomGeometry(DependencyObject d)
    {
     return (Geometry)d.GetValue(MapZoomGeometryProperty);
    }
    
    public static void SetMapZoomGeometry(DependencyObject d, Geometry value)
    {
     d.SetValue(MapZoomGeometryProperty, value);
    }
    
    private static void OnMapZoomGeometryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Map map = d as Map;
        if (map == null) throw new ArgumentException("DependencyObject must be of type ESRI.ArcGIS.Client.Map");

        Geometry newZoomGeometry = GetMapZoomGeometry(map);
        if (map.Extent == null)
        {    
            map.Extent = newZoomGeometry.Extent;
        } 
        else
        { 
            map.ZoomTo(newZoomGeometry);
 }
    }
}


Use with Map control...
<esri:Map Extensions:MapExtentHelper.MapZoomGeometry="{Binding SomeGeometryOnViewModel}"/>

Where "Extensions" is the mapped namespace for your static class.
0 Kudos
AndrewMurdoch
Regular Contributor
That was just what I needed.  I also did need to explicitly reference the ESRI.ArcGIS.Client.Geometry.Geometry object as the default for Geometry in Silverlight seems to reference a System.Windows.Media.Geometry object.

Thanks!
0 Kudos