Search code examples
wpfrefreshviewmodel

WPF - Viewmodel - Binding errors while refreshing data


I have a MVVM pattern test application that is tossing a lot of

System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ChuteGroup') from 'Groups' (type 'ChuteGroupsModel'). BindingExpression:Path=Groups[0]; DataItem='MainViewModel' (HashCode=41802290); target element is 'ChuteView' (Name=''); target property is 'DataContext' (type 'Object') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. Parameter name: index'

This happens when I enter my "onRefresh" routine in the viewmodel. I have an observable collection called "Current" and the first thing I do in the refresh routine is clear the entries from the Current collection. I then get a slew of these data error 17 messages because I think in the background the bindings are trying to update and now there isn't anything in the collection until I re-fill and re-create each entry into the observable collection.

Is there a better way of doing this? Runtime performance doesn't seem to be affected by this but I don't like errors in my output window. I found that if I didn't clear the collection it just doubled in size each time the viewmodel refreshed itself. Since this collection is used in conjunction with 54 UI elements, which are binded by index, the collection can't double in size or everything wont point to the correct UI element.

private void FetchData()
    {
        ChuteGroupsModel.isDuringRefresh = true;
        DataSet sqldata = new DataSet();
        SqlConnection conn = new SqlConnection("Data Source=(local);Initial Catalog=ScratchPaper;User ID=somecacct;Password=somepassword;Connect Timeout=5");
        SqlCommand cmd = new SqlCommand("Select chuteGroup, chuteGroupDef, TotalChutes, UpPackWave, UpColorId, UpPackWaveTS, DownPackWave, DownColorId, DownPackWaveTS from ChuteGroups Order by chuteGroup asc",conn);
        SqlDataAdapter da = new SqlDataAdapter(cmd);
        try
        {
        da.Fill(sqldata);

        }
        catch (Exception Ex){ MessageBox.Show(Ex.ToString());}


       //DataSet sqldata = this.DataLayer.getDataSet("Select * from AvailableColors Order by ID asc", CommandType.Text, null);
        foreach (DataRow row in sqldata.Tables[0].Rows)
        {
            ChuteGroup group = new ChuteGroup((int)row.ItemArray[0], (string)row.ItemArray[1], (int)row.ItemArray[2], (string)row.ItemArray[3],(string)row.ItemArray[4], (DateTime)row.ItemArray[5], (string)row.ItemArray[6], (string)row.ItemArray[7], (DateTime)row.ItemArray[8]);
            Add(group);
        }
        ChuteGroupsModel.isDuringRefresh = false;
    }

 private void onRefresh(object sender, System.EventArgs e)
    {
        try
        {     
            if (ChuteGroupsModel.isDuringRefresh)
            {
                return;
            }
            Refresh();
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message.ToString());
        }
    }
 public void Refresh()
    {
        Current.Clear();  //Current is a static reference to my collection
        FetchData();
    }

Solution

  • You are right in assuming that the binding errors are due to your collection being cleared. As you aren't experiencing any performance penalties, I wouldn't worry about it too much.

    If you are really annoyed by them, you can create a new observable collection type to allow you to set the new values. If you crack open the ObservableCollection class in Reflector, you'd copy the implementation, and add one new method like so:

    public class ObservableList<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
    {
        // ObservableCollection implementation here
        ...
    
        public void SetItems(IEnumerable<T> items)
        {
            this.CheckReentrancy();
            base.ClearItems();
    
            int i = 0;
            foreach (var item in items)
            {
                base.InsertItem(i, item);
                ++i;
            }
    
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
            this.OnCollectionReset();
    
        }
    }
    

    Then, instead of clearing your data and adding it one row at a time, you'd call the SetItems method.