WPF ListBox not binding to INotifyCollectionChanged or INotifyPropertyChanged Events - wpf

I have the following test code:
private class SomeItem
{
public string Title{ get{ return "something"; } }
public bool Completed { get { return false; } set { } }
}
private class SomeCollection : IEnumerable<SomeItem>, INotifyCollectionChanged
{
private IList<SomeItem> _items = new List<SomeItem>();
public void Add(SomeItem item)
{
_items.Add(item);
CollectionChanged(this, new
NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#region IEnumerable<SomeItem> Members
public IEnumerator<SomeItem> GetEnumerator()
{
return _items.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
}
private SomeCollection collection = new SomeCollection();
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
var expander = (Expander) sender;
var list = expander.DataContext as ITaskList;
var listBox = (ListBox)expander.Content;
//list.Tasks.CollectionChanged += CollectionChanged;
collection.Add(new SomeItem());
collection.Add(new SomeItem());
listBox.ItemsSource = collection;
}
and the XAML
<ListBox Name="taskListList" ItemsSource="{Binding}" BorderThickness="0" ItemContainerStyle="{StaticResource noSelectedStyle}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Expanded="Expander_Expanded">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBox KeyUp="TextBox_KeyUp" Width="200"/>
<Button Name="hide" Click="hide_Click">
<TextBlock Text="hide" />
</Button>
</StackPanel>
</Expander.Header>
<ListBox Name="taskList" ItemsSource="{Binding}" ItemTemplate="
{StaticResource taskItem}" />
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
the outer listbox gets populated on load. when the expander gets expanded I then set the ItemsSource property of the inner listbox (the reason i do this hear instead of using binding is this operation is quite slow and i only want it to take place if the use chooses to view the items). The inner listbox renders fine, but it doesn't actually subscribe to the CollectionChanged event on the collection. I have tried this with ICollection instead of IEnumerable and adding INotifyPropertyChanged as well as replacing INotifyCollectionChanged with INotifyPropertyChanged. The only way I can actually get this to work is to gut my SomeCollection class and inherit from ObservableCollection<SomeItem>. My reasoning for trying to role my own INotifyCollectionChanged instead of using ObservableCollection is because I am wrapping a COM collection in the real code. That collection will notify on add/change/remove and I am trying to convert these to INotify events for WPF.
Hope this is clear enough (its late).

ObservableCollection<T> also implements INotifyPropertyChanged. As you collection is simply an IEnumerable<T> you don't have any properties to create events for, but ObservableCollection<T> create PropertyChanged events for the Count and Item[] properties. You could try to make your collection more like ObservableCollection<T> by deriving from IList<T> and implementing INotifyPropertyChanged. I don't know if that will fix your problem, though.

I don't understand why I keep seeing people trying to implement their own collections in WPF. Just use an ObservableCollection<SomeItem> and all your CollectionChanged notifications will be taken care of.
private ObservableCollection<SomeItem> collection =
new ObservableCollection<SomeItem>();
If you want something to happen on SomeItem.PropertyChanged, make SomeItem implement INotifyPropertyChanged
As for why your CollectionChanged isn't being raised, you are setting the ItemsSource property, not binding it. Setting it means you are making a copy of collection and storing it in ListBox.ItemsSource. Binding it means you would be telling ListBox.ItemsSource to refer to collection for it's data.

Related

update GUI for SelectedeRow values of wpf datagrid

I have a wpf (.Net 4.5) datagrid. I am using the MVVM pattern for my application with the MVVM-Light framework.
I have a datagrid that is bound to an observable collection of "Tracking" objects called TrackingCollection. The datagrid selectedItem is bound to a "SelectedTracking" property in the viewModel.
<DataGrid Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="3" MinHeight="300"
ItemsSource="{Binding TrackingCollection}"
CanUserAddRows="False" CanUserDeleteRows="False"
SelectionMode="Single" SelectedItem="{Binding SelectedTracking, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RowDetailsTemplate="{StaticResource FTC_TrackingFullDetailTemplate}">
</DataGrid>
I have a comboBox in one column that is bound to an "idAction" property of the SelectedTracking object. When the user changes the selection of this comboBox, I want to assign the values of two other combo boxes in two other columns of the datagrid. These other columns are not bound to properties of the view model, rather they are bound directly to the properties of the SelectedTracking object. These properties of the SelectedTracking object are iSource_Type and iDestination_Type.
Here is the column definition for iSourceType:
<DataGridTemplateColumn Header="SOURCE" SortMemberPath="tracking_source.chrSource" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Style="{StaticResource FTC_DetailComboBox}" Margin="0" Padding="3"
ItemsSource="{Binding DataContext.TrackingSources, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
SelectedValuePath="idSource"
DisplayMemberPath="chrSource"
SelectedValue="{Binding iSource_Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
So when I assign these (iSource_Type, iDestination_Type) values in the ViewModel code (in a selectionChanged function of the first "Action" comboBox) the values are updated on the object itself. But the change is not reflected back to the UI's comboboxes bound to these properties.
What I tried:
First:
I have an implementation of INotifyPropertyCHanged with a function called RaisePropertyChanged. THis is provided through the MVVM_Light framework. SO i tried to use the following:
RaisePropertyChanged("iDestination_Type")
RaisePropertyChanged("iSource_Type")
RaisePropertyChanged("SelectedTracking")
RaisePropertyChanged("SelectedTracking.iDestination_Type")
RaisePropertyChanged("SelectedTracking.iSource_Type")
But these do not work.
Second:
I also tried to create properties in the viewmodel that bound to the SelectedTracking object. But this just caused all the tracking objects to get the same values.
Question:
Can INotifyPropertyChanged work on properties that are not a part of the viewmodel, but are properties of objects found in the view model. If so, what syntax do I need in the INotifyPropertyChanged event?
Additional INformation:
The MVVM-Light implementation of INotifyPropertyChanged (RaisePropertyChanged()) does not accept an empty string that would normaly update all UI elements. So is there a way I can override the Implementation of INotifyPropertyCHanged in just one CLass?
If I understand your problem correctly you would like a way to notify your ViewModel of changes to your Model.
If so you can implement INotifyPropertyChanged in your model and subscribe to the model objects PropertyChanged event in your ViewModel. Here you can raise the property changed notification on your ViewModel properties.
A simple example to demonstrate the concept:
Model:
public class Tracking : INotifyPropertyChanged
{
private string _isourcetype;
private string _idestinationtype;
public string SourceType
{
get { return _isourcetype; }
set
{
_isourcetype = value;
OnPropertyChanged("SourceType");
}
}
public string DestinationType
{
get { return _idestinationtype; }
set
{
_idestinationtype = value;
OnPropertyChanged("DestinationType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel:
public class TrackingViewModel : ViewModelBase
{
private Tracking _selectedTracking;
public string DestinationType
{
get { return _selectedTracking.DestinationType; }
}
public string SourceType
{
get { return _selectedTracking.SourceType; }
}
public Tracking SelectedTracking
{
get { return _selectedTracking; }
set
{
_selectedTracking = value;
RaisePropertyChanged("SelectedTracking");
}
}
public TrackingViewModel()
{
_selectedTracking = new Tracking();
_selectedTracking.PropertyChanged += SelectedTracking_PropertyChanged;
}
void SelectedTracking_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SourceType":
RaisePropertyChanged("SourceType");
break;
case "DestinationType":
RaisePropertyChanged("DestinationType");
break;
}
}
}

ObservableCollection<T> WPF Binding Display Not Updating

In setting up data binding for Observable Collection , under the following context: Implementing CollectionChanged Handler in XAML with WPF all bindings are working correctly, but I'm finding that in addition to changing the Property defined by ItemsSource within the ListBox, I am having to manually update the UI's visual container with code similar to:
XAML:
<Grid DataContext="{Binding ElementName=PollPublicStockMainWindow}">
<ListBox Height="132" HorizontalAlignment="Left" Name="lbFiles"
VerticalAlignment="Top" Width="167"
Margin="{StaticResource ConsistemtMargins}"
ItemsSource="{Binding LbItems}">
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="local:MainWindow.DeleteEntry"/>
</ListBox.InputBindings>
</ListBox>
</Grid>
CodeBehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LbItems = new ObservableCollection<string>();
LbItems.CollectionChanged += lbFiles_CollectionChanged;
}
private void lbFiles_CollectionChanged(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
MemoryPersistentStorageBridge memBridge = GetPersistentStorageBridge;
List<string> newFileList = new List<string>();
foreach (string str in LbItems) {
DoSomethingWithNewString(str); //these 2 lines are always paired?
lbFiles.Items.Add(str); // this should NOT be needed
}
}
}
Am I missing a binding?
Do you fire PropertyChanged when LbItems is set? It does not look that way. In the constructor, you call InitializeComponent first and then initialize the collection in LbItems = new ObservableCollection<string>();. I think that your collection is initialized "too late", because the binding will already have been processed. If you do not fire a property changed when LbItems is set then the binding will not be updated to actually bind to the collection.

Trigger Filter on CollectionViewSource

I am working on a WPF desktop application using the MVVM pattern.
I am trying to filter some items out of a ListView based on the text typed in a TextBox. I want the ListView items to be filtered as I change the text.
I want to know how to trigger the filter when the filter text changes.
The ListView binds to a CollectionViewSource, which binds to the ObservableCollection on my ViewModel. The TextBox for the filter text binds to a string on the ViewModel, with UpdateSourceTrigger=PropertyChanged, as it should be.
<CollectionViewSource x:Key="ProjectsCollection"
Source="{Binding Path=AllProjects}"
Filter="CollectionViewSource_Filter" />
<TextBox Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
<ListView DataContext="{StaticResource ProjectsCollection}"
ItemsSource="{Binding}" />
The Filter="CollectionViewSource_Filter" links to an event handler in the code behind, which simply calls a filter method on the ViewModel.
Filtering is done when the value of FilterText changes - the setter for the FilterText property calls a FilterList method that iterates over the ObservableCollection in my ViewModel and sets a boolean FilteredOut property on each item ViewModel.
I know the FilteredOut property is updated when the filter text changes, but the List does not refresh. The CollectionViewSource filter event is only fired when I reload the UserControl by switching away from it and back again.
I've tried calling OnPropertyChanged("AllProjects") after updating the filter info, but it did not solve my problem.
("AllProjects" is the ObservableCollection property on my ViewModel to which the CollectionViewSource binds.)
How can I get the CollectionViewSource to refilter itself when the value of the FilterText TextBox changes?
Many thanks
Don't create a CollectionViewSource in your view. Instead, create a property of type ICollectionView in your view model and bind ListView.ItemsSource to it.
Once you've done this, you can put logic in the FilterText property's setter that calls Refresh() on the ICollectionView whenever the user changes it.
You'll find that this also simplifies the problem of sorting: you can build the sorting logic into the view model and then expose commands that the view can use.
EDIT
Here's a pretty straightforward demo of dynamic sorting and filtering of a collection view using MVVM. This demo doesn't implement FilterText, but once you understand how it all works, you shouldn't have any difficulty implementing a FilterText property and a predicate that uses that property instead of the hard-coded filter that it's using now.
(Note also that the view model classes here don't implement property-change notification. That's just to keep the code simple: as nothing in this demo actually changes property values, it doesn't need property-change notification.)
First a class for your items:
public class ItemViewModel
{
public string Name { get; set; }
public int Age { get; set; }
}
Now, a view model for the application. There are three things going on here: first, it creates and populates its own ICollectionView; second, it exposes an ApplicationCommand (see below) that the view will use to execute sorting and filtering commands, and finally, it implements an Execute method that sorts or filters the view:
public class ApplicationViewModel
{
public ApplicationViewModel()
{
Items.Add(new ItemViewModel { Name = "John", Age = 18} );
Items.Add(new ItemViewModel { Name = "Mary", Age = 30} );
Items.Add(new ItemViewModel { Name = "Richard", Age = 28 } );
Items.Add(new ItemViewModel { Name = "Elizabeth", Age = 45 });
Items.Add(new ItemViewModel { Name = "Patrick", Age = 6 });
Items.Add(new ItemViewModel { Name = "Philip", Age = 11 });
ItemsView = CollectionViewSource.GetDefaultView(Items);
}
public ApplicationCommand ApplicationCommand
{
get { return new ApplicationCommand(this); }
}
private ObservableCollection<ItemViewModel> Items =
new ObservableCollection<ItemViewModel>();
public ICollectionView ItemsView { get; set; }
public void ExecuteCommand(string command)
{
ListCollectionView list = (ListCollectionView) ItemsView;
switch (command)
{
case "SortByName":
list.CustomSort = new ItemSorter("Name") ;
return;
case "SortByAge":
list.CustomSort = new ItemSorter("Age");
return;
case "ApplyFilter":
list.Filter = new Predicate<object>(x =>
((ItemViewModel)x).Age > 21);
return;
case "RemoveFilter":
list.Filter = null;
return;
default:
return;
}
}
}
Sorting kind of sucks; you need to implement an IComparer:
public class ItemSorter : IComparer
{
private string PropertyName { get; set; }
public ItemSorter(string propertyName)
{
PropertyName = propertyName;
}
public int Compare(object x, object y)
{
ItemViewModel ix = (ItemViewModel) x;
ItemViewModel iy = (ItemViewModel) y;
switch(PropertyName)
{
case "Name":
return string.Compare(ix.Name, iy.Name);
case "Age":
if (ix.Age > iy.Age) return 1;
if (iy.Age > ix.Age) return -1;
return 0;
default:
throw new InvalidOperationException("Cannot sort by " +
PropertyName);
}
}
}
To trigger the Execute method in the view model, this uses an ApplicationCommand class, which is a simple implementation of ICommand that routes the CommandParameter on buttons in the view to the view model's Execute method. I implemented it this way because I didn't want to create a bunch of RelayCommand properties in the application view model, and I wanted to keep all the sorting/filtering in one method so that it was easy to see how it's done.
public class ApplicationCommand : ICommand
{
private ApplicationViewModel _ApplicationViewModel;
public ApplicationCommand(ApplicationViewModel avm)
{
_ApplicationViewModel = avm;
}
public void Execute(object parameter)
{
_ApplicationViewModel.ExecuteCommand(parameter.ToString());
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
Finally, here's the MainWindow for the application:
<Window x:Class="CollectionViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CollectionViewDemo="clr-namespace:CollectionViewDemo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<CollectionViewDemo:ApplicationViewModel />
</Window.DataContext>
<DockPanel>
<ListView ItemsSource="{Binding ItemsView}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}"
Header="Name" />
<GridViewColumn DisplayMemberBinding="{Binding Age}"
Header="Age"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel DockPanel.Dock="Right">
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByName">Sort by name</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByAge">Sort by age</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="ApplyFilter">Apply filter</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="RemoveFilter">Remove filter</Button>
</StackPanel>
</DockPanel>
</Window>
Nowadays, you often don't need to explicitly trigger refreshes. CollectionViewSource implements ICollectionViewLiveShaping which updates automatically if IsLiveFilteringRequested is true, based upon the fields in its LiveFilteringProperties collection.
An example in XAML:
<CollectionViewSource
Source="{Binding Items}"
Filter="FilterPredicateFunction"
IsLiveFilteringRequested="True">
<CollectionViewSource.LiveFilteringProperties>
<system:String>FilteredProperty1</system:String>
<system:String>FilteredProperty2</system:String>
</CollectionViewSource.LiveFilteringProperties>
</CollectionViewSource>
CollectionViewSource.View.Refresh();
CollectionViewSource.Filter is reevaluated in this way!
Perhaps you've simplified your View in your question, but as written, you don't really need a CollectionViewSource - you can bind to a filtered list directly in your ViewModel (mItemsToFilter is the collection that is being filtered, probably "AllProjects" in your example):
public ReadOnlyObservableCollection<ItemsToFilter> AllFilteredItems
{
get
{
if (String.IsNullOrEmpty(mFilterText))
return new ReadOnlyObservableCollection<ItemsToFilter>(mItemsToFilter);
var filtered = mItemsToFilter.Where(item => item.Text.Contains(mFilterText));
return new ReadOnlyObservableCollection<ItemsToFilter>(
new ObservableCollection<ItemsToFilter>(filtered));
}
}
public string FilterText
{
get { return mFilterText; }
set
{
mFilterText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FilterText"));
PropertyChanged(this, new PropertyChangedEventArgs("AllFilteredItems"));
}
}
}
Your View would then simply be:
<TextBox Text="{Binding Path=FilterText,UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding AllFilteredItems}" />
Some quick notes:
This eliminates the event in the code behind
It also eliminates the "FilterOut" property, which is an artificial, GUI-only property and thus really breaks MVVM. Unless you plan to serialize this, I wouldn't want it in my ViewModel, and certainly not in my Model.
In my example, I use a "Filter In" rather than a "Filter Out". It seems more logical to me (in most cases) that the filter I am applying are things I do want to see. If you really want to filter things out, just negate the Contains clause (i.e. item => ! Item.Text.Contains(...)).
You may have a more centralized way of doing your Sets in your ViewModel. The important thing to remember is that when you change the FilterText, you also need to notify your AllFilteredItems collection. I did it inline here, but you could also handle the PropertyChanged event and call PropertyChanged when the e.PropertyName is FilterText.
Please let me know if you need any clarifications.
If I understood well what you are asking:
In the set part of your FilterText property just call Refresh() to your CollectionView.
I just discovered a much more elegant solution to this issue. Instead of creating a ICollectionView in your ViewModel (as the accepted answer suggests) and setting your binding to
ItemsSource={Binding Path=YourCollectionViewSourceProperty}
The better way is to create a CollectionViewSource property in your ViewModel. Then bind your ItemsSource as follows
ItemsSource={Binding Path=YourCollectionViewSourceProperty.View}
Notice the addition of .View This way the ItemsSource binding is still notified whenever there is a change to the CollectionViewSource and you never have to manually call Refresh() on the ICollectionView
Note: I can't determine why this is the case. If you bind directly to a CollectionViewSource property the binding fails. However, if you define a CollectionViewSource in your Resources element of a XAML file and you bind directly to the resource key, the binding works fine. The only thing I can guess is that when you do it completely in XAML it knows you really want to bind to the CollectionViewSource.View value and binds it for you acourdingly behind the scenes (how helpful! :/) .

WPF- Is there a way to bind to the SelectedValues of both a TreeView and a ListBox?

I need to bind so that the Content of a content control is set to the SelectedValue of either the TreeView or the ListBox. The SelectedValue that was most recently changed should provide the content for the ContentControl.
I was able to get this working using the following concept.
Bind the content control to a read only property "SelectedItem" (with private property _selectedItem).
Bind the ListBox.SelectedItem to a read/write property "SelectedItemLB".
In the SelectedItemLB setter, set the value of _selectedItem, and raise the PropertyChanged event for SelectedItem.
Create a handler for VreeView.SelectedItemChanged, which sets the value of _selectedItem and raises the PropertyChanged event for SelectedItem.
Here is my full code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.items = new List<object>();
this.items.Add(new Car("Green"));
this.items.Add(new Car("Blue"));
this.items.Add(new Car("Red"));
this._selectedItem = this.items[0];
this.treeView1.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(treeView1_SelectedItemChanged);
this.DataContext = this;
}
void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this._selectedItem = treeView1.SelectedItem;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
private List<object> items;
public List<object> Items
{
get { return items; }
set { items = value; }
}
public object SelectedItemLB
{
get { return _selectedItem; }
set
{
_selectedItem = value;
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
The XAML is pretty simple:
<StackPanel>
<ListBox Name="listBox1" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItemLB, Mode=TwoWay}" ></ListBox>
<TreeView Name="treeView1" ItemsSource="{Binding Path=Items}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}"></Setter>
</Style>
</TreeView.Resources>
</TreeView>
<ContentControl Content="{Binding Path=SelectedItem.Color}"></ContentControl>
</StackPanel>
I can't think of a way to do that directly. However there are several straightforward solutions.
A. Use events to set the Content
Simply attach a common handler to the SelectedValueChanged events of your ItemsControls. Whenever one of them changes its selection, the handler will set the Content to whatever was selected. I think this is most simple.
B. Use intermediary properties
Bind the SelectedValue of each ItemsControl to a property. In the property's setter, also set the Content equal to value. This allows you to use data binding instead of event handlers, but it still requires you to write code-behind and it doesn't buy you much. Of course, if you are already binding to properties for other purposes, there is almost no extra cost (only an assignment in each setter) so this method might be preferable.

Update a binding that doesn't implement IObservable collection

I am trying to bind a list box to a collection. The problem is that the collection can change, but the collection doesn't implement IObservableCollection. What is the best way to force the binding to refresh?
As Tormod suggested, the preferable methods would be changing the collection to an ObservableCollection, or implementing INotifyCollectionChanged in the collection would take care of refreshing the UI.
However, if those options aren't available, then you can 'force' a refresh by using INotifyPropertyChanged in whatever class contains the collection. We then will be treating the list just like a regular property, and using the setter to notify on changes. To do this it requires re-assigning the reference, which is why using something like an ObservableCollection is preferred, as well as raising the PropertyChanged event.
Here is a quick sample showing how this can be done with just a standard generic List:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
this.Names = new List<string>() { "Mike", "Robert" };
this.DataContext = this;
}
private IList<string> myNames;
public IList<string> Names
{
get
{
return this.myNames;
}
set
{
this.myNames = value;
this.NotifyPropertyChanged("Names");
}
}
private void OnAddName(object sender, RoutedEventArgs e)
{
Names.Add("Kevin");
Names = Names.ToList();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Xaml:
<Window x:Class="Sample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300">
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding Names}" />
<Button Content="Add Name"
Click="OnAddName" />
</StackPanel>
</Grid>
Without more information on how and where this collection is used, here are some pointers which may help you.
If the collection is not sealed, you could inherit it.
If the collection is sealed, you could create an adapter class which contains an instance of your collection and wraps all relevant methods.
In any case, your new class could implement IObservableCollection and be used for binding.
You can set a binding to update explicitly and then trigger an update through code by say having a refresh button for example.
As an example.
<StackPanel>
<ListBox
x:Name="lb"
ItemsSource="{Binding SomeList, UpdateSourceTrigger=Explicit}"
/>
<Button Content="Refresh" Click="Refresh_Click" />
</StackPanel>
private void Refresh_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = lb.GetBindingExpression(ListBox.ItemsSourceProperty);
be.UpdateSource();
}
You can also force a refresh in your ViewModel. This is sth I've seen Josh Smith do in his MVVM demo app:
ICollectionView coll = CollectionViewSource.GetDefaultView(myCollection);
if (coll!=null)
coll.Refresh();
myCollection can be any type of collection that you have bound to the View.
Bea Stollnitz has a bit more information about CollectionViewSource:
http://www.beacosta.com/blog/?m=200608

Resources