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.
Related
I'l start by letting a picture do some talking.
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
Many thanks for any insights,
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
First, our Window
<Window x:Class="UCsAndICommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:UCsAndICommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<t:ViewModel />
</Window.DataContext>
<t:ItemsEditor Items="{Binding Items}"
AddItem="{Binding AddItem}"
RemoveItem="{Binding RemoveItem}" />
</Window>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
Next, the UserControl
<UserControl x:Class="UCsAndICommands.ItemsEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.
Here's the definitions of these properties on the UC
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
public class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public ICommand RemoveItem { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
AddItem = new DelegatedCommand<object>(
o => true, o => Items.Add(new Item()));
RemoveItem = new DelegatedCommand<Item>(
i => true, i => Items.Remove(i));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
Refer the below code.
UserControl.XAML
<Grid>
<ListBox ItemsSource="{Binding Things}" x:Name="lst">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ThingName}" Margin="3"/>
<Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Window.Xaml
<Window x:Class="MultiBind_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBind_Learning"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
<local:UserControl2/>
</StackPanel>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
class ThingViewModel
{
private ObservableCollection<Thing> things = new ObservableCollection<Thing>();
public ObservableCollection<Thing> Things
{
get { return things; }
set { things = value; }
}
public ICommand AddCommnd { get; set; }
public ICommand RemoveCommand { get; set; }
public ThingViewModel()
{
for (int i = 0; i < 10; i++)
{
things.Add(new Thing() { ThingName="Thing" +i});
}
AddCommnd = new BaseCommand(Add);
RemoveCommand = new BaseCommand(Remove);
}
void Add(object obj)
{
things.Add(new Thing() {ThingName="Added New" });
}
void Remove(object obj)
{
things.Remove((Thing)obj);
}
}
Thing.cs
class Thing :INotifyPropertyChanged
{
private string thingName;
public string ThingName
{
get { return thingName; }
set { thingName = value; OnPropertyChanged("ThingName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.
I want to separate my user interface from my code, so I (obviously) landed at bindings. As a test, I've written the following XAML:
<Window x:Class="BindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="Auto" Width="200">
<StackPanel>
<TextBox Text="{Binding Item}"/>
<Button Content="Add" Click="AddNew"/>
<ListBox Height="100" ItemsSource="{Binding Items}"/>
</StackPanel>
</Window>
The C# looks like this:
namespace BindingTest
{
public partial class MainWindow : Window
{
public string Item { get; set; }
public ObservableCollection<string> Items { get; set; }
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<string>();
}
private void AddNew(object sender, RoutedEventArgs e)
{
Items.Add(Item);
}
}
}
What I want to happen is that the text entered into the textbox is added to the listbox's itemssource. However, this doesn't happen...
Two things you need two do -
Set - DataContext = this; in your constructor.
You'd be better off if you would change your properties to dependency properties instead. You could do that easily with the "propdp" snippet in visual studio.
Data binding is performed against the current data context. However, you have not set the data context for your window. Often you will set the data context to a view model but in your case you simply want to use the window class for that.
You should add the following line to the constructor:
DataContext = this;
Change your code to this:
public partial class MainWindow : Window
{
public string Item { get; set; }
public ObservableCollection<string> Items { get; set; }
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<string>();
DataContext = this;
}
private void AddNew(object sender, RoutedEventArgs e)
{
Items.Add(Item);
}
}
}
You do need to set your DataContext - works for me.
Two things:
You should set the correct data context for your window. Otherwise the binding will not find your properties.
You should initialize your Items collection before the InitializeComponent() call as inside it the ListBox tries to evaluate the expression and get NULL as the binding souce. And since you are not implementing INotifyPropertyChanged and the property is not a DependencyProperty the ListBox will never reevaluate the binding thus it will never get the instance of your Items collection.
So, the code should be as follows:
public MainWindow()
{
Items = new ObservableCollection<string>();
DataContext = this;
InitializeComponent();
}
Try this
hope this will work. But this is not hte right approach. You need to set the DataContext to the Object whose properties u guna use for binding. you must follow MVVM Architecture.
I have two UserControls (uc1 and uc2) loading into a third UserControl (shell). Shell has two properties, uc1 and uc2, of type UserControl1 and UserControl2, and each have a DependencyProperty registered to their own classes called IsDirty:
public static readonly DependencyProperty IsDirtyProperty = DependencyProperty.Register("IsDirty", typeof (bool), typeof (UserControl1));
public bool IsDirty
{
get { return (bool) GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
(same code for UserControl2)
Shell has TextBlocks bound to the IsDirty properties:
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
<TextBlock Text="{Binding ElementName=shell, Path=Uc2.IsDirty}"/>
When I change the values of IsDirty in uc1 and uc2, Shell never gets notified. What am I missing? UserControl is descendant of DependencyObject...
The same behavior occurs if I have regular properties notifying changes via INotifyPropertyChanged.
If I raise a routed event from uc1 and uc2, bubbling up to Shell, then I can catch the Dirty value and everything works, but I shouldn't have to do that, should I?
Thanks
Edit: The answer is to raise property changed event on the Uc1 and Uc2 properties or make them DPs.
I tried reproducing your problem using a simple setup, and it works fine for me. I'm not sure though if this setup is correct enough to replicate your situation. Anyway, I'm posting it just in case. It might be helpful:
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
x:Name="shell"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_Click">Click</Button>
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
</StackPanel>
</Window>
Code-Behind:
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyUserControl uc1 = new MyUserControl();
public MyUserControl Uc1
{
get { return this.uc1; }
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.uc1.IsDirty = !this.uc1.IsDirty;
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
}
public bool IsDirty
{
get { return (bool)GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
// Using a DependencyProperty as the backing store for IsDirty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsDirtyProperty =
DependencyProperty.Register("IsDirty", typeof(bool), typeof(UserControl), new UIPropertyMetadata(false));
}
}
Karmicpuppet's answer works well. However it didn't solve my problem because Shell is also of type UserControl. For it to work I needed to raise the property changed on Uc1 and Uc2. When I declared them as DependencyProperties all worked as expected. Duh!
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.
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