ObservableCollection<> not loading properly - c#

I am trying to implement filtering on an ObservableCollection<>. My current ObservableCollection<Payee> is working fine as an ItemsSource on a GridView. I added a second ObservableCollection<Payee> called FilteredPayees to use as the ItemsSource. For some reason, when I try to filter the items, the GridView is showing up blank.
Here is the code I'm using:
private void FilterPayees()
{
if (!_settings.ShowInactivePayees)
{
var filtered = _payees.Where(p => p.IsOpen == true);
_filteredPayees = new ObservableCollection<Payee>(filtered);
}
else
{
_filteredPayees = _payees;
}
this.FilteredPayees = _filteredPayees;
}
Basically, if the ShowInactivePayees setting is turned off, it should filter out the inactive payees. If it is on, then just use the full _payees collection. The strange thing, if I change the last line to:
this.FilteredPayees = _payees;
then the GridView will display all of the payees, just as it should if the "show inactive payees" settings is turned on. I set breakpoints and the _filteredPayees collection has 35 items in it (or 65 when not filtering). It does not appear to be any type of "object not set to an instance of an object" or anything like that. Is there some reason that
this.FilteredPayees = _payees;
would work, but
_filteredPayees = _payees;
this.FilteredPayees = _filteredPayees;
would not?
EDIT
I was able to get it to work for now by getting rid of the FilteredPayees property. I just filter the original Payees collection in the OnNavigatedTo() event handler, which is exactly the same place where I was calling FilteredPayees().
// load payees
var payees = await _payeesRepository.LoadAllAsync();
if (!_settings.ShowInactivePayees)
{
payees = payees.Where(p => p.IsOpen);
}
payees = payees.OrderBy(p => p.CompanyName);
this.Payees = new ObservableCollection<Payee>(payees);
The only part I added was the if (!_settings.ShowInactivePayees) ... block. My reasoning to use the FilteredPayees property was so that I could have the full collection loaded in the Payees property and not need to reload if the ShowInactivePayees setting was changed - just change the filter of the collection.

You are assigning a new object to FilteredPayees property, so GridView has to be notified that the property FilteredPayees is changed. There should be RaisePropertyChanged("FilteredPayees") or your notification code in the setter of FilteredPayees.
Also, the binding mode of GridView.ItemsSource should not be BindingMode.OneTime.

For filtering a collection in WPF it may be easier to use ICollectionView. For example:
public class Foo
{
private List<Payee> _payees;
private ICollectionView _filteredPayees;
public ICollectionView FilteredPayees
{
get { return _filteredPayees; }
}
public Foo()
{
_payees = GetPayees();
_filteredPayees = CollectionViewSource.GetDefaultView(_payees);
_filteredPayees.Filter = FilterPayees;
}
private bool FilterPayees(object item)
{
var payee = item as Payee;
if (payee == null)
{
return false;
}
if (_settings.ShowInactivePayees)
{
return true;
}
return payee.IsOpen;
}
}
You can bind the property FilteredPayees like any other property. The advantage is, that you don't need two properties and you can avoid the logic of which collection you want to bind.

_filteredPayees = new ObservableCollection<Payee>(filtered);
Here you create a completely new object, and that's not something ObservableCollection can automatically observe. The possible solution is to set ItemsSource on your GridView again after this line.

Related

How to get Item being added before commit in WPF DataGrid?

I've got a ViewModel, which is used by a View with a DataGrid.
In the DataGrid I try to allow the User directly to add new rows, but some of the columns need to open a new Dialog for the value.
Therefor I set the SelectedItem - Binding and added some Extensions to allow binding OnBeginningEdit and OnRowEditEnding to custom commands in my ViewModel.
Now I have the problem, that I want to get the current "new" item, which is partly filled by the GUI, before calling CommitEdit on the DataGrid.
I tried to use IEditableCollectionView and use CurrentAddItem of it, to achieve this.
But the CurrentAddItem does not get filled with the items entered in the GUI.
How may I achieve that properly in MVVM?
public ICommand DataGridDoubleClickCommand
{
get
{
return new Command(
delegate (object obj)
{
var dataGrid = ((object[])obj)[0] as DataGrid;
var args = ((object[])obj)[1] as MouseButtonEventArgs;
if (dataGrid.CurrentColumn.DisplayIndex != 2)
{
return;
}
args.Handled = true;
ICollectionView view = CollectionViewSource.GetDefaultView(dataGrid.Items);
IEditableCollectionView iecv = (IEditableCollectionView)view;
if(iecv.IsAddingNew)
{
// tried to use iecv.CurrentAddItem here - but was empty
}
dataGrid.CommitEdit();
...

Update ObservableCollection without blinking

Could someone help me, how can I update ObservableCollection, which is binded to ListView ItemSource, without blinking? When I do:
Contacs = _contacs;
the whole ListView is blinking. I would like to search in ListView too, but always after replacing the old results with new one, the listview blinks.
The problem here is, that you are reassigning the whole collection. This does not take advantage of the observability and forces the ListView to reload all items. Try to remove/add the items instead so the ListView only needs to update the Items that actually changed.
In the case of searching hiding the unmatched results might be a viable solution too. To do that create a property on your Contact type (called "IsVisible" for example) and bind it to the ListViewItems Visibility Property. (You might need the build in BooleanToVisibility Converter here)
Update
As pointed out in the comments using a CollectionViewSource is the correct wpf way of implementing a search filter. See this answer for details on how to use it.
If you want to enable filtering in your collection then there is no actual need to perform operations directly on your collection.
Use ICollectionView and CollectionViewSource for this purpose.
As you have an ObservableCollection so you can do something like this.
ICollectionView contactsView;
public ICollectionView ContactsView
{
get { return contactsView; }
set
{
if(contactsView != value)
{
contactsView = value;
}
}
}
And in the setter of the ObservableCollection
public ObservableCollection<ContactType> Contacs
{
get { return _contacs; }
set
{
if(_contacs != value)
{
_contacs = value;
ContactsView = CollectionViewSource.GetDefaultView(value);
ContactsView.Filter = ContactsFilter;
}
}
}
where ContactsFilter is a function with following definition.
bool ContactsFilter(object item)
{
var contact = item as ContactType;
if(condition)
return true; //show this item in ListView.
return false; //Do not show this item in ListView
}
and whenever you want to filter items you can do that just by
ContactsView.Refresh();
which I think will be in the TextChanged Event of your text box in which you are entering search query.
More detailed article is at CollectionViewSource

Binding a DataGrid*Column to data in code-behind

I would like to bind a DataGrid*Column (in this particular case, a DataGridTextBox) to its data in the code-behind. This is because, depending on a CheckBox's IsClicked property, the Column needs to bind to different collections.
Solutions such as this one all point to the following sort of code:
var binding = new Binding("X");
XColumn.Binding = binding;
Now, I've made use of this sort of code in other parts of my program with success, just not with a DataGrid*Column. With the column, however, this is not working as expected, since in fact all the rows of the column are presenting the X-value of the first element of the collection. This is confirmed when I edit any of the cells and all of them are altered, meaning they are all bound to the same single element of the collection, not to the collection as a whole.
Here is the relevant code:
//This is called whenever the CheckBox EqualToResults is clicked
void ControlBindings()
{
//only showing for (.IsChecked == true), but the other case is similar
//and presents the same problems
if (EqualToResults.IsChecked == true)
{
var cable = DataContext as NCable;
//This is the DataGrid object
Coordinates.ItemsSource = cable;
var binding = new Binding("X");
binding.Source = cable.Points;
//XColumn is the DataGridTextColumn
XColumn.Binding = binding;
}
}
Should it be relevant, here's the relevant code for the NCable class.
public class NCable : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<NPoint> Points;
public static DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(ICollectionView), typeof(NCable));
public ICollectionView IPointCollection
{
get { return (ICollectionView)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
public NCable(string cableName)
{
Points = new ObservableCollection<NPoint>();
for (int i = 0; i < 11; i++)
Points.Add(new NPoint(1,1));
IPointCollection = CollectionViewSource.GetDefaultView(Points);
}
}
EDIT 13/05: I've seen somewhere that one must also set the ItemsSource of the DataGrid in this case, so I've done that as well (edited the original code), but still to no avail. The entire column is still bound to the first element of the collection.
Figured it out. In this case, the DataGrid.ItemsSource must be defined (as per the edit in the oP), but the binding.Source must be left undefined. Therefore, the functional code-behind is
void ControlBindings()
{
if (EqualToResults.IsChecked == true)
{
var cable = DataContext as NCable;
Coordinates.ItemsSource = cable;
var binding = new Binding("X");
//REMOVE binding.Source = cable.Points;
XColumn.Binding = binding;
}
}

WPF ListView: Changing ItemsSource does not change ListView

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.
After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();
This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.
You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )
An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.
Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />

set not being called when editing a collection

I have a class containing a collection property which I want to display and edit in a property grid:
[EditorAttribute(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
public List<SomeType> Textures
{
get
{
return m_collection;
}
set
{
m_collection = value;
}
}
However, when I try to edit this collection with the CollectionEditor, set is never called; why is this and how can I fix it?
I also tried to wrap my List<SomeType> in my own collection as described here:
http://www.codeproject.com/KB/tabs/propertygridcollection.aspx
But neither Add, nor Remove is being called when I add and remove items in the CollectionEditor.
Your setter isn't being called because when you're editting a collection, you're really getting a reference to the original collection and then editting it.
Using your example code, this would only call the getter and then modify the existing collection (never resetting it):
var yourClass = new YourClass();
var textures = yourClass.Textures
var textures.Add(new SomeType());
To call the setter, you would actually have to assign a new collection to the Property:
var yourClass = new YourClass();
var newTextures = new List<SomeType>();
var newTextures.Add(new SomeType());
yourClass.Textures = newTextures;

Categories

Resources