I am trying to bind a combobox in WPF like this,
<ComboBox Width="350" Margin="5" IsEditable="True" ItemsSource="{Binding ComboboxItems}" DisplayMemberPath="Name">
public List<ExpandoObject> ComboboxItems
{
get
{
return comboboxItems;
}
}
If I set the list like this in my view model,
comboboxItems.Clear();
foreach (ExpandoObject comboboxItem in repository.LoadComboboxItems())
{
comboboxItems.Add(comboboxItem);
}
NotifyPropertyChanged(this, x => x.ComboboxItems);
The NotifyPropertyChanged seems to work because a breakpoint on the ComboboxItems is hit, but then the combobox list does not update on the GUI. Snoop shows no binding errors or anything like that.
The first time the above list is updated it seems to work, so it can't be anything to do with using an ExpandoObject I don't think.
UPDATE:
Using an observable collection works, but I would like to know if I have a setter in a viewmodel like this which binds to a control on the GUI,
public string Database
{
get
{
return importData.Database;
}
set
{
importData.Database = value;
NotifyPropertyChanged(this, x => x.Database);
comboboxItems.Clear();
foreach (ExpandoObject comboboxItem in repository.LoadComboboxItems())
{
comboboxItems.Add(comboboxItem);
}
NotifyPropertyChanged(this, x => x.ComboboxItems);
}
}
Is that setter being run on a background thread? The reason I ask is will the setter block the GUI if it takes a while to load the items from the database?
This is where I went wrong the first time trying to use an ObservableCollection, by running the code in the setter on a background thread using BackgroundWorker. Updating the ObservableCollection caused an exception under those conditions.
I think it will work if you use an ObservableCollection<> instead of a List<>. Unless you use an ObservableCollection, xaml will not know that the contents of the list changed.
To answer the second part of your question, if you're trying to set the ObservableCollection using a BackgroundWorker directly, you will get an exception. One of the ways to get around the exception is to set the ObservableCollection using BeginInvoke
One thing to note: you don't need to call NotifyPropertyChanged(this, x => x.ComboboxItems); in your setter. This is because the property isn't changing; the property is a collection and the collection contents are changing. ObservableCollection will notify subscribers that the contents have changed.
Use ObservableCollection instead of the List.
Quoting MSDN:
You can enumerate over any collection that implements the IEnumerable
interface. However, to set up dynamic bindings so that insertions or
deletions in the collection update the UI automatically, the
collection must implement the INotifyCollectionChanged interface. This
interface exposes the CollectionChanged event, an event that should be
raised whenever the underlying collection changes.
WPF provides the ObservableCollection class, which is a built-in
implementation of a data collection that implements the
INotifyCollectionChanged interface.
Before implementing your own collection, consider using
ObservableCollection or one of the existing collection classes,
such as List, Collection, and BindingList, among many others.
If you have an advanced scenario and want to implement your own
collection, consider using IList, which provides a non-generic
collection of objects that can be individually accessed by index.
Implementing IList provides the best performance with the data binding
engine.
Related
I have a WPF application with a ListBox (called listMyItems) which is successfully bound to a class of MyItems that I created. I have a List of MyItems called currentMyItems which is then assigned as ItemSource to the ListBox. It all works fine, if I add an item to the currentMyItems it pops up on the list, etc.
The problem occurs when I try to remove the selected item in the ListBox. This is the code that I use:
currentMyItems.Remove((MyItem)listMyItems.SelectedItem);
The item disappears from the ListBox but the next time I update it, it pops back up as it was never deleted. Any tips?
I think you may be confused about how data binding works. When you bind a property, you are telling WPF to go look somewhere else for the value of that property.
When you bind the ListBox.ItemsSource property to currentMyItems, you are telling WPF to go look at the currentMyItems list to find its list of items. If currentMyItems is an ObservableCollection instead of a List<T>, then the UI will automatically receive a notification to update the bound value when you add or remove an item from the collection.
Based on what you say in the question, it sounds like you you have two collections, one of which is bound, and the other which is used to recreate the first collection anytime a change occurs. All that is not needed.
Just create one ObservableCollection<MyItem>, bind it to the ListBox.ItemsSource property, and then add or remove items from that single collection. It should work as you would expect.
<ListBox x:Name="listMyItems" ItemsSource="{Binding MyItems}" />
and
MyItems.Add((MyItem)listMyItems.SelectedItem)
MyItems.Remove((MyItem)listMyItems.SelectedItem)
If you're interested, I also have some beginner articles on my blog for WPF users who are struggling to understand the DataContext. You may want to check out Understanding the change in mindset when switching from WinForms to WPF and What is this “DataContext” you speak of?
If you bound it correctly to an ObservableCollection and currentMyItems is that collection. Than it means that you must have reloaded currentMyItems in meantime.
Also consider binding the SelectedItem property of your ListView - your view model doesn't have to know about the view at all.
Your source collection must be modufy (inherit from IList or ICollection). If your source collection does not support this method of your interface Remove, you can't remove item from source.
So, when you want to remove item you must cast ItemsSource to IList or ICollection:
var source = listbox.ItemsSource as IList ?? listbox.ItemsSource as ICollection;
and then check:
if (source == null) return;
then:
listbox.SelectedItems.ForEach(source.Remove);
listbox.Items.Refresh();
Make the currentMyItems<MyItem> an ObservableColection<MyItem>. This way it will raise a property change whenever modified and the UI gets updated accordingly.
By using ObservableCollection you will automatically get updates on the UI.
You should use an ObservableCollection instead of List.
A good thing is to always use ObservableCollection instead of List when something to do with UI
As I am quite new to WPF and MVVM, this might be something quite obvious and trivial, so bear with me here.
Anyway, I have a view model with these properties:
class ViewModel : INotifyPropertyChanged {
ICollectionView Items; // a list of Items object, wraps _items field, each Item has a Date property
string Filter; // a filter key, wraps _filter field and calls ApplyFilter() as it is changed
void ApplyFilter(); // based on the filter key, _items.Filter gets set to some predicate
}
The properties raise the PropertyChanged event when set and all that common MVVM stuff.
In the view i have a simple ItemsControl which binds to the Items property, and some fancy data template to display each Item.
A request has been made to show the items grouped by day so that you see a date header for each day and a list of Items whose Date property corresponds to the date in the header.
Since this is strictly a display issue, I decided to leave the view model as it is, but use a converter to convert the ICollectionView Items into a Dictionary where the key is the date, and the collection is a subset of Items with that date.
The ItemsControl now has a StackPanel with a TextBlock to show the date header (the dictionary key) and another ItemsControl which is basically a copy of the old one, which just listed the items (the dictionary value).
The view renders nicely, but the filter no longer works. As the control is bound to Items, and the ICollectionView implements INotifyCollectionChanged, I was expecting the filter to work as it is changing the Items list, and that the converter would be re-run to rebuild the dictionary. Well, it is not. Changing the filter does call the ApplyFilter(), and the _items.Filter gets set to the required predicate, but the view never changes. I have also tried calling the PropertyChanged for Items from the ApplyFilter, but that does not work either.
Obviously, my contrived scenario of how this should work is wrong, and to be honest I am out of ideas, apart from creating new objects which will hold the date and list of items as properties and then using a list of those in the VM. But, as I said, in my mind, this is strictly a view issue, so the model just needs to provide a list of items, and it is the view's responsibility to decide how to render them.
Any help is greatly appreciated and thanks in advance.
EDIT:
Now I'm thinking that if I'm changing the _filter.Filter, then the PropertyChanged event for the Items is actually never raised, as it is in fact not changed (the internals have changed, but Items themselves are still the same ICollectionView).
Hence, the converter is never again triggered.
If this is the case, how can I trigger the converter? Raising the PropertyChanged for Items after doing ApplyFilter() also did nothing.
Perhaps using ListView instead of simple ItemsControl+converter is better idea? ListView has many good functions. Such as virtualizing, grouping, etc.
All you have to do, is modify your ICollectionView grouping property and apply templates(GroupStyle)
As for your problem, your current behaviour makes sense to me. If you want to converter re-run, you're supposed to create new method RefreshBinding() and do something like this;
var referenceCopy = Items;
Items = null;//make sure INotifyPropertyCHanged is fired.
Items = referenceCopy; //converter should be called again.
you will call it after you need your converter to be re-run. But to be completely honest, just use ICollectionView+Grouping property and ListView. Or you can implement ListView functionality yourself. Using converter does not seem good solution.
I have a listbox with items bound to an ObservableCollection.
Now, from within the viewModel, I need to cause an update to the UI.
I dont have a refernce to the listbox from my view model.
If I remove or add an item from my ObservableCollection, the ui gets updated.
Based on some other logic I need to update the UI...but the ObservableCollection is the same.
How can I update the UI WITHOUT either adding or removing items from my ObservableCollection?
Thanks
If you need to change your UI because you've edited the items in your collection, then you should arrange for those items to implement the INotifyPropertyChanged interface. If the objects within your collection have a PropertyChanged event, the UI will be listening for that event from individual items. (If possible, you could also change the items in your collection to be DependencyObjects with DependencyProperties, which accomplishes the same goal.)
If you really need to trigger a UI update when nothing at all about your collection has changed, the way to do it is to manually raise the CollectionChanged event. This can't be done with the ObservableCollection<> as is, but you could derive a new collection from that class, and call the protected OnCollectionChanged method from within some new, public method.
I've had a similar issue where I wanted to change the background on an item, but obviously neither the item nor the collection changed.
It was achieved by calling:
CollectionViewSource.GetDefaultView(your_collection_name).Refresh();
This refreshed the view from the view model without altering the collections
This is a good case for an extension method. It hides away the implementation in case it changes in future versions, can be modified in one place, and the calling code looks simpler and less confusing.
public static void Refresh<T>(this ObservableCollection<T> value)
{
CollectionViewSource.GetDefaultView(value).Refresh();
}
Usage:
myCollection.Refresh();
I am using an ObservableCollection for databinding as ItemsSource for DataGrid. Collection contains complex type objects. One of this type properties is a List of strings.
Just for now I see that when I update this List property from code nothing changes in the UI (the primary binding works fine). So, my question is: is it an expected behaviour? Maybe I should not use List as part of the type, but also use an ObservableCollection?
Update
Mode is set to OneWay.
Use a collection, instead of List, that implementes the interface INotifyCollectionChanged (like ObservableCollection). Then changes to the collection get populated to the ui.
Yes it is expected behaviour. The observable collection only notifies of changes to its own contents - that is add, delete, reorder.
What you are looking at is a change to an element in the observablecollection - if you want to see your changes to the class you put in, your element has to implement INotifyPropertyChanged.
So currently: If your list property on you complex object changes you won't see it, however if you change that too to be an observablecollection you could see changes to that collection in a sub-itemscontrol like a combobox - but not if you change the collection object to another one - so if you do not implement INotifyPropertyChanged you should set the collectionproperty before the binding is applied.
When you are updateding your list u have to call INotifyPropertyChange other wise UI wont get update the list result..
INotifyPropertyChange is the indication that here some changes occurred in the items source so update it.
This might help as well:
ObservableCollection that also monitors changes on the elements in collection
I have an IEnumerable<> which lazy loads it's data. I want to just set a Combobox's ItemsSource to the IEnumerable, but when I do it goes and loads all the data anyway (which removes the point of lazy loading).
I've tried it with Linq-To-Sql as well since it seems to be a similar theory and it also loads all the data.
Is there an easy way to do this?
Try setting the IsAsync-Property in the ItemsSource-Binding of the ComboBox to True:
<ComboBox ItemsSource={Binding YourItemsSourceProperty, IsAsync=True}
SelectedItem={Binding YourSelectionProperty} />
If that does not change anything, have a look at this one:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/3d343489-90c4-4bdc-8bd9-1046ec9daf76
Maybe you will need to use IList instead.
Alternatively, you could use PriorityBinding, to fill the list with some temporary data until the final list is completely loaded.
Don't bind the control to the IEnumerable directly. Instead, bind it to a ObservableCollection (which is empty at the beginning.) Meanwhile, still do your lazy loading on the IEnumerable as usual (either triggered by drop down combobox or something else.) While the data is loaded or when you have enough data, add the items to that ObservableCollection to populate the comboBox.
I don't think the WPF ComboBox supports lazily loading the items from the ItemsSource. Why do you need to lazy load anyway, and when would you expect it to trigger the lazy load?
Bind your ComboBox's ItemsSource to an ObservableCollection.
Now whenever your IEnumerable lazy loads the data, add it to the ObservableCollection instantly
foreach(Item i in myIEnumerable)
{
myObsCol.Add(i);
}
This would update the UI once each item is added.
I am trying to do same thing. But as I investigated, if you want to use standard bindings on combobox (collection to ItemsSource and dataItem to SelectedValue/SelectedItem), it is necessary to write your own control.
Combobox is inherited from Selector and when you have bounded collection to ItemsSource property and you change your value of property that is bounded to SelectedValue/SelectedItem then the Selector call it's own private method FindItemWithValue(object value). This method walks through items in bounded collection from first until it finds equal value. That of course will make you collection to load all items before the selected one.
If you are willing to do your own custom class that will have a list, you can use INotifyPropertyChanged interface to tell that your collection has been modified. Or as use ObservableCollection as it has been already suggested