Filtering issue with ICollectionView - c#

ImageAlbums is an ICollectionView type and GlobalCollection.MyImageAlubms is an ObservableCollection<T> type.
ImageAlbums = CollectionViewSource.GetDefaultView(GlobalCollection.MyImageAlubms);
ImageAlbums.Filter = new Predicate<object>(this.FilterImageAlbumList);
In a view I'm using ImageAlbums for showing a filtered image list. I have filtered the list using FilterImageAlbumList method. The problem is I have used the GlobalCollection.MyImageAlubms in another place. In that view I have used the GlobalCollection.MyImageAlubms directly as source but in there the list are being showed as filtered also. I am also providing the filter method here, following code represents the filter method
private bool FilterImageAlbumList(object item)
{
AlbumModel albumMoel = (AlbumModel)item;
if(LOGIC_OF_FILTERING)
{
return false;
}
return true;
}
Is there any way to filter only ImageAlbums without affecting the GlobalCollection. FYI - I won't deep copy the Global Collection.

Your problem is caused by these two facts:
Each collection instance has only one default (instance of the) view, thus CollectionViewSource.GetDefaultView always returns the same instance for the same argument
WPF binding mechanism does not bind directly to a collection, but to its default collection view
So if you set a filter on the default view, its effects are visible wherever you bind to the collection.
If you want a separate instance of an ICollectionView your best bet is to instantiate it manually. For ObservableCollection<T> a good choice is ListCollectionView. So this should resolve your problems:
ImageAlbums = new ListCollectionView(GlobalCollection.MyImageAlubms);

Related

Why the ItemsSource in WPF DataGrid IGNORES GetEnumerator() in my bound collection?

I try to implement filtering in my own observable collection.
My approach is following:
I assume the control using ItemsSource should call IEnumerable.GetEnumerator() on my collection to get the items it should render. So I define my own IEnumerable.GetEnumerator() to apply filtering.
Here's relevant code:
public Func<T, bool>? Filter { get; set; }
public void Refresh() {
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Reset
)
);
}
IEnumerator IEnumerable.GetEnumerator()
=> Filter is null
? (IEnumerator)BaseIEnumerableGetEnumerator.Invoke(this, null)!
: this.Where(Filter).GetEnumerator();
public new IEnumerator<T> GetEnumerator()
=> Filter is null
? base.GetEnumerator()
: this.Where(Filter).GetEnumerator();
private static readonly MethodInfo BaseIEnumerableGetEnumerator
= BaseType.GetMethod(
"System.Collections.IEnumerable.GetEnumerator",
BindingFlags.NonPublic | BindingFlags.Instance
)!;
BTW, my base class is List<T>. It also implements IList, ICollection, INotifyCollectionChanged and INotifyPropertyChanged.
Now - I set the filter.
Nothing happens.
So I call Refresh().
And to my surprise also nothing happens. Why? When Reset is sent to the ItemsCollection - the control should reload, and while reloading it should call GetEnumerator().
I set the breakpoint on my GetEnumerator() method, but it is not called on Refresh. WHY?
To clarify - I try to replicate exact ListCollectionView feature. It contains Refresh() method that applies the filtering.
Another weird thing I see is that my new GetEnumerator() is called by my own control, but it is not called AT ALL by DataGrid.
UPDATE:
As I've recently researched - built-in WPF controls might use some undocumented magic to bind items. They can trigger events on view model objects - that is possible (AFAIK) with Reflection.
IDK, using Reflection in the view you could dig into "underlying System Type" and use it's indexer if it's available to get items. In that case - it would just not use GetEnumerator.
I also checked the source code of ListCollectionView:
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/windows/Data/ListCollectionView.cs
It just uses a kind of shadow collection to achieve filtering. Well, that one way to achieve the effect for sure. But the easiest, if not the fastest way to filter any collection is to inject the filter into it's enumeration. No objects are created, no allocations, that should be fast. And easy. It works in my own control, that uses foreach on ItemsSource. It's obvious, foreach calls enumerator. There's no way around it. However, Microsoft's control obviously either don't use foreach or... they operate on something different than just the original items collection.
ItemsControl (including DataGrid, Listbox, etc.) works with the collection not directly, but through ICollectionView.
When a collection does not provide its own ICollectionView implementation (which is almost always the case), the ItemsControl itself creates the most suitable wrapper for it.
Typically, this is a CollectionView and types derived from it.
An example of a class that provides its own wrapper is CompositeCollection.
It provides a wrapper for the CompositeCollectionView.
The CollectionViewSource is also a means to create an ICollectionView for your collection.
Including using the GetDefaultView () method, you can return the default view of your collection.
This is what the ItemsControl uses when you pass your collection to it.
For almost all collections, a ListCollectionView will be a wrapper.
With the resulting wrapper, you can set properties to filter, group, sort, and render the collection view.
If you want to create your own presentation rules for your collection, then you need to implement the ICollectionViewFactory interface.
It has only one CreateView() method that returns the View wrapper for your collection.
You will have to create your own ICollectionView implementation (it's easier to do this based on the CollectionView or ListCollectionView classes).
And then return its instance in the CreateView() method.
OK, DataGrid just doesn't use GetEnumerator to display items.
It uses IList.this[index] and ICollection.Count for that.
So to implement filtering on my collection I had to create a ShadowList that contains filtered items.
Then I provided overrides for IList.this[index] and ICollection.Count to return items from the ShadowList.
Then I updated all adding to my collection to also update the ShadowList if the added item matches the Filter.
It works, however there's a catch: the filtered data is accessible only via IList indexer. So if the consuming control uses it - it will get filtered data, if not - it will get the original data.
I think it's preferable here. My view model needs original collection most of the time, and if it's not the case - I can always apply filter adding .Where(collection.Filter) to the enumerator.
GetEnumerator is called A LOT in a complex view model so it's best if it is the original List<T> enumerator.
The completed collection (ObservableList<T>) is available on GitHub:
https://github.com/HTD/Woof.Windows/blob/master/Woof.Windows.Mvvm/ObservableList.cs
It just uses a kind of shadow collection to achieve filtering.
For the WPF DataGrid, I believe it uses a backing CollectionViewSource, so filtering is a little bit different (as you say, it has a shadow collection).
MSDN has some info on filtering using the WPF DataGrid that I think might be helpful in your case.

WPF<-> entity binding

I have problem with updating data from my datasource(database through entity fw) to wpf-windows. I generate files using entity framework, so i'm accesing data from datebase this way:
public partial class sampleWindow : Window
{
myEntity en = new myEntity();
public sampleWindow()
{
InitializeComponent();
Bind();
}
private void Bind()
{
var list = from o in en.table select o;
someDatagrid.ItemsSource = list.ToList();
}
This method, firstly, was adequate for my program, i was refreshing 'Bind' method after i was doing some operations on database, so the data in my datagrids or combos was fresh. The problem occurs when i was changing database in diffrent wpf-windows. I have read that I should implement observable interface and use load instead of itemsSource. I tried to do it but i'm begginer and my attempts faild miserably. Could someone tell me step by step, what i should do?
You need a Singleton to manage your data, combined with using an ObservableCollection to expose the data. When the collection is changed by any view, it will notify any subscribers to the observation and they will automatically update.
See: Example of bindable list in XAML app (first part)
Example of Singleton
You would want to use a singleton for the instance of your entity as The Sharp Ninja mentioned. His article in the link he posted does a good job of explaining. You will want to use an observable collection to bind your ItemSource to. When an item is added or removed from an Observable collection the UI is automatically notified. The problem you are going to have is that there is not a .ToObservableCollection()
extension method build in to .net so you will have to implement your own.
I use this extension method
public static ObservableCollection<T> ToObservableCollection<T>(
this IEnumerable<T> enumeration)
{
return new ObservableCollection<T>(enumeration);
}
So now your bind method can set your ItemSource to the observable collection
private void Bind()
{
var list = from o in en.table select o;
someDatagrid.ItemsSource = list.ToObservableCollection();
}
There are so many and better ways (MVVM pattern) to accomplish this than your approach. To keep it simple it can be accomplished this way:
//Gets the Load() extention method available for DbSet
using System.Data.Entity;
private void Bind()
{
myEntity.table.Load();
/*Local returns an obvervable collection convenient for data binding.
This is a synchronized local view of your data. It means any item added , deleted and updated will be reflected in your controls.*/
var obsColl = myEntity.table.Local;
someDatagrid.ItemsSource = obsColl;
}

updating datagrid with BindingList

I've been looking at the difference between a BindingList and an observablecollection and List. From what I've read, it seems like the BindingList is the only collection type that will notify if an object in it has one of its properties changed. I cannot get this to work.
I have a property on a ViewModel called Matches, which returns a BindingList created out of a list of CarMatch objects in another class. (Cars m_Cars = new Cars();) My DataGrid on the View is bound to this Matches property in the VM.
public BindingList<CarMatch> Matches
{
get
{
Return new BindingList<CarMatch>(m_Cars.Matches);
}
}
Now, in the code I change one of the CarMatch object's properties, say.. automaticTrans = true from false. Matches[0].automaticTrans = true. I want to see that change in the DataGrid. Without implementing INotifyPropertyChanged inside of the CarMatch class, is there a way to update the datagrid from the viewmodel? Using INotifyPropertyChanged on Matches does not seem to do it. There is something about this I just don't understand, and could use an example to look at.
CarMatch (not Matches) has to implement INotifyPropertyChanged. But consider using ObservableCollection unless you really need some of the additional scenarios offered by BindingList: with ObservableCollection, INotifyPropertyChanged comes for free. And, more importantly, BindingList doesn't scale well.
try
dataGrid.Items.Refresh();
but keep in mind that is a expensive call if you have lots of data and you call it several times in a short period of time.

Why would overwriting .GetHashCode clear these databound values in WinForms?

We have run into a strange bug that we're having problems debugging.
We have a MDI workspace that uses Microsoft CAB, DevExpress components, and .Net 3.5.
If users open two windows in the workspace that each contain a UserControl bound to two separate data models, then minimize both of them, the first window to minimize is getting it's bound fields cleared when the second one minimizes.
The .Equals and .GetHashCode methods of the data model have been overridden so that both data models are considered equal. If we change that so they are unique, we do not get this behavior.
Here's some example pseudocode showing the problem
var a = new MyWindow();
a.DataModel = new SomeClass(123);
a.ShowInMdiWorkspace();
var b = new MyWindow();
b.DataModel = new SomeClass(123);
b.ShowInMdiWorksace();
a.Minimize();
// If SomeClass.GetHashCode() is overwritten to consider two objects
// as equal based on the value passed in, then the data bindings for A
// get cleared on this call. If SomeClass.GetHashCode is unique, then
// this problem does not happen.
b.Minimize();
Here's the Call Stack when the second window gets minimized:
At the EndEditSession() call in the stack trace above, it is calling EndEditSession for the second window minimized, while by the time the Stack Trace gets past the [External Code] to the OnChange breakpoint I have set, it is firing the change method in the first window.
EndEditSession() is something custom we have implemented which looks something like this
protected void EndEditSession()
{
IBindingValue bv = null;
if (_bindingValues == null)
return;
if (_data != null)
{
foreach (KeyValuePair<string, IBindingValue> kvp in _bindingValues)
{
bv = kvp.Value;
if (bv.IsBindable)
((PropertyManager)bv.Component.BindingContext[_data]).EndCurrentEdit();
}
}
}
_bindingValues gets populated when the UserControl initializes its data bindings. The key fields are the name of the bound control, and the value fields are a custom object which stores the control itself, its name, its bound value, and default value. bv.Component returns the control that the binding is set on, which in the case of my testing is a customized DevExpress LookupEdit
_data contains the data model for the UserControl, and I can verify that it is set to the instance for the second window.
My original thought was that the BindingContext was shared so the wrong PropertyManager was being returned, however I have verified that the .BindingContext for the two forms and controls are separate.
Is it possible that having two separate copies of a UserControl bound to two separate instances of a data model would get its bindings mixed up when the GetHashCode method has been overridden so that the two objects are considered equal?
I am not very familiar with the inner workings of the WinForms binding system, or with exactly how CAB's MDI workspace gets managed.
My theory is that when the first window minimizes, it is unloading the controls to save on memory, then when the second window minimizes the internal hash table that manages the bindings is incorrectly getting confused and running an update to take data from the first minimized window (which is now blank) and updating its datasource. There are plenty of holes in this theory, however its the only thing I can think of.
I don't know the internal workings the WinForm widget, but it seems that since you've encountered an issue with overriding equals that you'd be better off working around.
If you need to evaluate equality for your own purposes:
An approach is to provide your own method to evaluate equality, rather than changing the default behavior.
If your intention is to change how the widget treats the objects:
An approach is to make a static object factory for your class. The factory could maintain a collection of all of the objects created using weak references. Weak references allow the GC to collect the objects. The factory can then check the collection of previously created objects. If a match is found then return the existing one. If not then create it. This way rather than having two different objects that evaluate two equal (override equals) you'd have a single object with two references that is equal (same memory).
Hopefully one of these other approaches will solve your problem.
BindingContext object are not sharing its fields and properties with any other BindingContext because its fields and properties are not static.
But, it is possible to have one BindingContext object for several controls.
In the first case if several controls have the same parent and have not their own BindingContext then BindingContext property of this controls will return Control.Parent(.Parent...).BindingContext object.
In the second case there are can be something like this:
var bindingContext = new BindingContext();
var a = new SomeControl();
var b = new SomeControl();
a.BindingContext = bindingContext;
b.BindingContext = bindingContext;
In the third case BindingContext can be overwritten in such a way.
I don't know what is going on in your case, so I can only recommend to do something like this before initializing data bindings:
var a = new SomeControl();
var b = new SomeControl();
a.BindingContext = new BindingContext();
b.BindingContext = new BindingContext();
If this does not solve your problem then you need to check the populating of your _bindingValues object. It is possible that during the populating of this object it is populated with wrong values.

ObservableCollection and CollectionView

I'm writing an application that reads data from a local db and display them in a listbox(I'm working in WPF).
I've a DVD object, where its properties are the columns of the db. This DVD object also implements INotifyPropertyChanged. "MyDVDs" is the table that refers to db. Once created these object, I create a class that inherits from ObservableCollection and takes data from "MyDVDs" in the constructor. However I don't need only to add, remove and update data from the listbox, but I also need to sort and filter them. Here is the code for ObservableCollection:
class ObservableDVD : ObservableCollection<DVD>
{
private ICollectionView collection;
public ObservableDVD(MyDVDs e)
{
foreach (DVD d in e.DVDs)
{
this.Add(d);
}
Collection = CollectionViewSource.GetDefaultView(this);
}
public ICollectionView Collection
{
get { return collection; }
private set { collection = value; }
}
}
I wanted to know, this is a good way?? Or can I do better?
In the MainWindow of the project, when Load_Window event fires, I assign the Collection property to listbox.ItemSource(in MainWindow code-behind I declare a private field that obviously refers to an ObservableDVD Object). I have some buttons that allow me to do the operations I tell you before.In the event headler of the buttons, I directly update and modify the ObservableDVD Object, not its property Collection. However, the Collection property also reflects those changes.
Why this behavior occurs?
It's ok for me, but I can't understand why it's happens. Is because of the notifications?
The property Collection has a reference to the view of the ObservableDVD. Being a reference means pointing to the same data in memory.
ObservableCollection Class Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
The Collection you are specifying is just a "view" of the ObservableDVD collection. Which means that both are really pointing to the same data in the memory, they're not 2 separate things. A "view" can be a subset of items when you apply filters to a collection, for instance.
Otherwise said, your ObservableDVD contains your "Data Table" for the entire dataset while the ICollectionView lets you manipulate which records/objects are visible to the user through custom logic.

Categories