Update a binding that doesn't implement IObservable collection - wpf

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

Related

WPF: ×™how to populate my ViewModel in XAML instead of code behind

So i have this ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Porsons
{
get { return _persons; }
set
{
_persons = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And then create this ViewModel class and populate its Person list:
ViewModel viewModel;
ObservableCollection<Person> persons
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
viewModel.Porsons= persons;
}
And then my ListView:
<ListView ItemSource={Binding Persons}/>
So instead of binding this Persons list into my ViewModel class and then do this ItemSource can i do it in pure XAML or this is the right way ?
Instead of creating a ViewModel property on your view it is recommended to use it's DataContext (this link also shows how to set it using XAML). Also don't populate the view model in the view since most of the time the data resides in the model and the view should not know anything about any models (when following MVVM).
Please read the link above and visit the links you meet. Also read this article about MVVM. This gives you some basic knowledge to make it easier to understand how to use the WPF framework.
There are many variations of view model creation in XAML.
For example alternatively you can create it in the App.Xaml to make it globally accessible via the StaticResource markup extension and assign it to the individual controls's DataContext via a Style or use an ObjectDataProvider.
This example uses XAML Property Element declaration to create a ViewModel instance directly in the target view. This instance is locally accessible only.
ViewModel.cs:
namespace Example
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Persons = new ObservableCollection<Person>();
}
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons
{
get => _persons;
set
{
_persons = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View.xaml:
<Window x:Class="Example.MainWindow"
...
xmlns:local="clr-namespace:Example">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<ListView ItemSource={Binding Persons}/>
</Grid>
</Window>
Yes, you can. But no, you most certainly do not want to.
To answer your question, let's say your Person class looks like this:
public class Person
{
public string Name { get; set; }
}
You can easily declare a list in XAML and bind it to a ListView (say) like this:
<ListView DisplayMemberPath="Name">
<ListView.ItemsSource>
<x:Array Type="{x:Type vm:Person}">
<vm:Person Name="Tom" />
<vm:Person Name="Dick" />
<vm:Person Name="Harry" />
</x:Array>
</ListView.ItemsSource>
</ListView>
The result of which is this:
Just because you can do this, though, doesn't mean you should. The whole point of MVVM is to separate your view layer from your view model layer. You should be able to run your entire application from a test build without creating a single view object at all. In asking this question what you are apparently trying to do is declare a data structure in your view layer, which is totally the wrong place to put it. Your view layer should be as "dumb" as possible, with only the weakest possible bindings to your view model layer where the actual logic is going on.

observable collection and inotifypropertychanged

An ObservableCollection is self-contained when it comes to raising the CollectionChanged event because it implements INotifyPropertyChanged and INotifyCollectionChanged. So i think , we don't need to implement INotifyPropertyChanged again.
but i have seen some example where folks are defining ObservableCollection as property and raising property changed event in setter. i don't understand why this is done again or in better words why they are Raising property changed event in setter(see below code). As we already know that ObservableCollection automatically raises when add,update is done, then we need not to raise again.right?
please clarify my doubt.
public class TheViewModel()
{
private ObservableCollection<Camper> _campers;
public ObservableCollection<Camper> Campers
{
get { return _campers; }
set
{
if (Equals(_campers, value)) return;
_campers = value;
RaisePropertyChanged("Campers"); //Or however you implement it
}
}
If you set Campers to point to a new instance, that RaisePropertyChanged will do the job for you. Otherwise you will have a reference to the old instance and the View will remain out of sync. The other solution to this is, every time you set Campers to point to a new collection, set again the ItemsSource for your DataGrid or ListView or whatever control you use.
Indeed this works as long as you Add or Remove items from your collection. To conclude, that's the difference, when you set again
Campers = new ObservableCollection<Camper>();
your RaisePropertyChanged will be triggered.
Code update:
XAML:
<Window x:Class="ObservablePropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ListView ItemsSource="{Binding items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Change collection" Click="btnChangeCollection_Click"/>
</StackPanel>
</Grid>
Code behind:
public partial class MainWindow : Window
{
public ObservableCollection<string> items { get; set; }
public MainWindow()
{
InitializeComponent();
items = new ObservableCollection<string>();
items.Add("One");
items.Add("Two");
this.DataContext = this;
}
private void btnChangeCollection_Click(object sender, RoutedEventArgs e)
{
items = new ObservableCollection<string>();
items.Add("Three");
items.Add("Four");
}
}
As i don't have INPC interface implemented and no PropertyChanged added on the set of the items collection, after clicking the Button you will not get the View updated with items "Three" and "Four".
And here is another way to accomplish this behavior:
public ObservableCollection<string> items
{
get { return (ObservableCollection<string>)GetValue(itemsProperty); }
set { SetValue(itemsProperty, value); }
}
// Using a DependencyProperty as the backing store for items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty itemsProperty =
DependencyProperty.Register("items", typeof(ObservableCollection<string>), typeof(MainWindow), new UIPropertyMetadata(null));
Using this dependency property, the ListView will remain in sync.

Why ListBox does not display the bound ItemsSource

I am new to WPF. I have created a WPF project, and add the following class
public class MessageList:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private List<string> list = new List<string>();
public List<string> MsgList
{
get { return list; }
set
{
list = value;
OnPropertyChanged("MsgList");
}
}
public void AddItem(string item)
{
this.MsgList.Add(item);
OnPropertyChanged("MsgList");
}
}
Then in the main window I added a ListBox and below is the xaml content
<Window.DataContext>
<ObjectDataProvider x:Name="dataSource" ObjectType="{x:Type src:MessageList}"/>
</Window.DataContext>
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="52,44,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<ListBox Height="233" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" Margin="185,44,0,0" Name="listBox1" VerticalAlignment="Top" Width="260" ItemsSource="{Binding Path=MsgList}" />
</Grid>
Here is the source code of MainWindow.cs
public partial class MainWindow : Window
{
private MessageList mlist = null;
public MainWindow()
{
InitializeComponent();
object obj = this.DataContext;
if (obj is ObjectDataProvider)
{
this.mlist = ((ObjectDataProvider)obj).ObjectInstance as MessageList;
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.mlist.AddItem(DateTime.Now.ToString());
}
}
My question is after I clicked the button, there isn't any content displayed on the Listbox, what is the reason?
You should use an ObservableCollection instead of a List to notify the UI of collection changes.
You asked for a reason, while devdigital gave you the solution its worth mentioning why it is not working, and why his fix works:
Your mlist is bound to the ListBox and its all working well. Now you press the button and you add an entry to your list. The listbox just won't know about this change, because your list has no way of telling "Hey i just added a new item". To do that, you need to use a Collection implementing INotifyCollectionChanged, like the ObservableCollection. This is very similar to your OnPropertyChanged if you modify a property on your MessageList it also calls the OnPropertychanged method which fires the PropertyChanged event. The Databinding registers to the PropertyChanged event and now knows when you updated your property and automatically updates the UI. The same is necessary for Collections if you want this automatic updating of the UI on collections.
The culprit is the string items... string items being of primitive type, do not refresh bindings on the list box when you do the OnPropertyChanged
Either use observable collection or call this in your button1_Click() function...
listBox1.Items.Refresh();

WPF ListBox not binding to INotifyCollectionChanged or INotifyPropertyChanged Events

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.

wpf toolkit data grid

hello i'm building a wpf app with data grids,
the pattern is model view view model.
all og my screens contains a contentcontrol, and i just assign him the view model, that have a suitable data template,
anyway, my problem is with combo box column, the data context is the presented entity, and i need it to be the view model.
whats the best solution?
I'm using another datagrid, but it might be similar. The way i did it was like that:
in the XAML, i defined an ObjectDataProvider in the resources:
<ObjectDataProvider x:Key="VM" ObjectInstance="{x:Null}" x:Name="vm"/>
then after assigning the DataContext (either the constructor or the DataContextChanged event), i did this:
(this.Resources["VM"] as ObjectDataProvider).ObjectInstance = this.DataContext;
In the Combobox xaml, i used that as binding source:
ItemsSource="{Binding Source={StaticResource VM}, Path=SomeItems, Mode=OneWay}"
Not sure if it works for the microsoft datagrid, but i guess it's worth a try.
this is how I used ViewModel with ComboBoxes, the DataContext is the ViewModel, not the underlying entity (List<Person>).
ViewModel (Person is a Simple class with Name and Age):
public class PeopleViewModel : INotifyPropertyChanged
{
private List<Person> _peopleList;
private Person _selectedPerson;
public PeopleViewModel()
{
// initialize with sample data
_peopleList = getPeopleList();
}
// gets sample data
private List<Person> getPeopleList()
{
var result = new List<Person>();
for (int i = 0; i < 10; i++)
{
result.Add(new Person("person " + i, i));
}
return result;
}
public List<Person> PeopleList
{
get { return _peopleList; }
}
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
if (_selectedPerson == value) return;
_selectedPerson = value;
// required so that View know about changes
OnPropertyChanged("SelectedPerson");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// WPF will listen on this event for changes
public event PropertyChangedEventHandler PropertyChanged;
}
XAML for ComboBox:
<ComboBox Name="cmbEnum" Width="150" ItemsSource="{Binding Path=PeopleList}" SelectedValue="{Binding Path=SelectedPerson}" SelectedValuePath="" DisplayMemberPath="Name" ></ComboBox>
And in code behind I can do:
public Window2()
{
InitializeComponent();
vm = new PeopleViewModel();
// we are listening on changes of ViewModel, not ComboBox
vm.PropertyChanged += new PropertyChangedEventHandler(vm_PropertyChanged);
this.DataContext = vm;
}
void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedPerson")
{
MessageBox.Show(vm.SelectedPerson.Age.ToString());
}
}
// button1_Click should be probably replaced by Command
private void button1_Click(object sender, RoutedEventArgs e)
{
// sample showing that GUI is updated when ViewModel changes
vm.SelectedPerson = vm.PeopleList[2];
}
Hope this helps, I'm quite new to WPF, I'd like to hear any feedback if this is the right way to use MVVM, I think it's quite elegant since you only deal with the ViewModel and Model in code, and the View can be replaced.
I Found that the best way of implementing this is define some external class for all lookups that i use in grid and embedd them in the template as a static resource
We ended up having classes with static properties for each of of our combo box lists:
(you can't make the class itself static otherwise XAML won't be able to open it, but you won't get compile errors)
For example:
public class ZoneList
{
private static readonly IList<Zone> _Items = new List<Zone>();
public static IList<Zone> Items
{
get { return _Items; }
}
}
and then in XAML:
<UserControl.Resources>
<ResourceDictionary>
<ObjectDataProvider x:Key="myZoneList" ObjectType="{x:Type StaticLists:ZoneList}"/>
</ResourceDictionary>
</UserControl.Resources>
<ComboBox ItemsSource="{Binding Path=Items, Source={StaticResource myZoneList}}"></ComboBox>

Resources