Select to view content in your preferred language

How to bind a map object using the mvvm pattern?

2783
2
12-14-2010 06:50 AM
YinShi
by
Regular Contributor
Hi,

I tried to bind a map control using datacontext but had not been successful. When I ran the code, I just got a blank page. I watched the Map property of the view model and it was instantiated correctly. So I suspect the problem might be with the XAML binding expression. I would appreciate if someone can point me in the right direction. Here is what I did:

1) Create a singleton class which has a Map property
    public class MapSingleton
    {
        private static MapSingleton _instance = new MapSingleton();

        public static MapSingleton Instance()
        {
            if (_instance == null)
                _instance = new MapSingleton();
            return _instance;
        }

        private MapSingleton()
        {
            BaseLayer = new ArcGISTiledMapServiceLayer();
            BaseLayer.Url = "http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer";
            GraphicsLayer = new GraphicsLayer();

            //Creates an instance of a map object.
            Map = new Map();
            Map.Background = new SolidColorBrush(Colors.Red);
            Map.Layers.Add(BaseLayer);
            Map.Layers.Add(GraphicsLayer);
        }

        public Map Map { get; set; }

        public ArcGISTiledMapServiceLayer BaseLayer {get; set;}

        public GraphicsLayer GraphicsLayer { get; set; }
    }

2) Create a view model, I have only a Map property to keep things simple.  I didn???t implement INotifyPropertyChanged as the property is served only for one-way binding.
    public class MainPageViewModel
    {
        public Map Map
        {
             get
            {
                return MapSingleton.Instance().Map;
            }
      }
   }

3) In app.xmal I wired up the view model to the view, passing the view model in as a constructor parameter.
        private void Application_Startup(object sender, StartupEventArgs e)
        {
               MainPageViewModel vm = new MainPageViewModel ();
               MainPage mainPage = new MainPage(vm);
            this.RootVisual = mainPage;
        }

4) In the code behind of the view, MainPage.xmal. I set the datacontext for the entire page in the constructor
         public MainPage(MainPageViewModel VM)
        {
            this.DataContext = VM;
            InitializeComponent();
        }


5) In the view, MainPage.xmal. I bound the map property of the view model to the Map control
<esri:Map DataContext="{Binding Path=Map}" />

Configuration
1) Silverlight 3
2) VS2008 SP1
3) ArcGIS SilverlightWPF12
4) ArcGIS Server 9.3.1


Thanks for looking into this,
yin
0 Kudos
2 Replies
JenniferNery
Esri Regular Contributor
I don't think you will be able to add a UIElement into your XAML through binding.

For the sake of discussion, it's like saying you have a TextBlock property in your ViewModel and want it to be added to your MainPage LayoutRoot (Grid container). This sounds possible only in code-behind.
this.LayoutRoot.Children.Add(VM.MyTextBlock);


How else can this be done in XAML? There is a question of what property do you set? Do you override the DataContext? I don't think this is possible.
<Grid x:Name="LayoutRoot">
<TextBlock ???/>
</Grid>


I am not entirely sure how much SL3 and v1.2 support MVVM. This is still a work in progress for our API. But I do know that in comparison to SL3 and v1.2, SL4 and v2.1 have better MVVM support.

With that said, instead of binding the entire UIElement (Map), I think it is better to bind Map Properties. Note however, that not all properties of Map are "bindable" (i.e. Extent). Layers is a DependencyProperty, therefore you can bind this to your ViewModel's property. Other DependencyProperties include: PanDuration, ZoomDuration, ZoomFactor.

I think that this is more feasible:
<esri:Map Layers="{Binding Map.Layers}" PanDuration="{Binding Map.PanDuration}" ZoomDuration="{Binding Map.ZoomDuration}" ZoomFactor="{Binding Map.ZoomFactor}"/>
0 Kudos
BrandonCopeland
Emerging Contributor
Agree with Jennifer. Expose properties like Layers from your view model (not UI elements like a map control) and bind to those.

One workaround for properties like Map.Extent that will not support binding is attached properties. Create an attached property (maybe BindableExtent) and bind that to your view model. In the callback for the property change, do some work to set the actual map extent.

Quick and dirty example:
        public static readonly DependencyProperty BindableExtentProperty = DependencyProperty.RegisterAttached("BindableExtent", typeof(Geometry), typeof(OwnerClass), new PropertyMetadata(new PropertyChangedCallback(OnBindableExtentChanged)));

        public static Geometry GetBindableExtent(DependencyObject d)
        {
            return (Geometry)d.GetValue(BindableExtentProperty);
        }

        public static void SetBindableExtent(DependencyObject d, Geometry value)
        {
            d.SetValue(BindableExtentProperty, value);
        }

        private static void OnBindableExtentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Map map = d as Map;
            var newExtent = (Geometry)e.NewValue;
            map.ZoomTo(newExtent);
        }
0 Kudos