Binding two Observable Collections with each other - c#

I have two properties of ObservableCollection<string> type (in separate projects); What I want to do is to bind these two using reflection and SetBinding like this -
//Get the PropertyDescriptor for first collection property
PropertyDescriptor relatedPropertyDesc = prop.Find(firstCollPropName, false);
Binding relatedPropBinding = new Binding(relatedPropertyDesc.Name);
relatedPropBinding.Source = this.SelectedItem;
relatedPropBinding.Mode = BindingMode.TwoWay;
//Bind the second collection property using binding created above
propItem.SetBinding(MyItem.SecondCollProperty, relatedPropBinding);
This SecondCollProperty is then bound to a ComboBox's ItemsSource.
As such this works correctly, values present in firstCollProperty are displayed correctly in combobox; but if some changes are made in firstCollProperty at run time then they are not reflected in ComboBox!(adding new items or creating new collection object).
Changes are reflected correctly after refreshing the binding(again executing the above code).
My question is - If two ObservableCollections are binded together why any changes in first doesn't get reflected in other? but same thing works for properties of string or double type.
Is there any way of achieving this?

Just going through some old unanswered questions and saw this. Undoubtedly you've come up with a workaround by now, but my recommendation would be look into something like CLinq, Bindable Linq, or Obtics for this. See this question for more details. You'd take the first collection, create a dynamic query against it, and expose that dynamic query (which implements IObservableCollection) as your second property.

Related

Using ViewModels in ObservableCollections in Prism

As far as I know, the default way to use a ObservableCollection that is bound to a listview is with model classes as elements (ObservableCollection<MyModel>). So when a listview element is selected, we use NavigateAsync and pass the model, which then can be used by the ViewModel to "fill itself".
The problem with this approach is, that it's not possible to use ViewModel properties for binding in the listview.
For example:
I have a View, ViewModel and Model "PickList", which contains a collection of "PickLine" objects - each having a View, ViewModel and Model themselves. The PickLine object contains a property "PickedQuantity" and a property "OpenQuantity". Now in my PickList view, I don't want to bind these two to separate items (e.g. two labels), but I want to have one label to display both I a format like for example "PickedQuantity / OpenQuantity". I know this example can be solved by using multi binding or something like this. But that's not the meaning of it all.
My PickLine ViewModel already has a property "QuantityString", that I want to bind to the label of a listview element via DataTemplate. But how can I do this. Is it even possible?
Make a property that combines the two other properties and bind to that. E.g.:
public string FullQuantity {get {return $"{PickedQuantity} / {OpenQuantity}";}}
Then in the setter for PickedQuantity and OpenQuantity, you will want to call whatever PropertyChanged method you have set up to notify the bindings of a property change and pass in the FullQuantity property name so elements that are bound to FullQuantity get updated when either PickedQuantity or OpenQuantity are changed.
This way, you are only binding one label's text to one property and that label would get updated when either of the two quantity properties are changed.
Note: I am unfamiliar with Prism, but this approach should work regardless of the Mvvm framework in use.
Your PickListViewModel should expose a collection property whose items are of type PickLineViewModel (not PickLine).
Whether you need an ObservableCollection<PickLineViewModel> depends on where changes can happen - in service / model that initially created the PickLines or in the GUI or both. In any way, you have to make sure the changes are propagated from one side (the collection of view models) to the other (the collection of models). Google wrapping observable collection as a starter (hint: avoid two-way sync if possible). These blog posts are old but still relevant and make a good reading. A trivial wrapping is described in this answer.

Why should I avoid instantiating a CollectionView directly?

The documentation of the CollectionView class says:
You should not create objects of this class in your code. To create a collection view for a collection that only implements IEnumerable, create a CollectionViewSource object, add your collection to the Source property, and get the collection view from the View property.
In other words, I am supposed to write:
var cvs = new CollectionViewSource();
cvs.Source = myData.Where(d => someCondition(d));
var view = cvs.View;
instead of
var view = new CollectionView(myData.Where(d => someCondition(d));
However, the documention fails to explain why I should do that. Both options seem to work.
What bad things will happen if I choose option 2 over option 1?
There are a few specializations of the CollectionView class, like for example ListCollectionView. Whenever you bind to some collection, there is a view automatically generated for you based on the type of the source collection. Instead of explicitly creating a CollectionView or a CollectionViewSource, you can get the default view of a collection by using the static CollectionViewSource.GetDefaultView method:
ICollectionView view = CollectionViewSource.GetDefaultView(myData);
It will return a ListCollectionView if myData implements IList.
It's perfectly fine to expose an ICollectionView from a view model if you perform the filtering or sorting in there. A CollectionViewSource is mainly used when you want to perform the filtering, sorting or grouping of a source collection in the view.
Binding CollectionView may present problems down the road if you want to change the way your data is displayed.
From the CollectionViewSource documentation:
Because a view does not change the underlying source collection, a source collection can have multiple views associated with it. By using views, you can display the same data in different ways. For example, you can use two views on a collection of Task objects to show tasks sorted by priority on one part of the page and grouped by area on another part of the page.
Binding directly to a single CollectionView limits the ways you can display your data.In short, CollectionViewSource plays nicer with your collections in XAML when binding.

Binding values from local array to specific property (Visibility) of all listbox items

I need to change Visibility of all items in listbox in code based on changing some filters. I tried to loop the items using
myListbox.ContainerFromIndex(i)
but only first a few didn't return null. The answer why I quickly found here:
Why ItemContainerGenerator.ContainerFromIndex() returns null and how to avoid this behavior?
Turning off virtualization doesn't look like respectable solution to me, also I don't feel well about scrolling every item to view before calling ContainerFromIndex, even if it could probably work.
I'm not very clever from the rest of topics dealing with bindings and dependency properties etc, maybe also because majority of them are written in xaml and not in code. But I'm pretty sure the answer should be there. I'm very new to c# and I didn't find any clear-cut solution to such a basic problem.
I will prepare something like this array of Visibilities
Visibility[] visibilities=new Visibility[100]
...
and I want myListBox with 100 items to use these values. Either automatically with some c# magic or in a cycle as I used to do in Win32.
Based on the comment of Depechie, I made a research targeted on collections and created a functional code. Here is what I did:
Instead of adding items directly to listbox by
lbGames.Items.Add(...)
I created two collections:
List<object> gameList=new List<object>();
ObservableCollection<object> filteredGameList = new ObservableCollection<object>();
Adding items this way:
gameList.Add(...); or filteredGameList.Add(...)
While gameList is holding all items (stack panels), filteredGameList is holding only those I want visible. I had some problems with using only one big collection using its Visibility property, maybe my mistake, maybe not.
Withouth ObservableCollection I wasn't able to redraw listbox, that was problem with another List<> holding filtered data. When I filter data, I first clear filteredGameList and then I add all items I want visible, never changing Visibility property. ObservableCollection always tells listbox when items change.
On the application init, I attach listbox to this filteredGamesList.
lbGames.ItemsSource = filteredGameList;
Now everything works fine.

How can I manually add an item to GridView.Items after binding ItemsSource?

I currently have a GridView populated with ItemsSource binding to an ObservableCollection.
It's showing the items as I intended but I'd like to manually add one more item at the end that looks and behaves differently. Is there a way I can do it without modifying the ObservableCollection the ItemsSource is bound to?
There are two approaches to this:
Using MVVM the original collection would be pulled up from the model layer and the additional item would be added in the view-model. It works as you can consider the additional item required only for the view, e.g. for example a list of poll options where you need to add an 'all of the above'.
Have two collections, separate, and then concatenate/join them together using an IValueConverter when you bind the ItemsSource. This approach is more tricky and only really useful when you need both collections separate and combined, e.g. a list of items in one collection and a list of item categories in another but also combined.
I have successfully used both approaches in a large widely-used WPF application.
Edit: Reading the comment on the question, if you did mean that you want a different visual or behaviour of the additional item you can achieve that using an Selector DataTemplateSelector and/or ItemContainerStyleSelector. I recently used a combination of option #2 and template selecting successfully.
Some options:
Use a CompositeCollection, you can define this is xaml. Not sure if it would solve the issue, you might need to play around with it a bit
Create a new collection behind the scenes that is populated with the items from your collection + the extra item.
Write a converter than creates a new collection (or returns an IEnumerable) with the new item added.
I guess all of these options have a common theme in that they are all a new collection. You basically can't bind directly to the original collection because it doesn't have the extra item, so you have to bind to something else in some form.

Update a single item in a DataGrid's ItemsSource

I have a simple problem that probably has a simple answer. I have a DataGrid displaying some items. I wish to update one of the items like so:
var old = (SomeClass)grid.SelectedItem;
var newItem = new SomeClass(...);
old = newItem;
//grid.Items.Refresh();
First, the code is trivialized in the example, but this is the important bit (the real issue is updating an object via Linq2Sql and then setting the old object to the updated one).
Anyway, I was hoping that a call to Items.Refresh would update the grid's UI, but it doesn't. I am certainly missing something in regards to how DataGrid's cache their ItemsSource elements, but I would imagine this is a pretty common scenario. Any idea?
You would need to get your Grid's ItemSource and update the correct item inside the ItemsSource. Right now you are making a copy of the selected item and updating that, so it does nothing.
I'm not sure the exact syntax but you'll want something like
((IEnumerable)grid.ItemsSource)[grid.SelectedIndex] = newItem;
You will need to modify the item in the ItemsSource. And even that will only work if the item is either a DependecyObject or implements INotifyPropertyChanged

Categories