I have a custom control which is having a dependency property defined in it and my control implements INotifyPropertyChanged interface.
Dependency Property is Collection of Custom Objects.
Scenario 1
DP is of type List, whatever change I make in the list, nothing updated in MainUI, because I believe WPF does not understand adding and removing objects in list. it understands completely new references so to achieve this, whenever I want to update my list on control I use
MyProperty=new List();
In this approach, my DP callback fires everytime but eventArgs.NewValue always remains zero(it updated the list on UI correctly) but because I need to write some logic in property changed callback based on e.NewItems.Count, in this case that didn't work. Please tell me why e.NewItems does not work.
Scenario 2
DP is of type ObservableCollection, so as my collection property in view model against which I am binding my DP. in this case my property change callbacks does not fire at all, because I never use "new" keyword again after initialzing the property first time. UI updates but property change still not fires. So my logic in property change call back does not gets executed.
How should I make any of them or both of them working.
I would use the ObservableCollection approach, and subscribe to it's CollectionChanged event.
That way you will get notified whenever the collection has been changed.
But the other approach should work as well. When you set the regular list to a new instance, the PropertyChangedCallback will be fired for the dependency property, and by examining the DependencyPropertyChangedEventArgs object you can get the new value.
XAML:
<StackPanel>
<Button Content="Add to observablecollection" Click="click1" />
<Button Content="Set list to new instance" Click="click2" />
</StackPanel>
Code-behind:
public partial class Window1 : Window
{
public ObservableCollection<string> Strings { get; set; }
public List<string> StringsList
{
get { return (List<string>)GetValue(StringsListProperty); }
set { SetValue(StringsListProperty, value); }
}
public static readonly DependencyProperty StringsListProperty =
DependencyProperty.Register("StringsList", typeof(List<string>), typeof(Window), new PropertyMetadata(null, StringsListPropertyChanged));
public Window1()
{
InitializeComponent();
Strings = new ObservableCollection<string>();
Strings.CollectionChanged += strings_CollectionChanged;
StringsList = new List<string> { "Test1", "Test2", "Test3", "Test4" };
}
void strings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Fires everytime the observablecollection has an item added/removed etc.
MessageBox.Show(string.Format("ObservableCollection has changed! Count is now {0}", this.Strings.Count.ToString()));
if (this.Strings.Count == 10)
Console.WriteLine("Collection contains 10 strings!!");
}
private static void StringsListPropertyChanged(DependencyObject e, DependencyPropertyChangedEventArgs args)
{
var newCount = ((List<string>)args.NewValue).Count.ToString();
MessageBox.Show(string.Format("Dependency property has changed! Count is now {0}", newCount));
}
private void click1(object sender, RoutedEventArgs e)
{
this.Strings.Add("Test1");
}
private void click2(object sender, RoutedEventArgs e)
{
this.StringsList = new List<string> { "Newitem1", "Newitem2" };
}
}
ObservableCollection inherits from both INotifyPropertyChanged and INotifyCollectionChanged. I think if you want to know when the collection changed you should use this interface:
INotifyCollectionChanged
Related
More about the question
First, let me clear the air... I'm somewhat new to C#. I bumped into this issue in an application I was working on. This particular class only had ObservableCollections exposed as Properties so my initial thought was that ObservervableCollection has it's own event, so I don't need the PropertyChanged event. The first attempt worked beautifully. Then I started cleaning up my code and found I didn't really need one of the backing vars, so I moved it all into the method...and it quit updating UI. Adding the INotifyPropertyChanged fixed the issue but I was left with a very big "WHY?"
Here is some test code I put together to see what I could figure out. Be advised, it is littered with bad practice, but I am really only trying to figure out when CollectionChanged can be depended on and when PropertyChagned must be added. Should I ever depend on CollectionChanged over PropertyChanged? If not, should we be using another List type, as we wouldn't really need the overhead of the ObservableCollection. It really seems like a waste to use both.
The bulk of the code
private TestClass test = new TestClass();
public MainWindow()
{
InitializeComponent();
this.DataContext = test;
}
internal class TestClass : INotifyPropertyChanged
{
public ObservableCollection<String> test1 = new ObservableCollection<String>();
public ObservableCollection<String> test2 = new ObservableCollection<String>() { "T2-A" };
public TestClass()
{
}
public ObservableCollection<String> Test1 { get => test1; set { } }
public ObservableCollection<String> Test2 { get => test2; set { this.test2 = value; } }
public ObservableCollection<String> Test3 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void BT1_Click(object sender, RoutedEventArgs e)
{
//Both of these work fine
test.test1.Add("T1-A");
test.Test1.Add("T1-B");
//First line works...Second line breaks it... but there is a setter.
test.Test2.Add("T2-C");
test.Test2 = new ObservableCollection<String>() { "T2-A", "T2-B" };
test.test2.Add("T2-D");
//First Line throws Null exception... So it's not creating a backing var of it's own.
//test.Test3.Add("T3-A");
var t3 = new ObservableCollection<String>() { "T3-B", "T3-C" };
test.Test3 = t3;
test.Test3.Add("T3-D");
//It updates when I trigger the property changed...but what broke CollectionChanged
test.OnPropertyChanged(nameof(test.Test3));
}
Here is the XAML if anyone is interested
<ListBox ItemsSource="{Binding Test1}" HorizontalAlignment="Left" Height="205" VerticalAlignment="Top" Width="161" Margin="10,5,0,0" />
<ListBox ItemsSource="{Binding Test2}" Height="205" Margin="186,5,187,0" VerticalAlignment="Top" />
<ListBox ItemsSource="{Binding Test3}" Height="205" Margin="366,5,8,0" VerticalAlignment="Top" />
<Button x:Name="bT1" Content="Test It" HorizontalAlignment="Left" Height="33" Margin="10,215,0,0" VerticalAlignment="Top" Width="516" Click="BT1_Click" />
My Findings so far
It seems that if you want to use the ObservableCollection CollectionChanged event, you must create your backing var, and never alter the instance of Collection. In otherwords, use Clear() to wipe and rebuild rather than 'var col = new ObservableCollection()'. Is there something I am missing? I would think this would get rather flaky when you start looking at TwoWay data. How would you prevent someone from breaking your code downstream following the 2nd line of test2?
//First line works...Second line breaks it... but there is a setter.
test.Test2.Add("T2-C");
test.Test2 = new ObservableCollection<String>() { "T2-A", "T2-B" };
Well, lets look at the setter
public ObservableCollection<String> Test2 { get => test2; set { this.test2 = value; } }
That does set the value to the new collection, but what about the binding?
In order for the binding to know of the new object you need to raise the NotifyPropertyChanged event. So after you set that value currently the binding will still be pointing to the old collection (which still is listening for collection changes but on the previous value). Adding a notify property changed call to the setter will allow the binding to be updated:
private ObservableCollection<String> test2;
public ObservableCollection<String> Test2
{
get
{
return test2;
}
set
{
test2 = value;
OnPropertyChanged("Test2");
}
}
Add this example to each property and your bindings will update when you update the entire collection to a new object. i.e. property = new ObservableCollection... Then the ListBox will be looking for collection changes on the new object.
In response to your last paragraph, there is another way to go about it:
You can keep one instance of the collection and never change it. i.e.:
public ObservableCollection<String> Test2 { get; set; }
Then in the constructor of the class initialize your collection. (or initialize it in-line, either works, the key is to only initialize it one time)
Test2 = new ObservableCollection<String>();
Then, when you want to create a new list do not overwrite the object. Instead just clear the list and add the new values in:
public void UpdateCollection(List<String> newValues)
{
Test2.Clear(); //notifies the list box with the CollectionChanged event
foreach(var value in newValues)
{
Test2.Add(value); //notifies the list box with the new item in the collection
}
}
so I have a model which contains 2 variables, a List and a DateTime. In my UserControl I have a DependencyProperty and I also defined a PropertyChangedCallback.
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(List<MyContainer>), typeof(UC), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyProperty)));
public List<MyContainer> My
{
get
{
return GetValue(MyProperty) as List<MyContainer>;
}
set
{
SetValue(MyProperty, value);
}
}
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
//do stuff
}
On my form there is a button, which do the changes on the other model variable (on the DateTime).
private void Date_Click(object sender, RoutedEventArgs e)
{
MyModel model = DataContext as MyModel;
if (model != null)
{
model.Date = model.Date.AddDays(1);
}
}
And finally here is my model.
public class MyModel : INotifyPropertyChanged
{
private List<MyContainer> _My;
private DateTime _Date;
public MyModel()
{
_Date = DateTime.Now.Date;
_My = new List<MyContainer>();
}
public List<MyContainer> My
{
get
{
return _My;
}
set
{
_My = value;
OnPropertyChanged("My");
}
}
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML declaration is the following.
<local:UC My="{Binding My}" />
So my problem is the after I hit the run, it fires the OnMyProperty once, after that if I hit the button, it changes the DateTime property well, but the OnMyProperty callback doesn't firing again. However I noticed that if I modify my model like this
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
_My = new List<MyContainer>(_My); //added
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
now it fires it every time when I hit the button. How can I trigger the second behaviour without that modification?
After setting the value of a DependencyProperty it first checks if the new value is different to the old one. Only in this case the PropertyChangedCallback method you registered with that DependencyProperty is called. So the name PropertyChanged makes sense.
In your (not modified) case you not even try to change My (only Date). So there is no reason to raise the callback function.
The answer is that you almost certainly do not need to do this. When you ask a question about how to make the framework do something it really does not want to do, always say why you think you need to do that. It's very likely that there's a much easier answer that everybody else is already using.
The only thing you have bound to the control is My. Therefore, if My hasn't changed, then the state of the control should not change. If you want the state of the control to change when Date changes, bind Date to some property of the control. The only way the control should ever get information from any viewmodel is through binding one of its dependency properties to a property of the viewmodel.
The control should not ever know or care who or what is providing values for its properties. It should be able to do its job knowing only the property values it has been given.
If the contents of My have changed -- you added an item or removed one -- of course the control has no way of knowing that, because you refused to tell it. You're just telling it there's a new list. It checks, sees it's still got the same old list, and ignores you. The My property of your viewmodel should be an ObservableCollection, because that will notify the control when you add or remove items in the collection.
The items themselves, your MyContainer class, must implement INofityPropertyChanged as well, if you want to be able to change their properties while they are displayed in the UI.
The dependency property My on your control must not be of type List<T>. It should probably be type object, just like ItemsControl.ItemsSource. Then your control template can display it in an ItemsControl which knows what to do with it. If an ObservableCollection is bound to it as I suggested above, the ItemsControl will update automatically. In OnMyProperty, your control class can check to see if it's an observable collection as well:
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged +=
(s, ecc) => {
// Do stuff with UC and ecc.NewItems, ecc.OldItems, etc.
};
}
}
I have this piece of code:
private ObservableCollection<Stats> _stats;
public StatisticsViewModel()
{
Stats = new ObservableCollection<Stats>();
Stats.Add(new Stats() { Type = "Min", Price = 100, Legend = Legend.Default });
}
public ObservableCollection<Stats> Stats
{
get
{
return _stats;
}
set
{
if (_stats != value)
{
_stats = value;
RaisePropertyChanged("Stats");
}
}
}
When the new collection is created, the set of Stats is called. However, when adding the object, it does not and so it does not execute RaisePropertyChanged...
What could I do to resolve this issue?
When the new collection is created, the set of Stats is called.
Correct. This is because the code is setting the Stats property, which invokes the setter method.
However, when adding the object, it does not and so it does not execute RaisePropertyChanged
Also correct. When calling .Add on an object the code isn't setting the property which holds that object. So there's no reason for the setter method to be invoked. The Stats property itself isn't being changed in this case, you're just interacting with the object.
What could I do to resolve this issue?
The ObservableCollection class exposes two events that you can subscribe to. The CollectionChanged event is raised when the collection itself changes, and the PropertyChanged event is raised when a property value on the collection changes.
For example, if you want to handle the event when the collection changes, you can subscribe to that event:
Stats.CollectionChanged += CollectionChangeHandler;
// elsewhere...
private void CollectionChangeHandler(object sender, NotifyCollectionChangedEventArgs e)
{
// do something to respond to the changed collection
}
I have a XAML view with a list box:
<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}"
SelectedItem="{Binding SelectedFoo, Mode=TwoWay}"
ScrollSelectedItem="{Binding SelectedFoo}">
<!-- data templates, etc. -->
</control:ListBoxScroll>
The selected item is bound to a property in my view. When the user selects an item in the list box my SelectedFoo property in the view model gets updated. When I set the SelectedFoo property in my view model then the correct item is selected in the list box.
The problem is that if the SelectedFoo that is set in code is not currently visible I need to additionally call ScrollIntoView on the list box. Since my ListBox is inside a view and my logic is inside my view model ... I couldn't find a convenient way to do it. So I extended ListBoxScroll:
class ListBoxScroll : ListBox
{
public static readonly DependencyProperty ScrollSelectedItemProperty = DependencyProperty.Register(
"ScrollSelectedItem",
typeof(object),
typeof(ListBoxScroll),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(onScrollSelectedChanged)));
public object ScrollSelectedItem
{
get { return (object)GetValue(ScrollSelectedItemProperty); }
set { SetValue(ScrollSelectedItemProperty, value); }
}
private static void onScrollSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listbox = d as ListBoxScroll;
listbox.ScrollIntoView(e.NewValue);
}
}
It basically exposes a new dependency property ScrollSelectedItem which I bind to the SelectedFoo property on my view model. I then hook into the property changed callback of the dependent property and scroll the newly selected item into view.
Does anyone else know of an easier way to call functions on user controls on a XAML view that is backed by a view model? It's a bit of a run around to:
create a dependent property
add a callback to the property changed callback
handle function invocation inside the static callback
It would be nice to put the logic right in the ScrollSelectedItem { set { method but the dependency framework seems to sneak around and manages to work without actually calling it.
Have you tried using Behavior... Here is a ScrollInViewBehavior. I have used it for ListView and DataGrid..... I thinks it should work for ListBox......
You have to add a reference to System.Windows.Interactivity to use Behavior<T> class
Behavior
public class ScrollIntoViewForListBox : Behavior<ListBox>
{
/// <summary>
/// When Beahvior is attached
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
/// <summary>
/// On Selection Changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void AssociatedObject_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (sender is ListBox)
{
ListBox listBox = (sender as ListBox);
if (listBox .SelectedItem != null)
{
listBox.Dispatcher.BeginInvoke(
(Action) (() =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem !=
null)
listBox.ScrollIntoView(
listBox.SelectedItem);
}));
}
}
}
/// <summary>
/// When behavior is detached
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -=
AssociatedObject_SelectionChanged;
}
}
Usage
Add alias to XAML as xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
then in your Control
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem,
Mode=TwoWay}"
SelectionMode="Single">
<i:Interaction.Behaviors>
<Behaviors:ScrollIntoViewForListBox />
</i:Interaction.Behaviors>
</ListBox>
Now When ever "MyItem" property is set in ViewModel the List will be scrolled when changes are refelected.
After reviewing the answers a common theme came up: external classes listening to the SelectionChanged event of the ListBox. That made me realize that the dependant property approach was overkill and I could just have the sub-class listen to itself:
class ListBoxScroll : ListBox
{
public ListBoxScroll() : base()
{
SelectionChanged += new SelectionChangedEventHandler(ListBoxScroll_SelectionChanged);
}
void ListBoxScroll_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView(SelectedItem);
}
}
I feel this is the simplest solution that does what I want.
Honourable mention goes to adcool2007 for bringing up Behaviours. Here are a couple of articles for those interested:
http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
I think for generic behaviours that will be added to several different user controls (e.g. click behaviours, drag behaviours, animation behaviours, etc.) then attached behaviours make a lot of sense. The reason I don't want to use them in this particular case is that the implementation of the behaviour (calling ScrollIntoView) isn't a generic action that can happen to any control other than a ListBox.
Because this is strictly a View problem, there's no reason you can't have an event handler in the code behind of your view for this purpose. Listen for ListBox.SelectionChanged and use that to scroll the newly selected item into view.
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
((ListBox)sender).ScrollIntoView(e.AddedItems[0]);
}
You also don't need a derived ListBox to do this. Just use a standard control and when the ListBox.SelectedItem value changes (as described in your original question), the above handler will be executed and the item will be scrolled into view.
<ListBox
ItemsSource="{Binding Path=FooCollection}"
SelectedItem="{Binding Path=SelectedFoo}"
SelectionChanged="ListBox_SelectionChanged"
/>
Another approach would be to write an attached property that listens for ICollectionView.CurrentChanged and then invokes ListBox.ScrollIntoView for the new current item. This is a more "reusable" approach if you need this functionality for several list boxes. You can find a good example here to get you started: http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/
I know this is an old question, but my recent search for the same problem has brought me to this. I wanted to use the behavior approach, but didn't want a dependency on the Blend SDK just to give me Behavior<T> so here's my solution without it:
public static class ListBoxBehavior
{
public static bool GetScrollSelectedIntoView(ListBox listBox)
{
return (bool)listBox.GetValue(ScrollSelectedIntoViewProperty);
}
public static void SetScrollSelectedIntoView(ListBox listBox, bool value)
{
listBox.SetValue(ScrollSelectedIntoViewProperty, value);
}
public static readonly DependencyProperty ScrollSelectedIntoViewProperty =
DependencyProperty.RegisterAttached("ScrollSelectedIntoView", typeof (bool), typeof (ListBoxBehavior),
new UIPropertyMetadata(false, OnScrollSelectedIntoViewChanged));
private static void OnScrollSelectedIntoViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as Selector;
if (selector == null) return;
if (e.NewValue is bool == false)
return;
if ((bool) e.NewValue)
{
selector.AddHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
}
else
{
selector.RemoveHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
}
}
private static void ListBoxSelectionChangedHandler(object sender, RoutedEventArgs e)
{
if (!(sender is ListBox)) return;
var listBox = (sender as ListBox);
if (listBox.SelectedItem != null)
{
listBox.Dispatcher.BeginInvoke(
(Action)(() =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem !=null)
listBox.ScrollIntoView(listBox.SelectedItem);
}));
}
}
}
and then usage is just
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem, Mode=TwoWay}"
SelectionMode="Single"
behaviors:ListBoxBehavior.ScrollSelectedIntoView="True">
I'm using this (in my opinion) clear and easy solution
listView.SelectionChanged += (s, e) =>
listView.ScrollIntoView(listView.SelectedItem);
where listView is name of ListView control in xaml, SelectedItem is affected from my MVVM and code is inserted in constructor in xaml.cs file.
Try this:
private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lstBox.ScrollIntoView(lstBox.SelectedItem);
}
After tying various methods I found the following to be the simplest and the best
lstbox.Items.MoveCurrentToLast();
lstbox.ScrollIntoView(lstbox.Items.CurrentItem);
I took Ankesh's answer and made it not dependent on the blend sdk. The downside of my solution is that it will apply to all listboxes in your app. But the upside is no custom class needed.
When your app is initializing...
internal static void RegisterFrameworkExtensionEvents()
{
EventManager.RegisterClassHandler(typeof(ListBox), ListBox.SelectionChangedEvent, new RoutedEventHandler(ScrollToSelectedItem));
}
//avoid "async void" unless used in event handlers (or logical equivalent)
private static async void ScrollToSelectedItem(object sender, RoutedEventArgs e)
{
if (sender is ListBox)
{
var lb = sender as ListBox;
if (lb.SelectedItem != null)
{
await lb.Dispatcher.BeginInvoke((Action)delegate
{
lb.UpdateLayout();
if (lb.SelectedItem != null)
lb.ScrollIntoView(lb.SelectedItem);
});
}
}
}
This makes all of your listboxes scroll to selected (which I like as a default behavior).
I have a custom control that has an Items property. I Have applied an EditorAttribute with a UITypeEditor of type CollectionEditor.
Collection Type:
[Serializable]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public class ListItemsCollection : CollectionBase
{
// methods
}
Property Declaration In The Control:
private new ListItemsCollection _Items;
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public new ListItemsCollection Items
{
get
{
return _Items;
}
set
{
_Items = value;
// do other UI changes
}
}
Problem:
When I drop this control to the designer surface, I am able to add items to the Items property using the PropertyGrid. But, the when I click the Ok button of the CollectionEditor the setter of the Items property is not getting called.
AFAIK when a value is returned from the EditValue method of a UITypeEditor class the setter block of the property is supposed to be called.
This is driving me insane. I even tried adding Event's to the ListItemsCollection, so that when Items are added, I can whatever I want with the control's ui.
This is not supposed to be hard. What am I doing wrong?
I try to reprodeuce your situation: using following code, I get a message box showing whenever I edit the list from VS property window. Beware that you have to create the list by yourself. If you don't create it, VS create a temp list which you can edit from property window, but does not set your property to this list (so your setter will never be called)
public UserControl1()
{
InitializeComponent();
list = new BindingList<ListViewItem>();
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
}
void list_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show(e.ListChangedType.ToString());
}
private BindingList<ListViewItem> list;
public BindingList<ListViewItem> List1
{
get { return list; }
}
Collection properties should be read-only. It's the collection that is retrieved through the getter, and adjusted. The setter never enters into it, because that would mean setting a new collection.