1
votes

Hi I have a WPF datagrid which is bound with TrulyObservableCollection, Whenever I try editing a cell through textinput the datagrid cell looses focus after each key entered,

public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
    where T : INotifyPropertyChanged
{
    public TrulyObservableCollection()
    {
        CollectionChanged += FullObservableCollectionCollectionChanged;
    }

    public TrulyObservableCollection(IEnumerable<T> pItems)
        : this()
    {
        foreach (var item in pItems) {
            this.Add(item);
        }
    }

    private void FullObservableCollectionCollectionChanged(object sender,
        NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null) {
            foreach (Object item in e.NewItems) {
                ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
            }
        }
        if (e.OldItems != null) {
            foreach (Object item in e.OldItems) {
                ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
            }
        }
    }

    private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        try {
            var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            OnCollectionChanged(a);
        }
        catch {
            // ignored
        }
    }
}

Below is my ViewModel, Which is binded to Datagrid,

public TrulyObservableCollection<SubLotModel> SubLotCollection
{
    get { return _subLotCollection; }
    set
    {
        _subLotCollection = value;
        NotifyOfPropertyChange(() => SubLotCollection);
        SerialNumberAdded = _subLotCollection.Count;
    }
}

In the Datagrid i have a textbox column which is binded to Quantity property in SublotCollection,

<DataGridTemplateColumn Header="Quantity">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Quantity, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>

        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

As soon as i type a valid key in texbox column the cell loses focus.

1
My guess would be that each time you type in a letter the view-model Quantity property is updated raising the PropertyChanged event, which causes your TrulyObservableCollection to raise CollectionChanged with NotifyCollectionChangedAction.Reset, which in turn causes the DataGrid to rebuild itself, causing previously focused editor to lose the focus (possibly it's not even the same editor anymore, but a newly created instance in it's place). - Grx70
@Grx70: Good point. But why are you (the OP) raising a Reset event each time a property of an item is changed, i.e. what is the purpose of using the TrulyObservableCollection class? - mm8
I am using TrulyObservable collection so that i can receive notification when an item inside the collection changes as well. - Vinay
But why are you raising the Reset event each time an item is changed...? It just doesn't make any sense to me. - mm8

1 Answers

4
votes

Diagnosis

As I mentioned in my comment the reason why your editor loses focus is this chain of events:

  • You type in a letter, the new text is pushed to the view-model because of the UpdateSourceTrigger=PropertyChanged
  • The Quantity property on your view-model is updated and PropertyChanged event is raised
  • Your TrulyObservableCollection handles the PropertyChanged event and in consequence raises CollectionChanged event with action set to NotifyCollectionChangedAction.Reset
  • The DataGrid is designed to rebuild itself on such occasion, so each cell is constructed from scratch (re-templated), and ultimately there's a completely new TextBox where there used to be the one you used to type in the letter

The focus is not returned to the new TextBox because the DataGrid is not really aware of it's existence (since it's defined in a DataTemplate). I don't think the focus would be restored even if you used DataGridTextColumn either (but you can check).

So the core issue here is the NotifyCollectionChangedAction.Reset. It seems like total overkill to claim the collection was drastically changed when merely one property of one item was changed.

Solution

Much more reasonable choice would be to raise the CollectionChanged with NotifyCollectionChangedAction.Replace including information which item was "replaced". Together with some improvements here's a collection implementation that should behave like you expect (it did when I tested it):

public class TrulyObservableCollection<T> : ObservableCollection<T> 
    where T : INotifyPropertyChanged
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        if (e.NewItems != null)
        {
            foreach (INotifyPropertyChanged inpc in e.NewItems)
                    inpc.PropertyChanged += Item_PropertyChanged;
        }
        if (e.OldItems != null)
        {
            foreach (INotifyPropertyChanged inpc in e.OldItems)
                    inpc.PropertyChanged -= Item_PropertyChanged;
        }
    }

    private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var index = IndexOf((T)sender);
        var args = new NotifyCollectionChangedEventArgs(
            action: NotifyCollectionChangedAction.Replace, 
            newItem: sender, 
            oldItem: sender, 
            index: index);
        //no need to call the override since we've already
        //subscribed to that item's PropertyChanged event
        base.OnCollectionChanged(args); 
    }
}