Select to view content in your preferred language

GraphicsLayer data binding issue

3540
11
08-25-2011 09:23 AM
MichaelCheng
Emerging Contributor
I am writing a POC to integrate with our current app, which builds on MVVM pattern.  I'm trying to bind a collection of "Graphic" objects to either Graphics or GraphicsSource property of a GraphicsLayer.  I am using Silverlight4 and that should allow DependencyProperties to allow binding in DependencyObjects as long as it is contained in a FrameworkElement, which in this case, is the Map object.  However I am not having any success.  I can; however, bind Layers to a collection of Layer objects and that'd trickle down, but I am hoping I can have better control and only bind where need be.  Is this possible with your control?  Or am I missing something?  Thank you.
0 Kudos
11 Replies
JenniferNery
Esri Regular Contributor
You should be able to bind GraphicsLayer.Graphics property as in this sample where GraphicsSource implements INotifyPropertyChanged or can even be just a GraphicCollection.

xmlns:local="clr-namespace:BindableGraphics">
<UserControl.Resources>
 <esri:SimpleRenderer x:Key="MyBlueRenderer">
  <esri:SimpleRenderer.Symbol>
   <esri:SimpleMarkerSymbol Color="Blue" Size="10" Style="Circle"/>
  </esri:SimpleRenderer.Symbol>
 </esri:SimpleRenderer>  
 <local:GraphicsSource x:Key="MyGraphicSource"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
 <esri:Map x:Name="MyMap">
  <esri:ArcGISTiledMapServiceLayer Url="http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer" />
  <esri:GraphicsLayer ID="MyGraphicsLayer" Graphics="{Binding Source={StaticResource MyGraphicSource}, Path=Graphics}" Renderer="{StaticResource MyBlueRenderer}"/>
 </esri:Map>
</Grid>
0 Kudos
MichaelCheng
Emerging Contributor
Marked as answered!  Must be one of the differences between WPF and Silverlight having to make the data a resource in order to bind...  Thank you!
0 Kudos
MichaelCheng
Emerging Contributor
I have successfully made it bind, but cannot make the binding update.  Currently GraphicsLayer binds this way:
Graphics="{Binding Source={StaticResource MappingSource}, Path=MappingItems, Mode=TwoWay, Converter={StaticResource mappingsConverter}}"

where the converter simply converts a list of view models to a GraphicsCollection object that Graphics expects.  Binds fine the first time, but I cannot make the binding update through NotifyPropertyChanged event.

My goal is to bind Graphic.Selected property to a property of a view model so I can toggle the Selected state through setting the view model property:

Binding binding = new Binding("Selected");
binding.Source = mappingItem;
binding.Mode = BindingMode.TwoWay;    
BindingOperations.SetBinding(graphic, Graphic.SelectedProperty, binding);


That is; however, not happening.  Neither can I make Graphics property rebind to the entire collection. 

Not sure if this is a limitation of Silverlight or the control itself.  I expect this should work ok with WPF.

Thank you.
0 Kudos
dotMorten_esri
Esri Notable Contributor
To make collections update, you will need to implement INotifyCollectionChanged instead on your collection (GraphicsCollection has this already though).
Also you should be binding to GraphicsLayer.GraphicsSource and not GraphicsLayer.Graphics.
You shouldn't be using TwoWay mode either, since the GraphicsLayer shouldn't be changing the graphics collection instance or the collection itself (modifying the features in this scenario should only be done through your model).

It's also worth noting that you can't do data binding to objects that are not FrameworkElements (since they don't have a datacontext). That's Why you can't do binding on to a layer in a layer collection, but you can bind the Layers property on the map (since Map is a FrameworkElement). This doesn't mean that you cant assign a property to a StaticResource though (as shown above).
0 Kudos
TraceyRichvalsky
Emerging Contributor
Hi,

I am having the same problem that is described here.  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
DominiqueBroux
Esri Frequent Contributor
Hi Tracey,

At first glance, I don't see any problem in your code.

If your class inherits from INotifyPropertyChanged, that should work.

Does your class Map_ViewModelDataSource inherit from INotifyPropertyChanged?
0 Kudos
TraceyRichvalsky
Emerging Contributor
Hi Dominique,

Thanks for getting back to me!  I'm stumped.  Yes, my class Map_ViewModelDataSource inherits from our class Base_ViewModel which inherits from INotifyPropertyChanged.  I've tried changing the GraphicCollection to ObservableCollection<Graphic> and that has the same problem.  The funny thing is that it works on the initial load.  Any updates after that do not work.  Also, if I remove the initial load (say we wanted the user to query first rather than showing all the data), it still does not work when the user queries so then I do not see any points on the map.  I've stepped through in the debugger, and I see the collection being updated (the set) and I see the PropertyChange being called successfully.  What does not happen is the get of the collection afterward which would indicate that the binding is working.  If you have any suggestions for what else I should check/try I'd appreciate it!

Thanks!
Tracey
0 Kudos
DominiqueBroux
Esri Frequent Contributor
Strange!
I tweaked the UsingDataSource interactive SDK sample and I set a new GraphicCollection each time the user clicks on the map. That works!

Here is the code. Comparing with yours might give you some clue:

XAML:
<UserControl x:Class="ArcGISSilverlightSDK.UsingGraphicsSource"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
    xmlns:local="clr-namespace:ArcGISSilverlightSDK"         >

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.Resources>
            <local:Customers x:Key="customers" />
        </Grid.Resources>

        <esri:Map x:Name="MyMap" MouseClick="MyMap_OnMouseClick">
            <esri:ArcGISTiledMapServiceLayer 
                Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer" />
            <esri:GraphicsLayer ID="MyGraphicsLayer" 
                                GraphicsSource="{Binding Source={StaticResource customers}, Path=MyGraphicsList}" />
        </esri:Map>
    </Grid>
</UserControl>


C#:
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using ESRI.ArcGIS.Client.Symbols;
using System;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Media;

namespace ArcGISSilverlightSDK
{
    public partial class UsingGraphicsSource : UserControl
    {
        public UsingGraphicsSource()
        {
            InitializeComponent();
        }

        private void MyMap_OnMouseClick(object sender, ESRI.ArcGIS.Client.Map.MouseEventArgs e)
        {
            var customer = LayoutRoot.Resources["customers"] as Customers;
            customer.MyGraphicsList = Customers.CreateNewGraphicCollection();
        }
    }

    public class Customers : INotifyPropertyChanged
    {
        private static readonly ESRI.ArcGIS.Client.Projection.WebMercator mercator = new ESRI.ArcGIS.Client.Projection.WebMercator();

        public Customers()
        {
            MyGraphicsList= CreateNewGraphicCollection();
        }

        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));
            }
        }

        internal static GraphicCollection CreateNewGraphicCollection()
        {
            var graphicCollection = new GraphicCollection();
            var random = new Random();

            for (int i = 0; i < 10; i++)
            {
                var g = new Graphic
                {
                    Geometry = mercator.FromGeographic(new MapPoint(random.Next(-180, 180), random.Next(-90, 90))),
                    Symbol = new SimpleMarkerSymbol
                    {
                        Color = new SolidColorBrush(Color.FromArgb(255, (byte)random.Next(0, 255), (byte)random.Next(0, 255), (byte)random.Next(0, 255))),
                        Size = 28
                    }
                };
                graphicCollection.Add(g);
            }
            return graphicCollection;
        }
    }
}
0 Kudos
TraceyRichvalsky
Emerging Contributor
It is strange!  Thanks for the example.  I looked at it and it looks very similar to what I have.  I have tried mine creating a new GraphicCollection each time as well as setting MyGraphicList to null in the "set" and none of that seems to make any difference.  I think it has something to do with how it's being set in the beginning vs in the update because if it's not set in the beginning, the map is never updated.  (in other words, if I comment out this line at the very end of the model, the map never shows any points even though the Collection is populated:
               map_vm.FilteredAirportData = airportData;
)

Here is more of my model and viewModel- maybe that will help.  If you need the xaml for the form I can send that but that part is working correctly - I can see that in the debugger that the Filtered list and the Collection are correct.  It's something with the binding I think...

ViewModel:
namespace SIAW_Geospatial.ViewModels
{
    public class Map_ViewModel : Base_ViewModel
    {
        private static ESRI.ArcGIS.Client.Projection.WebMercator mercator =
              new ESRI.ArcGIS.Client.Projection.WebMercator();

        private Map_Model mapModel;

        public Map_ViewModel()
        {
            //models
            mapModel = new Map_Model(this);

            updateMapCmd = new RelayCommand(updateMap) { IsEnabled = true };

            mapModel.getAirportData(); //populate lists            
        }

        private ObservableCollection<AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin> filteredAirportData;
        public ObservableCollection<AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin> FilteredAirportData
        {
            get { return filteredAirportData; }
            set
            {
                filteredAirportData = value;
                RaisePropertyChanged("FilteredAirportData");
                //set the map
                setGraphicsList();
            }

        }

        //private GraphicCollection myGraphicsList;
        //public GraphicCollection MyGraphicsList
        private ObservableCollection<Graphic> myGraphicsList;
        public ObservableCollection<Graphic> MyGraphicsList
        {
            get { return myGraphicsList; }
            set
            {
                myGraphicsList = value;
                RaisePropertyChanged("MyGraphicsList");                                
            }
        }

        private readonly ICommand updateMapCmd;
        public ICommand UpdateMapCmd
        {
            get
            {
                return updateMapCmd;
            }
        }
        private void updateMap()
        {
            var query = from c in mapModel.AirportData
                        select c;
            # region filters
            if (AirlineListSelectedIndex > 0) //0 is All
            {
                string airline = AirlineList.ElementAt(AirlineListSelectedIndex);
                query = query.Where(c => c.Airline.Equals(AirlineList.ElementAt(AirlineListSelectedIndex)));
            }
            if (DestinationListSelectedIndex > 0) //0 is All
            {
                query = query.Where(c => c.Destination.Equals(DestinationList.ElementAt(DestinationListSelectedIndex)));
            }
            if (ScheduledOperatorSelectedIndex > 0) //0 is No selection (i.e. don't filter by this)
            {
                if (ScheduledOperatorSelectedIndex == (int)(OperatorTypes.GreaterThan))
                    query = query.Where(c => c.Operations.Value > NumScheduledFlights);

                else if (ScheduledOperatorSelectedIndex == (int)(OperatorTypes.EqualTo))
                    query = query.Where(c => c.Operations.Value.Equals(NumScheduledFlights));

                else if (ScheduledOperatorSelectedIndex == (int)(OperatorTypes.LessThan))
                    query = query.Where(c => c.Operations.Value < NumScheduledFlights);
            }

            if (CancelledOperatorSelectedIndex > 0) //0 is No selection (i.e. don't filter by this)
            {
                if (CancelledOperatorSelectedIndex == (int)(OperatorTypes.GreaterThan))
                    query = query.Where(c => c.No_Canceled.Value > NumCancelledFlights);

                else if (CancelledOperatorSelectedIndex == (int)(OperatorTypes.EqualTo))
                    query = query.Where(c => c.No_Canceled.Value.Equals(NumCancelledFlights));

                else if (CancelledOperatorSelectedIndex == (int)(OperatorTypes.LessThan))
                    query = query.Where(c => c.No_Canceled.Value < NumCancelledFlights);
            }
            # endregion filters

            //convert to an observable collection
            ObservableCollection<AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin> tmp = new ObservableCollection<AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin>();
            foreach (AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin data in query)
            {
                tmp.Add(data);
            }

            FilteredAirportData = tmp; 
        }
        public void setGraphicsList()
        {
            //GraphicCollection glist = new GraphicCollection();
            ObservableCollection<Graphic> glist = new ObservableCollection<Graphic>();
            SpatialReference sref = new SpatialReference(4326);
            SolidColorBrush pointColor;

            foreach (AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin data in FilteredAirportData)
            {
                //get the right color based on the percent of cancelled flights
                if (data.Percent_Canceled < 25)
                    pointColor = new SolidColorBrush(Colors.Green);
                else if ((25 <= data.Percent_Canceled) && (data.Percent_Canceled < 75))
                    pointColor = new SolidColorBrush(Colors.Yellow);
                else
                    pointColor = new SolidColorBrush(Colors.Red);

                Graphic g = new Graphic()
                    {
                        Geometry = new MapPoint((double)data.Longitude, (double)data.Latitude, sref)
                    };

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

                //mouse over data
                g.Attributes.Add("OriginCity", "Origin City: " + data.City);
                g.Attributes.Add("Destination", "Destination Airport: " + data.Destination);
                g.Attributes.Add("PercentCanceled", "Percent of flights Canceled: " + data.Percent_Canceled);
                //g.Attributes.Add("Lat", "Lat: " + data.Latitude);
                //g.Attributes.Add("Lon", "Lon: " + data.Longitude);                

                glist.Add(g);
            }

            MyGraphicsList = new ObservableCollection<Graphic>(glist);
        }
     
    }
}


Model:
namespace SIAW_Geospatial.Models
{

    public class Map_Model : Base_Model
    {
        private AirportDataServiceReference.AirportDataServiceClient airportDataServiceClient;
        private ViewModels.Map_ViewModel map_vm;


        public Map_Model(ViewModels.Map_ViewModel vm)    
        {
            map_vm = vm;

            airportDataServiceClient = new AirportDataServiceReference.AirportDataServiceClient();
            airportDataServiceClient.GetAirportDataCompleted += new EventHandler<AirportDataServiceReference.GetAirportDataCompletedEventArgs>(airportDataServiceClient_GetAirportDataCompleted);
        }

        void airportDataServiceClient_GetAirportDataCompleted(object sender, AirportDataServiceReference.GetAirportDataCompletedEventArgs e)
        {
            //set the combo boxes
            AirportData = e.Result;    
        }

        public void getAirportData()
        {
            airportDataServiceClient.GetAirportDataAsync();
        }

        private ObservableCollection<AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin> airportData;
        public ObservableCollection<AirportDataServiceReference.July2010RegScheduledFlightDelaysByOrigin> AirportData
        {
            get { return airportData; }
            set
            {
                airportData = value;
                RaisePropertyChanged("AirportData");

                //to start, set the filtered data to the whole set
                map_vm.FilteredAirportData = airportData;
            }
        }

    }

}
0 Kudos