GpsLayer and MVVM issues

1189
10
Jump to solution
07-26-2012 02:29 AM
Labels (1)
EladNachmias
New Contributor
Hello,

I am trying to use the GpsLayer under MVVM architecture, however, as it seems - no luck yet.

Binding the GeoPositionWatcher to a property when using MVVM does not work (getter not reached).
The reason is that GpsLayer inherits DependencyObject, but DependencyObject does not implement DataContext.
So actually it is impossible to bind the DependencyProperty to a property on the ViewModel.
Microsoft states that the DO should inherit the DataContext from the containing FrameworkElement (esri:map in this case), but that in fact doesn't happen.

Would love for some opinion from ESRI about this - have you tested this in MVVM architecture?

Also - when a certain object/class implements visual capabilities (GpsLayer has  a GraphicsLayer, that allows actual creation of geometries, shapes, lines, etc), it should inherit FrameworkElement - providing the infrastructure of inheritance through dependency properties that the WPF Dependency Object+Dependency Property Backing data store offers.

Not to mention - The actual inheritance through DependencyObject  and not through FrameworkElement might cause visual problems (And I have personally had situations where the layer stopped refreshing and stopped redrawing, I would suggest this is related to the WPF rendering engine).
0 Kudos
1 Solution

Accepted Solutions
AnttiKajanus1
Occasional Contributor III
When working with Caliburn.Micro you can use DataContextProxy to define DataContext from the Map so you can bind to it since Caliburn handles ViewModel mapping automatically.

DataContext can be proxied with solution like this:
  public class DataContextProxy : Freezable     {         #region Overrides of Freezable          protected override Freezable CreateInstanceCore()         {             return new DataContextProxy();         }          #endregion          public object Data         {             get { return (object)GetValue(DataProperty); }             set { SetValue(DataProperty, value); }         }          public static readonly DependencyProperty DataProperty =             DependencyProperty.Register("Data", typeof(object), typeof(DataContextProxy), new UIPropertyMetadata(null));     }


And in the XAML you can use it like this

        <esri:Map>             <esri:Map.Resources>                     <local:DataContextProxy x:Key="proxy" Data="{Binding}" />             </esri:Map.Resources>             <esri:ArcGISTiledMapServiceLayer ID="PhysicalTiledLayer"                        Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer"/>              <esri:GraphicsLayer ID="MyGraphicsLayer"                      GraphicsSource="{Binding Path=Data.SearchResults, Source={StaticResource proxy}}" Renderer="{StaticResource MySimpleRenderer}" />         </esri:Map>


This is just one solution but seems to work quite well in the cases where you don't set ViewModels explicitly in the XAML.

Edit:
Note that when using proxy like this, you need to prefix binding to real proxy property since the solution is kind of relative binding. In this case, path is set to Data.SearchResults.

View solution in original post

0 Kudos
10 Replies
MichaelBranscomb
Esri Frequent Contributor
Hi,

Can you provide an example of your code - including both the view and the view model - which demonstrates how you're trying to do the binding?


Cheers

Mike
0 Kudos
EladNachmias
New Contributor
Hi, Sure.

I'm using the Caliburn.Micro framework.
Application works in ViewModel first concept.
DataContext is automatically set to the ViewModel by caliburn.

View XAML (relevant part)
  
                <esri:Map IsLogoVisible="False" x:Name="MapObject" Grid.Row="1">

                    <Mapping:LocalOpenStreetMapLayer Style="Mapnik"  />
                    <Controls:AutoGpsLayer x:Name="AutoGpsLayer" Bearing="{Binding Bearing,Mode=OneWay}" />
                  
                </esri:Map>

Whereas LocalOpenStreetMapLayer inherits OpenStreetMapLayer in order to fetch tiles from a different server.
AutoGpsLayer inherits GpsLayer, but adds several other dependency properties in order to receive more info - e.g. - inertial navigation data.

ViewModel Property:
        public double Bearing
        {
            get { return _bearing; }
            set { _bearing = value;
            NotifyOfPropertyChange(()=>Bearing);}
        }


Trying to bind from the XAML, yields this binding error:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Bearing; DataItem=null; target element is 'AutoGpsLayer' (HashCode=22325283); target property is 'Bearing' (type 'Double')


EDIT: By the way, worth mentioning is that the inheritance from DependencyObject limits binding from code too - there is no way to bind from code because there is no implementation for the SetBinding method.
0 Kudos
JeffJackson
New Contributor III
Sheled,

DependencyObject's don't provide a DataContext, but they can still participate in data binding so they work great in MVVM. The trick is to specify the Source in your binding statement. For a FrameworkElement there is an implicit source (the DataContext) but for a DependencyObject you have to declare it explicitly.

There are different techniques to do this. In the following example, I declare the ViewModel as a resource in the View and then I can use the StaticResource approach:

    
   <Window.Resources>
     <local:MainViewModel x:Key="MainViewModel" />
   </Window.Resources>

  <Grid DataContext="{StaticResource MainViewModel}">

    <esri:Map Extent="-14268281.1311858,2195120.17402859,-7232639.54776086,7467160.93387503">
    <esri:ArcGISTiledMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer" />
      <esri:GraphicsLayer ID="MyGraphicsLayer" 
                          GraphicsSource="{Binding SearchResults, Source={StaticResource MainViewModel}}"/>
    </esri:Map>
  </Grid>


Hopefully that does the trick for you.

/Jeff
0 Kudos
AnttiKajanus1
Occasional Contributor III
When working with Caliburn.Micro you can use DataContextProxy to define DataContext from the Map so you can bind to it since Caliburn handles ViewModel mapping automatically.

DataContext can be proxied with solution like this:
  public class DataContextProxy : Freezable     {         #region Overrides of Freezable          protected override Freezable CreateInstanceCore()         {             return new DataContextProxy();         }          #endregion          public object Data         {             get { return (object)GetValue(DataProperty); }             set { SetValue(DataProperty, value); }         }          public static readonly DependencyProperty DataProperty =             DependencyProperty.Register("Data", typeof(object), typeof(DataContextProxy), new UIPropertyMetadata(null));     }


And in the XAML you can use it like this

        <esri:Map>             <esri:Map.Resources>                     <local:DataContextProxy x:Key="proxy" Data="{Binding}" />             </esri:Map.Resources>             <esri:ArcGISTiledMapServiceLayer ID="PhysicalTiledLayer"                        Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer"/>              <esri:GraphicsLayer ID="MyGraphicsLayer"                      GraphicsSource="{Binding Path=Data.SearchResults, Source={StaticResource proxy}}" Renderer="{StaticResource MySimpleRenderer}" />         </esri:Map>


This is just one solution but seems to work quite well in the cases where you don't set ViewModels explicitly in the XAML.

Edit:
Note that when using proxy like this, you need to prefix binding to real proxy property since the solution is kind of relative binding. In this case, path is set to Data.SearchResults.
0 Kudos
JeffJackson
New Contributor III
Antti,

I love this solution! Very clean and works like a champ.

Thanks for the tip.

-Jeff
0 Kudos
AnttiKajanus1
Occasional Contributor III
To Jeff,

I am glad if you found that useful. I think that this is a quite clean solution and does the trick.

From my perspective pros of the solution are:

  • it is easy to understand and implement

  • DataContext changes are reflected all way to bindings

  • you can control what data to proxy

  • Minimal overhead

And cons:

  • If you want to proxy multiple properties, you need to make multiple proxyproperties. I use this mainly to proxy the DataContext so its fine for me.

  • You need to prefix path in bindings (annoying but I can live with that)

If you find situations where the solution does not work, let me know. I am still testing this solution since it is not a long ago when I made this.

To original poster,

If the post answered to your problem, please mark to answered.
0 Kudos
EladNachmias
New Contributor
Sheled,

DependencyObject's don't provide a DataContext, but they can still participate in data binding so they work great in MVVM. The trick is to specify the Source in your binding statement. For a FrameworkElement there is an implicit source (the DataContext) but for a DependencyObject you have to declare it explicitly.

There are different techniques to do this. In the following example, I declare the ViewModel as a resource in the View and then I can use the StaticResource approach:

    
   <Window.Resources>
     <local:MainViewModel x:Key="MainViewModel" />
   </Window.Resources>

  <Grid DataContext="{StaticResource MainViewModel}">

    <esri:Map Extent="-14268281.1311858,2195120.17402859,-7232639.54776086,7467160.93387503">
    <esri:ArcGISTiledMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer" />
      <esri:GraphicsLayer ID="MyGraphicsLayer" 
                          GraphicsSource="{Binding SearchResults, Source={StaticResource MainViewModel}}"/>
    </esri:Map>
  </Grid>


Hopefully that does the trick for you.

/Jeff

Hi Jeff,

This won't work in Caliburn since ViewModels are instantiated before Views. Your suggested solution will instantiate a new ViewModel which will be disconnected from the actual ViewModel which is 'alive' and connected to the application.


Antti,

I love this solution! Very clean and works like a champ.

Thanks for the tip.

-Jeff


Why use a workaround when you can just utilize the "Binding" keyword and make the platform do all the rest of the work for you?


To original poster,

If the post answered to your problem, please mark to answered.


Will do.


Good day.
0 Kudos
AnttiKajanus1
Occasional Contributor III

Why use a workaround when you can just utilize the "Binding" keyword and make the platform do all the rest of the work for you?


At my knowledge, the issue here is that you need to explicitly define source for your binding when binding from non-visual elements (layers in this case) to something.

Since layers aren't part of the visual tree, they do not get the DataContext derived from the upper elements so we need to proxy the DataContext somehow to the non-visual elements in XAML.
0 Kudos
EladNachmias
New Contributor
At my knowledge, the issue here is that you need to explicitly define source for your binding when binding from non-visual elements (layers in this case) to something.

Since layers aren't part of the visual tree, they do not get the DataContext derived from the upper elements so we need to proxy the DataContext somehow to the non-visual elements in XAML.


I fully understand the problem.
However, Layers ARE somewhat visual elements (they contain the graphics to be displayed on the map).
(From what I've seen when disassembling the libraries - their actual rendering is handled by the Map object itself)

The issue here, to my opinion, is just a misconception in implementation. Binding is by far one of WPF/SL's strongest features.
Implementing a data oriented object without the ability to natively use data binding on it in WPF seems, well, you get the picture.
0 Kudos