I have a ListBox, I populate it with ItemsSource with List<Control>.
But when I delete or add new control for this List, I need every time reset my ListBox ItemsSource
Have any method for ListBox sync List content?
Instead of using a List<T>, use an ObservableCollection<T>. It is a list that supports change notifications for WPF:
// if this isn't readonly, you need to implement INotifyPropertyChanged, and raise
// PropertyChanged when you set the property to a new instance
private readonly ObservableCollection<Control> items =
new ObservableCollection<Control>();
public IList<Control> Items { get { return items; } }
In your Xaml, use something like this...
<ListBox ItemsSource="{Binding MyItemsSource}"/>
And wire it up like this...
public class ViewModel:INotifyPropertyChanged
{
public ObservableCollection<Control> MyItemsSource { get; set; }
public ViewModel()
{
MyItemsSource = new ObservableCollection<Control> {new ListBox(), new TextBox()};
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
This will present the items to the ListBox. In the example here, the collection contains a ListBox and a TextBox. You can add/delete from the collection and get the behaviour you are after. Controls themselves are not all that great as ListBox items because they do not have a meaningful way of populating a visual. So you will probably need to run them through an IValueConverter.
Implement INotifyPropertyChanged interface in your viewmodel.
Post that in the setter of this List, call the NotifyPropertyChanged event. This will
result in updating your changes on UI
Related
I have a DataGrid that is bound to an ObservableCollection in the ViewModel. This is a search results DataGrid. The problem is that after I update the search results ObservableCollection the actual DataGrid is not updated.
Before I get down voted to nothing, please note this is NOT about data in the columns (that bind works perfectly) it is about clearing and then placing completely new data into ObservableCollection that is not updating the DataGrid. So linking to something like this will not help as my properties are working correctly
Background:
The ObservableCollection is declared in the ViewModel like this;
public ObservableCollection<MyData> SearchCollection { get; set; }
The search DataGrid that is bound to my search ObservableCollection like this;
<DataGrid ItemsSource="{Binding SearchCollection}" />
In the ViewModel I have a search method like this;
var results =
from x in MainCollection
where x.ID.Contains(SearchValue)
select x;
SearchCollection = new ObservableCollection<MyData>(results);
The method fires correctly and produces the desired results. Yet the DataGrid is not updated with the new results. I know the ViewModel has the correct data because if I put a button on the Page and in the click event put this code;
private void selectPromoButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
var vm = (MyViewModel)DataContext;
MyDataGrid.ItemsSource = vm.SearchCollection;
}
The DataGrid now correctly shows the results.
I know I could put some event in the code behind of the Page but wouldn't that defeat MVVM? What is the correct MVVM way to handle this?
Try to implement INotifyPropertyChanged in your modelview
example:
public abstract class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
handler?.Invoke(this, args);
}
}
public class YourViewModel : ViewModelBase {
private ObservableCollection<MyData> _searchCollection ;
public ObservableCollection<MyData> SearchCollection
{
get { return _searchCollection; }
set { _searchCollection = value; OnPropertyChanged("SearchCollection"); }
}
}
The problem is that your are resetting your SearchCollection property rather than updating the collection. Observable collection raises the correct change events when items in the list are added, deleted, or updated. But not when the collection property itself is changed.
In your SearchCollection setter, you can fire a PropertyChanged event. Just like any other property when it changes. Also make sure your DataGrid ItemsSource binding is one-way not one-time.
<DataGrid ItemsSource="{Binding SearchCollection, Mode=OneWay}" />
Or you can change the members of the collection (clearing the old results and adding new ones). That should also update the DataGrid as you expect.
From your code sample, I would go with the first option.
I have a class called DataModel where I am storing an ObservableCollection of projects. I was using a static ObservableCollection, but since I want to bind to it, and OnPropertyChanged doesn't seem to work correctly for static properties, I created it as a singleton:
public sealed class DataModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static readonly DataModel instance = new DataModel();
private DataModel() { }
public static DataModel Instance
{
get
{
return instance;
}
}
#region Projects
private ObservableCollection<Project> projects = new ObservableCollection<Project>();
public ObservableCollection<Project> Projects
{
get
{
return projects;
}
set
{
if (projects == value)
{
return;
}
projects = value;
OnPropertyChanged("Projects");
}
}
#endregion Projects
public void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Then when I click a button,
Project newProject = new Project() { Title = "Test" };
DataModel.Instance.Projects.Add(newProject);
From what I could come up with from various sources, this ought to work correctly. However, the OnPropertyChanged event is never called. If I do
DataModel.Instance.Projects = new ObservableCollection<Project>();
it is called. But adding a Project to the collection won't call it.
OnPropertyChanged is only automatically fired when that property is reassigned. That is why reassigning your entire collection causes it to be fired. Modifying the collection fires the collection's own CollectionChanged event instead, since you're not actually changing the Projects reference, just mutating the same collection it's referring to.
If your collection is bound to a control's ItemsSource property correctly, e.g.
<ListView ItemsSource="{Binding Projects}"/>
where the data context is your DataModel instance, you should not need to do anything beyond adding the new item.
If you need to do something when the collection is changed, subscribe to its CollectionChanged event instead:
private DataModel()
{
Projects.CollectionChanged += Projects_CollectionChanged;
}
private void Projects_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// An item was added...
}
}
I expect it NOT to work. Because you are adding item to Observation collection in which case there is no reason setter would be called and thus OnPropertyChanged. That's why it works when you initialize DataModel.Instance.Projects from calling code. You could call property changed explicitly as below:
Project newProject = new Project() { Title = "Test" };
DataModel.Instance.Projects.Add(newProject);
DataModel.Instance.OnPropertyChanged("Projects");
Disclaimer: Although this is possible, you really need not do this. That is why they have provided us with ObservableCollection. Any item/s added or removed to/from such collection are automatically notified to the View. If this was not true, why would anybody use ObservableCollection instead of simple List for data bindings.
In my windows store app I have a gridview with data source set to Observable collection. When the item is added or removed to the collection everything works fine and view is updated. However when property of item of the collection is changed, the collectionChanged event is not fired and the views is not updated. I found a solution how to use INotifyChanged and propertyChanged event, but I want to fluidly update the view without doing something as reassigning the data source of gridview in propertyChanged Handler.
So I want to ask, if there is any solution to this problem.
Thank You in advance.
Refer the below code snippet, to notify while collection changed.
public class MyClass : INotifyPropertyChanged
{
private ObservableCollection<double> _myCollection;
public ObservableCollection<double> MyCollection
{
get { return _myCollection; }
set
{
_myCollection = value;
RaisedOnPropertyChanged("MyCollection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisedOnPropertyChanged(string _PropertyName)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName));
}
}
}
Hope it will help you..!
Regards,
Joy Rex
I have a simple class with a string property and a List property and I have the INofityPropertyChanged event implemented, but when I do an .Add to the string List this event is not hit so my Converter to display in the ListView is not hit. I am guessing the property changed is not hit for an Add to the List....how can I implement this in a way to get that property changed event hit???
Do I need to use some other type of collection?!
Thanks for any help!
namespace SVNQuickOpen.Configuration
{
public class DatabaseRecord : INotifyPropertyChanged
{
public DatabaseRecord()
{
IncludeFolders = new List<string>();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
private string _name;
public string Name
{
get { return _name; }
set
{
this._name = value;
Notify("Name");
}
}
private List<string> _includeFolders;
public List<string> IncludeFolders
{
get { return _includeFolders; }
set
{
this._includeFolders = value;
Notify("IncludeFolders");
}
}
}
}
You should use ObservableCollection<string> instead of List<string>, because unlike List, ObservableCollection will notify dependents when its contents are changed.
And in your case I'd make _includeFolders readonly - you can always work with one instance of the collection.
public class DatabaseRecord : INotifyPropertyChanged
{
private readonly ObservableCollection<string> _includeFolders;
public ObservableCollection<string> IncludeFolders
{
get { return _includeFolders; }
}
public DatabaseRecord()
{
_includeFolders = new ObservableCollection<string>();
_includeFolders.CollectionChanged += IncludeFolders_CollectionChanged;
}
private void IncludeFolders_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Notify("IncludeFolders");
}
...
}
The easiest way to make WPF's list binding work is to use a collection that implements INotifyCollectionChanged. A simple thing to do here is to replace or adapt your list with an ObservableCollection.
If you use ObservableCollection, then whenever you modify the list, it will raise the CollectionChanged event - an event that will tell the WPF binding to update. Note that if you swap out the actual collection object, you will want to raise the propertychanged event for the actual collection property.
Your List is not going to fire the NotifyPropertyChanged event automatically for you.
WPF controls that expose an ItemsSource property are designed to be bound to an ObservableCollection<T>, which will update automatically when items are added or removed.
You should have a look at ObservableCollection
I have a class:
public class A : INotifyPropertyChanged
{
public List<B> bList { get; set; }
public void AddB(B b)
{
bList.Add(b);
NotifyPropertyChanged("bList");
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
And a binding (DataContext of UserControl is an instance of A):
<ListBox ItemsSource="{Binding Path=bList}" />
Elements are shown, the ListBox is not updated after new object is added to List
After changing list to ObservableCollection and removing the NotifyPropertyChanged handler everything works.
Why the list is not working?
Your property has to be public, or the binding engine won't be able to access it.
EDIT:
After changing list to ObservableCollection and removing the NotifyPropertyChanged handler everything works.
That's precisely why the ObservableCollection<T> class was introduced... ObservableCollection<T> implements INotifyCollectionChanged, which allows it to notify the UI when an item is added/removed/replaced. List<T> doesn't trigger any notification, so the UI can't detect when the content of the list has changed.
The fact that you raise the PropertyChanged event does refresh the binding, but then it realizes that it's the same instance of List<T> as before, so it reuses the same ICollectionView as the ItemsSource, and the content of the ListBox isn't refreshed.
First thought is that you're trying to bind to a private member. That certainly doesn't seem right.
I think that the problem is that, although you are notifying the binding framework that the property has changed, the actual value of the property remains the same. That is to say that although the listbox may reevaluate the value of its ItemsSource binding, it will find that it is still the same object instance as previously. For example, imagine that the listbox reacts by to the property changed event somehow similar to the below.
private void OnItemsSourceBindingChanged()
{
var newValue = this.EvaluateItemsSourceBinding();
if (newValue != this.ItemsSource) //it will be equal, as its still the same list
{
this.AddNewItems();
}
}
In your example, this would mean that it would not reevaluate the items.
Note: I do not know how the listbox works with the ItemsSource property - I'm just speculating!
ObservableCollections send CollectionChanged events not PropertyChanged events
By signaling
NotifyPropertyChanged("bList");
you are basically saying you have a new list object, not that the content of the List has changed.
If you Change the type to ObservableCollection, the collections automatically sends
CollectionChanged
notifications that the collection items have changed, which is what you want.