Select to view content in your preferred language

How to Remove Items from a DataGrid Bound to an ObservableCollection?

4598
3
10-06-2010 11:30 AM
RussCoffey
Occasional Contributor
How do I remove items from a DataGrid bound to an ObservableCollection?  I can remove items from the ObservableCollection, but the DataGrid does not update.  (And I might more correcly phrase the question as "how do I properly bind a DataGrid to an ObservableCollection to realize full functionality of interaction with the DataGrid"?)  Here is the background.

Step 1: Allow the user to select line features, which are then displayed as graphics in a graphicslayer and attribute information in a DataGrid.
Solution: The "Spatial Query" sample code did this quite nicely.

Step 2: Allow the user to keep working with the selection set, adding/removing records. 
My solution that doesn't work yet: Write the results of the QueryTask into an ObservableCollection and alter the binding of the DataGrid to be the ObservableCollection instead of the QueryTask results.

My solution for Step 2 works well for performing the SpatialQuery repeatedly and adding new rows to the DataGrid.  I can remove graphics and items from the ObservableCollection (verified by checking the Count property after each delete), but I cannot tell why the DataGrid will not recognize deletes.

Thanks for any help or suggestions.

.........j.russ


Here is my code as it currently stands, and I realize that I probably have a few crossed wires at this point.


  private void MyDrawSurface_DrawComplete(object sender, ESRI.ArcGIS.Client.DrawEventArgs args)
  {

   QueryTask queryTask = new QueryTask("http://arcserverdev/arcgis/rest/services/ETS/Record_Drawing_Request/MapServer/5");
   queryTask.ExecuteCompleted += QueryTask2_ExecuteCompleted;
   queryTask.Failed += QueryTask2_Failed;

   Query query = new ESRI.ArcGIS.Client.Tasks.Query();
   query.OutSpatialReference = MyMap.SpatialReference;

   // Specify fields to return from query
   query.OutFields.AddRange(new string[] { "INSTALLPROJID", "IDFEATURE", "DIAMETER", "MATERIAL" });
   query.Geometry = args.Geometry;

   // Return geometry with result features
   query.ReturnGeometry = true;

   queryTask.ExecuteAsync(query);

  }


  private void QueryTask2_ExecuteCompleted(object sender, ESRI.ArcGIS.Client.Tasks.QueryEventArgs args)
  {
   FeatureSet featureSet = args.FeatureSet;
   GraphicsLayer graphicsLayer = MyMap.Layers["MySelectionGraphicsLayer"] as GraphicsLayer;

   if (featureSet == null || featureSet.Features.Count < 1)
   {
    MessageBox.Show("No features returned from query");
    return;
   }

   if (first_spatial_query)
   {
    _thisgrid.Clear(); //Should already be empty anyway
    Binding resultFeaturesBinding = new Binding("LastResult");
    resultFeaturesBinding.Source = _thisgrid;
    resultFeaturesBinding.Mode = BindingMode.TwoWay;
    QueryDetailsDataGrid.SetBinding(DataGrid.ItemsSourceProperty, resultFeaturesBinding);
    QueryDetailsDataGrid.ItemsSource = _thisgrid;

////I realize that this next block of code overwrites/undoes some of the previous lines of code, but these are the lines that allow the graphics and the DataGrid to be in sync (and obviously I am still experimenting).
    QueryDetailsDataGrid.Columns.Clear();
    QueryDetailsDataGrid.ItemsSource = featureSet;  // featureSet.Features;
    foreach (KeyValuePair<string, string> kv in featureSet.FieldAliases)
    {
     DataGridTextColumn dataGridColumn = new DataGridTextColumn();
     Binding b = new Binding("Attributes");
     b.Converter = new DictionaryConverter();
     b.ConverterParameter = kv.Key;
     dataGridColumn.Binding = b;
     dataGridColumn.Header = kv.Value;
     QueryDetailsDataGrid.Columns.Add(dataGridColumn);
    }

    first_spatial_query = false;
   }

   if (featureSet != null && featureSet.Features.Count > 0)
   {
    foreach (Graphic feature in featureSet.Features)
    {
     feature.Symbol = LayoutRoot.Resources["ResultsLineSymbol"] as LineSymbol;
     graphicsLayer.Graphics.Insert(graphicsLayer.Graphics.Count, feature);
     PipeItem newrow = new PipeItem(feature.Geometry as ESRI.ArcGIS.Client.Geometry.Polyline, feature.Attributes["INSTALLPROJID"].ToString(), feature.Attributes["IDFEATURE"].ToString(), feature.Attributes["DIAMETER"].ToString(), feature.Attributes["MATERIAL"].ToString());
     _thisgrid.Add(newrow);
    }
    //MessageBox.Show(graphicsLayer.Graphics.Count + " graphics created.");
    ResultsDisplay.Visibility = Visibility.Visible;

   }

   MyDrawSurface.IsEnabled = false;
  
  }


Here is one of the points where I am trying to remove a record from the DataGrid (any record at this point - I am currently just trying to remove the first one in the list).

  private void GraphicsLayer_MouseEnter_DeleteRecords(object sender, GraphicMouseEventArgs args)
  {
   //The next two lines work fine for removing the graphic
   GraphicsLayer selectionGraphicslayer = MyMap.Layers["MySelectionGraphicsLayer"] as GraphicsLayer;
   selectionGraphicslayer.Graphics.Remove(args.Graphic);
     //The next line removes a record from the ObservableCollection, but does nothing to the DataGrid.
   _thisgrid.RemoveAt(0);
  }


And finally, here is my ObservableCollection:

public class PipeGrid : ObservableCollection<PipeItem>
{
}

public class PipeItem
{
  private ESRI.ArcGIS.Client.Geometry.Polyline geom;
  private string projid;
  private string idf;
  private string dia;
  private string mat;

  public PipeItem(ESRI.ArcGIS.Client.Geometry.Polyline GEOM, string ProjID, string IDF, string DIA, string MAT)
  {
   this.GEOMETRY = GEOM;
   this.IDFEATURE = IDF;
   this.INSTALLPROJID = ProjID;
   this.DIAMETER = DIA;
   this.MATERIAL = MAT;
  }

  public ESRI.ArcGIS.Client.Geometry.Polyline GEOMETRY
  {
   get { return geom; }
   set { geom = value; }
  }
  public string INSTALLPROJID
  {
   get { return projid; }
   set { projid = value; }
  }
  public string IDFEATURE
  {
   get { return idf; }
   set { idf = value; }
  }

  public string DIAMETER
  {
   get { return dia; }
   set { dia = value; }
  }
  public string MATERIAL
  {
   get { return mat; }
   set { mat = value; }
  }
}
0 Kudos
3 Replies
JenniferNery
Esri Regular Contributor
The key here is to assign the DataGrid's ItemsSource once through Binding to an instance of your ObservableCollection and manipulate this collection instead of re-assigning the DataGrid's ItemsSource because that will defeat the purpose of Binding.

In your XAML, you can create an instance of your PipeGrid or simply use esri's GraphicCollection.
  <Grid x:Name="LayoutRoot" Background="White">
  <Grid.Resources>
   <esri:GraphicCollection x:Key="MyGraphicCollection"/>
  </Grid.Resources>
<!-- more code goes here -->
     <slData:DataGrid x:Name="QueryDetailsDataGrid" ItemsSource="{Binding Source={StaticResource MyGraphicCollection}}"
<!-- more code goes here -->


In the code-behind, you can grab the instance of your ObservableCollection and update them after the query has completed.
        public MainPage()
        {
            InitializeComponent();
            myGraphics = this.LayoutRoot.Resources["MyGraphicCollection"] as GraphicCollection;
        }
        GraphicCollection myGraphics;
        void QueryTask_ExecuteCompleted(object sender, ESRI.ArcGIS.Client.Tasks.QueryEventArgs args)
        {
            FeatureSet featureSet = args.FeatureSet;
            if (featureSet != null && featureSet.Features.Count > 0)
            {
                if (myGraphics != null)
                {
                    myGraphics.Clear();
                    foreach (Graphic g in featureSet.Features)
                        myGraphics.Add(g);
                }
            }
        }


This way anytime you remove a graphic from myGraphics, the DataGrid updates itself.
0 Kudos
RussCoffey
Occasional Contributor
Thank you, Jennifer.  Your reply was very helpful and my app now performs the way I want it to.

I was able to simply utilize the GraphicCollection as you suggested and drop the ObservableCollection and the code now adds and removes records from both the map view and the DataGrid.

However, I need to understand more about GraphicCollection because when I read your response, I thought I would utilize the GraphicCollection and drop the GraphicsLayer.  Instead, I am using both - adding and removing features from the GraphicCollection (because it is bound to the DataGrid) and the GraphicsLayer (for the display).  (Is this efficient/necessary?)

In the end, I want a unique list of the feature IDs that were selected by the user, and I will just pull that from the attributes in the GraphicCollection/DataGrid.  (Do you see any problem with me going this route and dropping the ObservableCollection?)

Thanks again for your help.
0 Kudos
JenniferNery
Esri Regular Contributor
Actually I don't know what your GraphicsLayer is used for. Are you referring to an SDK sample?

If your GraphicsLayer will update its Graphics based on the result of the query, then having an instance of GraphicCollection is not necessary.

You can update your ItemsSource binding to:
<slData:DataGrid x:Name="QueryDetailsDataGrid" ItemsSource="{Binding ElementName=MyMap, Path=Layers[MyGraphicsLayer].Graphics>


Note that the graphics contained in your GraphicsLayer should also include the attributes for your DataGrid.Columns binding to work. You might need something like:
graphicsLayer.Graphics.Clear();
foreach(Graphic g in featureSet.Features)
{
 Graphic graphic = new Graphic(){Geometry = g.Geometry};   
 foreach(var item in g.Attributes) 
  graphic.Attributes.Add(item.Key, item.Value);
 graphicsLayer.Graphics.Add(graphic);
}
0 Kudos