Collection binding like "Mode=TwoWay" scenario question

Apr 5, 2010 at 4:55 PM

Hi Michael!

I'm now using UpdateControls successfully in the application with some 70 quite sophisticated views and it did bring a lot of ease in handling property changes. So, once again, thanks a lot!

Then I have one interesting scenario that I wanted to share with you and check your opinion on usage of UpdateControls in this case.

I have a DataGrid bound to collection in my viewmodel (ItemsSource={Binding Records} - nothing special). Then I wanted to have multiple selection in DataGrid and collection of selected items to be bound in viewmodel. I found this extension. it suits my needs and it was the best available so I went to check it out. I added it to my DataGrid declaration as:

 

DataGridMultipleSelection.SelectedItemsSource="{Binding CurrentRecords}"

and according to debugging it seems to work as advertised with the only exception: changes to collection coming from this extension never reach the viewmodel's collection. CurrentRecords is declared as IList<T>. I tried changing the type to ObservableCollection<T> to see how it will work with unwrapped collection but the problem then is that DataGrid is bound to collection of wrappers over actual data objects (ObjectInstance<T>) while collection can accept only T-typed objects. One option here is to upgrade my selection binding extension to unwrap actual data objects from UpdateControls wrappers but it sounds really wrong to me + I will loose UpdateControls handling on CurrentRecords property (if I understand things correctly). Do you have any idea of how can I accomplish this scenario with UpdateControls?

 

Thanks in advance!

Coordinator
Apr 6, 2010 at 5:27 AM

Unfortunately, Update Controls is unprepared to apply two-way data binding to a collection. I usually expose a collection as IEnumerable<T> -- not as List<T> or ObservableCollection<T> -- in order to enforce the read-only nature of the property.

When you read a collection, you need to call OnGet(). When you change it, you need to call OnSet(). Modifying a collection that you've read bypasses the OnSet().

One way that you could attack this problem is by creating your own IList<T> implementation. Put OnGet() calls in the accessors, and OnSet() calls in the mutators. That way, when the extension modifies the collection, it triggers the OnSet()s.

Give this a try. If it solves your problem, I'll add it to the library:

    public class IndependentList<T> : IList<T>
    {
        private IList<T> _list;
        private Independent _indList = new Independent();

        public IndependentList()
            : this(new List<T>())
        {
        }

        public IndependentList(IList<T> list)
        {
            _list = list;
        }

        public int IndexOf(T item)
        {
            _indList.OnGet();
            return _list.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            _indList.OnSet();
            _list.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            _indList.OnSet();
            _list.RemoveAt(index);
        }

        public T this[int index]
        {
            get
            {
                _indList.OnGet();
                return _list[index];
            }
            set
            {
                _indList.OnSet();
                _list[index] = value;
            }
        }

        public void Add(T item)
        {
            _indList.OnSet();
            _list.Add(item);
        }

        public void Clear()
        {
            _indList.OnSet();
            _list.Clear();
        }

        public bool Contains(T item)
        {
            _indList.OnGet();
            return _list.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            _indList.OnGet();
            _list.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { _indList.OnGet(); return _list.Count; }
        }

        public bool IsReadOnly
        {
            get { return _list.IsReadOnly; }
        }

        public bool Remove(T item)
        {
            _indList.OnSet();
            return _list.Remove(item);
        }

        public IEnumerator<T> GetEnumerator()
        {
            _indList.OnGet();
            return _list.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            _indList.OnGet();
            return _list.GetEnumerator();
        }
    }

Apr 6, 2010 at 8:01 AM

Well, I gave it a try but it doesn't work. I think the problem lies in ObjectPropertyCollection. It exposes its own ObservableCollection<object> _collection and keeps it in sync with the source collection but not vice versa. I guess, to allow such behaviour it is necessary to subscribe to _collection.CollectionChanged in ObjectPropertyCollection and propagate changes down to source collection. Not sure if it's an easy thing to implement though.

May 16, 2010 at 12:42 AM

Michael,

I wanted to thank you for your Update Controls. I'm finishing a very large project using around 120 view models and many more views and it works great.

As far as multiselect option, I have used 2 work arounds to make it work with Update Controls.

1. I used a similar logic to the above suggestion made by you, but I did it using attached behaviour for DataGrid and I had to change your collections class, where it throws Not Implemented exception for two way binding.

2. In the other workaround I just created IsSelected property with Independent.OnGet and OnSet inside it and bound DataGrid cell style to that property and multiselect worked perfectly.

  <wpfToolkit:DataGridTextColumn Header=" Friday " Width="90" Binding="{Binding Path=Friday}" IsReadOnly="True">
      <wpfToolkit:DataGridTextColumn.CellStyle>
          <Style TargetType="{x:Type wpfToolkit:DataGridCell}">
               <Setter Property="IsSelected" Value="{Binding Path=Friday.IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
           </Style>
      </wpfToolkit:DataGridTextColumn.CellStyle>
 </wpfToolkit:DataGridTextColumn>
So it works great over all. Most of the other issues had been overcome as well. 
The only other problem I have is that it is more difficult now to control UI refreshes and if there is too much data displayed on a UI with IQueryable 
then I get too many database calls.
But this is more a design issue and is not related much to Update Controls.
Great work and take care!
Jun 19, 2010 at 7:34 AM

Hi ivan,

It seems that the 2nd solution you provided may cause problems similar to this one:

http://stackoverflow.com/questions/1273659/virtualizingstackpanel-mvvm-multiple-selection

I'm really getting a little confused about how to write navigation models for collections...

Jul 7, 2010 at 7:02 PM

@tomtung,

Well there are two ways the binding works from source and to source:

to source (or from UI):
- items in DataGrid can't be selected unless they are visible on the UI, which means they get bound to view model as soon as UI element is instantiated.

from source (or from View Model):
- as soon as DataCell gets into view binding kick in and gets data from View Model which has either IsSelected=true or =false. So this works fine as well.

I tried it on big collections and small and it works fine. Even Ctrl+A works, when items are not visible.

  

Jul 8, 2010 at 6:41 PM
I forgot to mention that IsSelected is an independent property.