Suggestion for GraphisLayer.GraphicsSource property

2917
5
05-24-2012 08:06 PM
wangzhifang
Occasional Contributor
Hi all,
GraphicsSource is very good idea for binding using in MVVM. But I found some issues on this property and thought it worth to be improved for GraphicsSource.
For instance, I have a GraphicsLayer on Map control which ID="GLayer", and a ObservableCollection<Graphic> which stores my Graphics. Manipulating on ObservableCollection<Graphic> such as remove and add can be reflected to GraphicsLayer and then on Map:
_graphics = new ObservableCollection<Graphic>(some graphics);
(Map1.Layers["GLayer"] as GraphicsLayer).GraphicsSource = _graphics;
...
_graphics.Clear();
foreach (Graphic g in new ObservableCollection<Graphic>(some new graphics)
                _graphics.Add(g);

However, directly set the _graphics just not working:
_graphics = new ObservableCollection<Graphic>(some new graphics);//just not working as expected.

When directly setting _graphics as above, nothing happend on Map.

I expected below directly set the _graphics just working as previously. Don't know am I right?
0 Kudos
5 Replies
JoeHershman
MVP Regular Contributor
What you are showing does not have anything to do with binding or MVVM so am not sure why are discussing it in that context.

In the first line of you code you assign a variable to the GraphicsSource property and so you now have two variables reference that same object.  When you make changes to the _graphics variable it is reflected in the GraphicsSource because they both reference that object.

When you set _graphics = new ... The _graphics variable now references a new object.  The original object that GraphicsSource is referencing has not been affected, GraphicsSource still references the same object it has the entire time.

What it seems like you want is to have an object bound to the GraphicsSource.

       <esri:GraphicsLayer ID="GL" GraphicsSource="{Binding GraphicsSource, Mode=TwoWay}" />


If you have a class like below that is set as the DataContext for your the GraphicsLayer (e.g., through setting the DataContext on the View with the map in it)

    public class MyViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<Graphic> _graphicsSource;
        public ObservableCollection<Graphic> GraphicsSource
        {
            get { return _graphicsSource; }
            set
            {
                _graphicsSource = value;
                PropertyChanged("GraphicsSource");
            }
        }

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if ( PropertyChanged != null )
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }


Now something like this would reset the graphics to those contained in someOtherGraphics and it would be reflected in the map

        private void ExecuteNewGraphicsCollection()
        {
            GraphicsSource = new ObservableCollection<Graphic>(someOtherGraphics);
        }


Hope that helps
Thanks,
-Joe
0 Kudos
TraceyRichvalsky
New Contributor
Hi,

I am binding a GraphicCollection to the GraphicsSource of the GraphicsLayer of my map. I am using the method suggested here by ESRI and the binding only works the first time. I have a query form that udpates the collection based on user input. Everything is working up to the point of the binding - the get for the collection is not called after the PropertyChange and so the map is not updated. Could someone provide some input on what might be wrong?

Here is my xaml code:

<esri:GraphicsLayer
GraphicsSource="{Binding Source={StaticResource Map_ViewModelDataSource}, Path=MyGraphicsList, UpdateSourceTrigger=PropertyChanged}">

Here is the C#:
private GraphicCollection myGraphicsList;
public GraphicCollection MyGraphicsList
{
get { return myGraphicsList; }
set
{
myGraphicsList = value;
RaisePropertyChanged("MyGraphicsList");
}
}

public event PropertyChangedEventHandler PropertyChanged;

protected void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}

Thanks for your help!
Tracey
0 Kudos
JamesMullinnix
New Contributor
Tracey,

Hi --  I am having the exact same issue you are... I am binding to the GraphicsSource of a layer, and it works GREAT the first time.  I am also updating the bound graphics list based on user input.  The first search updates fine, but upon changing the bound graphics list a 2nd time, I get the following error at the end of this post.

I have been searching online for the better part of 2 days trying to find someone with the same problem, and this is the first post I have seen!

My error comes when the setter of the graphics list tries to update the property with PropertyChanged.... and like I said, this works great the first time... just doesn't after that.

Did you, or anyone else, find a solution to this, or are having the same problem?

Thanks,

James

Error:

Operation not supported when using GraphicsSource.

{System.InvalidOperationException: Operation not supported when using GraphicsSource.
   at ESRI.ArcGIS.Client.GraphicsLayer.set_Graphics(GraphicCollection value)
   at ESRI.ArcGIS.Client.GraphicsLayer.OnGraphicsSourceChanged(IEnumerable`1 oldValue, IEnumerable`1 newValue)
   at ESRI.ArcGIS.Client.GraphicsLayer.OnGraphicsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   at System.Windows.DependencyObject.RaisePropertyChangeNotifications(DependencyProperty dp, Object oldValue, Object newValue)
   at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)
   at System.Windows.DependencyObject.RefreshExpression(DependencyProperty dp)
   at System.Windows.Data.BindingExpression.SendDataToTarget()
   at System.Windows.Data.BindingExpression.SourcePropertyChanged(PropertyPathListener sender, PropertyPathChangedEventArgs args)
   at System.Windows.PropertyPathListener.ReconnectPath()
   at System.Windows.Data.Binding.EnsureBreakPoint(BindingDebugState debugState, Action callback, Boolean canDelay)
   at System.Windows.Data.BindingExpression.OnSourcePropertyChanging(Action action)
   at System.Windows.PropertyPathListener.RaisePropertyPathStepChanged(PropertyPathStep source)
   at System.Windows.PropertyAccessPathStep.RaisePropertyPathStepChanged(PropertyListener source)
   at System.Windows.CLRPropertyListener.SourcePropertyChanged(Object sender, PropertyChangedEventArgs args)
   at System.Windows.Data.WeakPropertyChangedListener.PropertyChangedCallback(Object sender, PropertyChangedEventArgs args)
   at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)
   at DevSummitDemo.ViewModel.NotifyPropertyChanged(Expression`1 property)
   at DevSummitDemo.ViewModel.SetProperty(T& backingField, T value, Expression`1 property)
   at MVVMMapApplication.MainViewModel.set_SearchResults2(List`1 value)
   at MVVMMapApplication.MainViewModel.findTask_ExecuteCompleted2(Object sender, FindEventArgs e)
   at ESRI.ArcGIS.Client.Tasks.FindTask.OnExecuteCompleted(FindEventArgs args)
   at ESRI.ArcGIS.Client.Tasks.FindTask.request_Completed(Object sender, RequestEventArgs e)
   at ESRI.ArcGIS.Client.Tasks.TaskBase.request_Completed(Object sender, RequestEventArgs e)
   at ESRI.ArcGIS.Client.WebRequest.OnComplete(RequestEventArgs args)
   at ESRI.ArcGIS.Client.WebRequest.downloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e, Action retryCallback)
   at ESRI.ArcGIS.Client.WebRequest.<>c__DisplayClass1a.<BuildClient>b__18(Object s, DownloadStringCompletedEventArgs e)
   at System.Net.WebClient.OnDownloadStringCompleted(DownloadStringCompletedEventArgs e)
   at System.Net.WebClient.DownloadStringOperationCompleted(Object arg)}
0 Kudos
JamesMullinnix
New Contributor
Thought I should add my code, even though it is very similar to yours.

Here is my xaml:
<esri:GraphicsLayer ID="MyGraphicsLayer" 
                                GraphicsSource="{Binding SearchResults2, Source={StaticResource MainViewModel}}" />


And here is my property:
public List<Graphic> SearchResults
    {
      get { return _searchResults; }
      set { SetProperty(ref _searchResults, value, () => SearchResults); }
    }


I am using a helper class for this sandbox solution that I got from a Jeff Jackson presentation online:
/// <summary>
    /// Helper method to set a property value, typically used in implementing a setter.
    /// Returns true if the property actually changed.
    /// </summary>
    protected bool SetProperty<T>(ref T backingField, T value, Expression<Func<T>> property)
    {
      var changed = !EqualityComparer<T>.Default.Equals(backingField, value);
      if (changed)
      {
        backingField = value;
        NotifyPropertyChanged<T>(property);
      }
      return changed;
    }

    /// <summary>
    /// Raises the PropertyChanged event for the specified property.
    /// </summary>
    public void NotifyPropertyChanged<T>(Expression<Func<T>> property)
    {
        if (PropertyChanged == null)
            return;

        var lambda = (LambdaExpression)property;

        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = (UnaryExpression)lambda.Body;
            memberExpression = (MemberExpression)unaryExpression.Operand;
        }
        else memberExpression = (MemberExpression)lambda.Body;

        PropertyChanged(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
    }


I have also tried other ways of updating the property more directly, but it doesn't help.  I should also add that I can update a listbox or other structure with the SAME property (SearchResults), and it will update multiple times perfectly.  It is just the ESRI map that complains.
0 Kudos
TraceyRichvalsky
New Contributor
Hi James,
It's been a while since I've looked at this part of my project but here is what I ended up doing.  Also, I don't think I was actually getting an error - I think it was just that nothing happened on the updates.  Hopefully this helps you.  I found this in another post from an ESRI developer in a forum:  "While your Map can inherit the DataContext of your UserControl, GraphicsLayer will not. GraphicsLayer, unlike Map or any other UIElement, does not have a DataContext and will therefore fail to resolve this type of Binding."  So what I did was to define the map and all of its components in the C# as follows (some of my names are different than my original post b/c that was a prototype - I did have some other issues when I integrated the prototype so I'm hoping that I'm answering your question and not a different problem I had):

C# that replaces the .xaml code in the View - this is in the ViewModel of the piece of my project that creates a map report
Map myMap = new Map();
                    myMap.WrapAround = true;
                    myMap.ZoomFactor = 1.2;
                    //street map
                    ArcGISTiledMapServiceLayer sl = new ArcGISTiledMapServiceLayer();
                    sl.Url = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer";
                    myMap.Layers.Add(sl);

                    //add the points layer to the map
                    List<string> pointsMapTipLabels = new List<string>() { "Info" };
                    myMap.Layers.Add(((Geospatial_ViewModel)tileViewModel.ContentViewModel).addGraphicsLayer("MyPointsList", "IsPointsLayerVisible", pointsMapTipLabels));
                    tileContentView.Content = myMap;


Here is the addGraphicsLayer() function (this is in the map ViewModel):
//This adds a layer containing WACs to the map; pass in the name of the wac list and
        // the name of the boolean that controls visibility for binding
        public GraphicsLayer addGraphicsLayer(string list, string isGridVis, List<string> mapTips)
        {
            GraphicsLayer layer = new GraphicsLayer();
            //list binding            
            Binding listBinding = new Binding(list);
            listBinding.Source = this;
            listBinding.Mode = System.Windows.Data.BindingMode.TwoWay;
            BindingOperations.SetBinding(layer, GraphicsLayer.GraphicsProperty, listBinding);

            //list visibility binding - controls whether thepoints are visible
            Binding listVisBinding = new Binding(isGridVis);
            listVisBinding.Source = this;
            listVisBinding.Mode = BindingMode.TwoWay;
            BindingOperations.SetBinding(layer, GraphicsLayer.VisibleProperty, listVisBinding);

            //map tips
            MapTip mt = new MapTip();
            Grid mtgrid = new Grid();
            Color lightYellow = getColorFromHexString("FFFFFFE0");
            mtgrid.Background = new SolidColorBrush(lightYellow);
            System.Windows.Thickness thOne = new Thickness();
            thOne.Bottom = thOne.Left = thOne.Right = thOne.Top = 1;
            mtgrid.Children.Add(new Border() { BorderBrush = new SolidColorBrush(Colors.Black), BorderThickness = thOne });
            StackPanel mtpanel = new StackPanel();
            System.Windows.Thickness thFive = new Thickness();
            thFive.Bottom = thFive.Left = thFive.Right = thFive.Top = 5;
            mtpanel.Margin = thFive;
            mtpanel.Orientation = Orientation.Vertical;
            mtgrid.Children.Add(mtpanel);

            foreach (string tip in mapTips)
            {
                mtpanel.Children.Add(createMapTipTextBlock(tip));
            }
            layer.MapTip = mtgrid;

            return layer;
        }


This is all I have in the map View (.xaml file).  I am using a ComponentOne tile to display different types of reports.  In most cases, the content of the TileItem gets set to the View.  This works when my report is a chart but when the report is the ESRI map, it does not so there is basically nothing in the map's .xaml View file.
    <UserControl.Resources>
        <esriConverters:DictionaryConverter x:Key="MyDictionaryConverter" />
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot"
          Background="White">
    </Grid>
</UserControl>


This is the list of points that is used for the binding (in the map ViewModel):
        private GraphicCollection myPointsList;
        public GraphicCollection MyPointsList
        {
            get { return myPointsList; }
            set
            {
                myPointsList = value;
                RaisePropertyChanged("MyPointsList");                                
            }
        }


Then in the map Model, when my query results come back, I update MyPointsList:
GraphicCollection glist = new GraphicCollection();
                SpatialReference sref = new SpatialReference(4326);
                SolidColorBrush pointColor;

                double prevLat = 0.0;
                double prevLon = 0.0;
                double maxValue = 0.0;

                //loop through points returned
                for (int x = 0; x < e.Result.geoData.Count(); x++)
                {
                    double lat = e.Result.geoData.Latitude;
                    double lon = e.Result.geoData.Longitude;
                    string name = e.Result.Name;
                    double value = e.Result.MeasureValues;

                        Graphic g = new Graphic()
                            {
                                Geometry = new MapPoint(lon, lat, sref)
                            };

                        g.Symbol = new SimpleMarkerSymbol()
                        {
                            Color = new SolidColorBrush(Colors.Red),
                            Size = 18,
                            Style = SimpleMarkerSymbol.SimpleMarkerStyle.Circle
                        };

                        //mouse over data
                        g.Attributes.Add("Info", name + ": " + value.ToString() + " " + viewModel.MeasureSelected.Name);

                        glist.Add(g);                    

                } //end for each point
                viewModel.MyPointsList = glist;


Hopefully this helps and isn't too confusing!  I tried to remove the non-relevant code so you could see what the binding is doing easliy.  Good luck!
0 Kudos