ObservableCollection dependency property does not update when item in collection is deleted - wpf

I have a attached property of type ObservableCollection on a control. If I add or remove items from the collection, the ui does not update. However if I replace the collection within with a new one the ViewModel the ui does update.
Can someone give me an example of what I need to do within the Dependency object so that it can handle changes within the collection?
Part of the dependency object is listed below:
public class RadCalendarBehavior : DependencyObject
{
private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var calendar = d as RadCalendar;
if (e.NewValue != null)
{
calendar.DayTemplateSelector = new SpecialDaySelector((ObservableCollection<DateTime>)e.NewValue, GetSpecialDayTemplate(d));
}
}
public static ObservableCollection<DateTime> GetSpecialDays(DependencyObject obj)
{
return (ObservableCollection<DateTime>)obj.GetValue(SpecialDaysProperty);
}
public static void SetSpecialDays(DependencyObject obj, ObservableCollection<DateTime> value)
{
obj.SetValue(SpecialDaysProperty, value);
}
public static readonly DependencyProperty SpecialDaysProperty =
DependencyProperty.RegisterAttached("SpecialDays", typeof(ObservableCollection<DateTime>), typeof(RadCalendarBehavior), new UIPropertyMetadata(null, OnSpecialDaysChanged));
}
}
I understand that I need to register that the collection has changed, but I am unsure how to do this within the dependency property

A change within the collection won't trigger the OnSpecialDaysChanged callback, because the value of the dependency property hasn't changed. If you need to react to detect changes with the collection, you need to handle the event CollectionChanged event manually:
private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var calendar = d as RadCalendar;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
// Unsubscribe from CollectionChanged on the old collection
coll.CollectionChanged -= SpecialDays_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<DateTime>)e.NewValue;
calendar.DayTemplateSelector = new SpecialDaySelector(coll, GetSpecialDayTemplate(d));
// Subscribe to CollectionChanged on the new collection
coll.CollectionChanged += SpecialDays_CollectionChanged;
}
}
private static void SpecialDays_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// handle CollectionChanged
}

This is just to add to the answer by Thomas. In my code I do interact with the DependencyObject's properties by creating a handler object localy like below:
private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var action = new NotifyCollectionChangedEventHandler(
(o, args) =>
{
var calendar = d as RadCalendar;
if (calendar!= null)
{
// play with calendar's properties/methods
}
});
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
// Unsubscribe from CollectionChanged on the old collection
coll.CollectionChanged -= action;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<DateTime>)e.NewValue;
// Subscribe to CollectionChanged on the new collection
coll.CollectionChanged += action;
}
}
Hope this is helpful to someone.

If you have a collection-type dependency property keep the following in mind:
If your property is a reference type, the default value specified in dependency property metadata is not a default value per instance; instead it is a default value that applies to all instances of the type. [...]
To correct this problem, you must reset the collection dependency property value to a unique instance, as part of the class constructor call.
(see MSDN Collection-Type Dependency Properties)
To answer Sam's question (I just ran into the same problem):
Make your CollectionChanged-handler non-static and unsubscribe/re-subscribe on instance-level.
private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var calendar = (RadCalendar)d;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
// Unsubscribe from CollectionChanged on the old collection of the DP-instance (!)
coll.CollectionChanged -= calendar.SpecialDays_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<DateTime>)e.NewValue;
calendar.DayTemplateSelector = new SpecialDaySelector(coll, GetSpecialDayTemplate(d));
// Subscribe to CollectionChanged on the new collection of the DP-instance (!)
coll.CollectionChanged += calendar.SpecialDays_CollectionChanged;
}
}
private void SpecialDays_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// handle CollectionChanged on instance-level
}

Related

Prevent RepositoryItemSearchLookUpEdit when Popup is Open When CloseUpKey.Key is pressed

I use a RepositoryItemSearchLookUpEdit. its CloseUpKey property is set to space
result.CloseUpKey = new DevExpress.Utils.KeyShortcut(System.Windows.Forms.Keys.Space);
I want to use this shortcut only for open popup and not for closing popup.
How can I achieve this?
UPDATE------------------------
First I create an RepositoryItemSearchLookUpEdit object
var result = new RepositoryItemSearchLookUpEdit();
result.CloseUpKey = new DevExpress.Utils.KeyShortcut(System.Windows.Forms.Keys.Space);
result.KeyDown += repositoryItemLookUpEdit_KeyDown;
result.CloseUp += repositoryItemLookUpEdit_CloseUp;
result.QueryCloseUp += repositoryItemLookUpEdit_QueryCloseUp;
private void repositoryItemLookUpEdit_QueryCloseUp(object sender, System.ComponentModel.CancelEventArgs e)
{
var edit = sender as SearchLookUpEdit;
KeyEventArgs k = new KeyEventArgs(edit.Properties.CloseUpKey.Key);
AttachKeyPressEvent(k);
if (k.KeyCode == edit.Properties.CloseUpKey.Key)
e.Cancel = true;
}
And pass it to a grid column:
grdListView.Columns["yyy"].ColumnEdit = result
How can I achieve that with these events, without creating a descendant SearchLookUpEdit
UPDATED:
The problem is that CloseUp event (where you could get the necessary info about the closeup key) occurs after the QueryCloseUp event (where you could precent the closing up event). Also the KeyPress, KeyDown and KeyUp events seem also NOT to occur when the QueryCloseUp occurs, as a result they couldn't be overridden. So I tried this, I created a custom KeyEventHandler and triggered him during QueryCloseUp event in order to get the necessary info of what key was pressed and cancel the event if the close key event was the one. Here is my codeTry it to see if it suits you
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//Here you can add your grid control as you have created
DataTable dt = new DataTable();
dt.Columns.Add("ID"); //use your own names and types
gridControl1.DataSource = dt;
var result = new RepositoryItemSearchLookUpEdit();
result.CloseUpKey = new DevExpress.Utils.KeyShortcut(System.Windows.Forms.Keys.Space);
result.QueryCloseUp += new CancelEventHandler(repositoryItemLookUpEdit_QueryCloseUp);
((gridControl1.MainView as GridView).Columns["ID"] as GridColumn).ColumnEdit = result;
}
private static readonly object myQueryCloseUp = new object();
public event KeyEventHandler MyQueryCloseUp
{
add { Events.AddHandler(myQueryCloseUp, value); }
remove { Events.RemoveHandler(myQueryCloseUp, value); }
}
protected virtual void AttachKeyPressEvent(KeyEventArgs e)
{
KeyEventHandler handler = (KeyEventHandler)Events[myQueryCloseUp];
if (handler != null)
handler(this, e);
}
//Here you add your own Handler implementation
public void repositoryItemLookUpEdit_QueryCloseUp(object sender, CancelEventArgs e)
{
KeyEventArgs k = new KeyEventArgs((sender as SearchLookUpEdit).Properties.CloseUpKey.Key);
AttachKeyPressEvent(k);
if (k.KeyCode == (sender as SearchLookUpEdit).Properties.CloseUpKey.Key)
e.Cancel = true;
}
}

Bindings seems broken while using observable collection

I have a ViewModel with an Observable Collection Property
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get
{
return currentSensorAreasList;
}
set
{
if (currentSensorAreasList != value)
{
currentSensorAreasList = value;
OnPropertyChanged(PROPERTY_NAME_CURRENT_SENSOR_AREAS_LIST);
}
}
}
Then in my xaml i have a binding
ItemsSource="{Binding CurrentSensorAreasList}">
This Observable Collection is updated trought a method that can be call in the viewModel constructor or when a collectionchanged handler from another list gets called.
I just clear the list and then add a fewer new items. While debugging i see all my new items updated on the list. But the UI does not get updated.
When i regenerate the viewModel and then this update method gets call in the constructor the list gets updated in the UI.
Any ideas?? I don't know if the problem comes when i call the method from a handler.....
UPDATE #1
As requested i'm going the code when I update the list
I have tested two ways to do this update
private void UpdateList1()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList.Clear();
CurrentSensorAreasList.AddRange(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList));
//AddRange is an extension method.
}
}
private void UpdateList2()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList = new ObservableCollection<GeographicArea>(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList))
}
}
Both cases works when i call it from the constructor. Then I have Other Lists where the Areas changes, and i get notified via CollectionChanged Handlers.
private void globalAreaManagerList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (AreaManager newItem in e.NewItems)
{
newItem.AreaList.CollectionChanged += AreaList_CollectionChanged;
}
}
if (e.OldItems != null)
{
foreach (AreaManager oldItem in e.OldItems)
{
oldItem.AreaList.CollectionChanged -= AreaList_CollectionChanged;
}
}
UpdateList();
}
private void AreaList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateList();
}
So when i use UpdateList1 seems to work more times but suddenly the Binding is broken and then this update does not show in the UI.
If you wish change exactly an instance of collection I recomend using DependecyProperty for that case.
Here is:
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get { return (ObservableCollection<GeographicArea>)GetValue(CurrentSensorAreasListProperty); }
set { SetValue(CurrentSensorAreasListProperty, value); }
}
public static readonly DependencyProperty CurrentSensorAreasListProperty =
DependencyProperty.Register("CurrentSensorAreasList", typeof(ObservableCollection<GeographicArea>), typeof(ownerclass));
Where ownerclass - a name of class where you put this property.
But the better way is create only one instance of ObservaleCollection and then just change its items. I mean Add, Remove, and Clear methods.

Dependency Properties' PropertyChangedCallback not getting called

If have an own user control with a DependencyProperty and the corresponding callback method like below:
public partial class PieChart : UserControl
{
public static readonly DependencyProperty RatesProperty = DependencyProperty.Register("Rates", typeof(ObservableCollection<double>), typeof(PieChart),
new PropertyMetadata(new ObservableCollection<double>() { 1.0, 1.0 }, new PropertyChangedCallback(OnRatesChanged)));
[...]
public static void OnRatesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((PieChart)d).drawChart();
}
When using this user control, I bind an ObservableCollection called "Rates" to the RatesProperty. Rates and the related methods look like this:
private ObservableCollection<double> rates;
public ObservableCollection<double> Rates
{
get { return this.rates; }
set
{
if (this.rates != value)
{
this.rates = value;
OnPropertyChanged("Rates");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
When I change the ObservableCollection Rates (e.g. with this.Rates = new ObservableCollection<double>() { 1.0, 2.0 }) the OnRatesChanged() method of the user-control is called like expected. But when I execute the following, it is not called:
this.Rates[0] = (double)1;
this.Rates[1] = (double)2;
OnPropertyChanged("Rates");
I expected that when I raise the PropertyChanged event with the correct property name, the corresponding callback in the user control is always called. Is that wrong?
I found this thread: Getting PropertyChangedCallback of a dependency property always - Silverlight
which covers silverlight but I think then that the same is true in WPF.
So the Framework in the background checks if the bound property (in my example "Rates") changed and only if it changed, it calls the associated callback, correct? Thus changing the elements of my collection has no effect, I always have to change the complete collection?
Thank you!
Your conclusion is right, your OnRatesChanged callback will only be called when the Rates dependency property is set to a new collection (or null).
In order to get notified about changes in the collection, you would also have to register a NotifyCollectionChangedEventHandler:
private static void OnRatesChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var pieChart = (PieChart)d;
var oldRates = e.OldValue as INotifyCollectionChanged;
var newRates = e.NewValue as INotifyCollectionChanged;
if (oldRates != null)
{
oldRates.CollectionChanged -= pieChart.OnRatesCollectionChanged;
}
if (newRates != null)
{
newRates.CollectionChanged += pieChart.OnRatesCollectionChanged;
}
pieChart.drawChart();
}
private void OnRatesCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
...
}
drawChart();
}

WPF: Handle a large number of routed events

I need your recomandations for the following problem:
Let say you have a MyView type (UserControl), which defines a routed event IsSelectedChanged. It is raised every time myView.IsSelected property value is changed.
Also, you have a MyContainer (Canvas), which contains a very (very!) large number of children of type MyView. MyContainer has routed event MyViewsSelectionChanged, that is raised whenever MyViewsSelection is changed. MyViewsSelection is a set of MyView objects that have IsSelected property set to true. MyContainer will handle MyView.IsSelectedChanged for every child and will provide its MyViewSelection status to the MyContainerParent (Panel)
MyContainerParent will handle myContainer.MyViewsSelectionChanged event
The issue I am afraid of is that my application will under-perform for a very large selection of MyView objects, resulting in a sort of 'wildfire' of events.
Any recomandations to prevent the issue, will be much appreciated!
Thanks
some code:
BatchView.IsSelectedChanged (MyView):
public static readonly RoutedEvent IsSelectedChangedEvent = EventManager.RegisterRoutedEvent(
"IsSelectedChanged",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(BatchView)
);
/// <summary>
/// Occurs when IsSelected property value is changed.
/// </summary>
public event RoutedEventHandler IsSelectedChanged {
add { AddHandler(IsSelectedChangedEvent, value); }
remove { RemoveHandler(IsSelectedChangedEvent, value); }
}
void RaiseIsSelectionChangedEvent() {
RoutedEventArgs e = new RoutedEventArgs(IsSelectedChangedEvent, this.BatchViewModel);
RaiseEvent(e);
Logger.Debug("IsSelectionChanged: {0}; IsSelected = {1}", this.BatchViewModel.Description, this.IsSelected);
}
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached(
"IsSelected",
typeof(bool),
typeof(BatchView),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(delegate(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
BatchView view = sender as BatchView;
bool isSelected = Convert.ToBoolean(args.NewValue);
if ( view != null ) {
view._border.BorderBrush = isSelected ? Brushes.Magenta : Brushes.Black;
view.IsPrimarySelected = view.IsFocused && isSelected;
}
})));
/// <summary>
/// Get/set whether this batch view is selected
/// </summary>
public bool IsSelected {
get { return (bool)GetValue(IsSelectedProperty); }
set {
if ( IsSelected != value ) {
SetValue(IsSelectedProperty, value);
RaiseIsSelectionChangedEvent();
}
}
}
GanttView (MyContainer):
static GanttView() {
EventManager.RegisterClassHandler(typeof(BatchView), BatchView.IsSelectedChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var batchView = sender as BatchView;
var ganttView = batchView.FindVisualParent<GanttView>();
if ( ganttView != null ) {
ganttView.RaiseBatchViewsSelectionChangedEvent();
}
args.Handled = true;
}));
}
public static readonly RoutedEvent BatchViewsSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"BatchViewsSelectionChanged",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(GanttView)
);
public event RoutedEventHandler BatchViewsSelectionChanged {
add { AddHandler(BatchViewsSelectionChangedEvent, value); }
remove { RemoveHandler(BatchViewsSelectionChangedEvent, value); }
}
void RaiseBatchViewsSelectionChangedEvent() {
RoutedEventArgs e = new RoutedEventArgs(BatchViewsSelectionChangedEvent, this);
RaiseEvent(e);
Logger.Debug("BatchViewsSelectionChanged: {0};", this.SelectedBatchViews.Count());
}
SchedulerView (MyContainerParent):
static SchedulerView() {
EventManager.RegisterClassHandler(typeof(GanttView), GanttView.BatchViewsSelectionChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var schedulerView = ((GanttView)sender).FindVisualParent<SchedulerView>();
if ( schedulerView != null ) {
if ( schedulerView.BatchesSelectionChanged != null ) {
BatchesSelectionChangedEventArgs e = new BatchesSelectionChangedEventArgs();
e.SelectedBatchesCount = schedulerView.GanttView.SelectedBatchViews.Count();
e.TotalBatchesDuration = schedulerView.GanttView.SelectedBatchViews.Sum<BatchView>(bv => bv.BatchViewModel.Model.Duration);
e.TotalBatchesQuantity = schedulerView.GanttView.SelectedBatchViews.Sum<BatchView>(bv => bv.BatchViewModel.Model.Quantity);
schedulerView.BatchesSelectionChanged(schedulerView, e);
}
}
}));
}
If you are concerned about the number of events you have to process. You should re-evaluate your approach. Is there a way to determine when the user has finished selecting the items?
If there is no way of reducing the number of events then you might want to implement a throttle, i.e. you only process an event if no event has been received for a certain amount of time.
You can implement this yourself - e.g. by using a timer - or you can use the reactive extensions' (RX) Throttle function.
Throttle "Ignores the values from an observable sequence which are
followed by another value before due time with the specified source
and dueTime"
You can find the RX at http://msdn.microsoft.com/en-us/data/gg577609.aspx and the documentation for Trottle at http://msdn.microsoft.com/en-us/library/hh229298%28v=vs.103%29. Obviously you can also install RX via NuGet.
I want to share the solution to my problem. Solution is based on recomandation given by my manager and Obalix. So, I will use a timer, to delay a bit the raising of GanttView.BacthViewsSelectionChangedEvent.
static GanttView() {
EventManager.RegisterClassHandler(typeof(BatchView), BatchView.IsSelectedChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var batchView = sender as BatchView;
var ganttView = batchView.FindVisualParent<GanttView>();
if ( ganttView != null && !ganttView._batchViewIsSelectedChangedEventQueued ) {
ganttView._batchViewIsSelectedChangedEventQueued = true;
System.Timers.Timer eventTrigger = new System.Timers.Timer(100) { AutoReset = false };
eventTrigger.Start();
eventTrigger.Elapsed += new System.Timers.ElapsedEventHandler(delegate(object timer, System.Timers.ElapsedEventArgs e) {
ganttView._batchViewIsSelectedChangedEventQueued = false;
ganttView.Dispatcher.Invoke(new Action(delegate() { ganttView.RaiseBatchViewsSelectionChangedEvent(); }), DispatcherPriority.Normal, null);
});
}
args.Handled = true;
}));
}
It is important to use the Dispatcher property for invokation of ganttView.RaiseBatchViewsSelectionChangedEvent(), otherwise you will get an exception (" The calling thread cannot access this object because a different thread owns it."), please refer to this post http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
Dan and Obalix, thank you a lot for your time and considerations!

ObservableCollection has Depedency Property in Silverlight

Recently i was developing a custom control in Silverlight, I created custom dependency property which is of type ObservableCollection. I have another 2 custom dependency properties of type strings. My requirement is on addition of any item to collection, I have to fire collectionChanged Event , in this event handler, i want to update the other 2 dependency properties.
public static readonly DependencyProperty itemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<ValidationErrorMessage>), typeof(SummaryUserControl), new PropertyMetadata(new ObservableCollection<ValidationErrorMessage>(), new PropertyChangedCallback(fun1)));
public ObservableCollection<ValidationErrorMessage> Items
{
get
{
return (ObservableCollection<ValidationErrorMessage>)base.GetValue(itemsProperty);
}
set
{
base.SetValue(itemsProperty, value);
}
}
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
var newValue = evt.NewValue as ObservableCollection<ValidationErrorMessage>;
if(newValue!=null)
newValue.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
var oldValue = evt.OldValue as ObservableCollection<ValidationErrorMessage>;
if(oldValue!=null)
oldValue.CollectionChanged -= new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
}
static void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var newItems = e.NewItems as ObservableCollection<ValidationErrorMessage>;
foreach (var item in newItems)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
public static readonly DependencyProperty headerProperty = DependencyProperty.Register("Header", typeof(String), typeof(SummaryUserControl), new PropertyMetadata(String.Empty, null));
public String Header
{
get
{
return (String)base.GetValue(headerProperty);
}
set
{
base.SetValue(headerProperty, value);
RaisePropertyChange("Header");
}
}
public static readonly DependencyProperty messageTypeProperty =
DependencyProperty.Register("MessageType", typeof(MessageEnumType), typeof(SummaryUserControl), new PropertyMetadata(MessageEnumType.Error, null));
public MessageEnumType MessageType
{
get { return (MessageEnumType)GetValue(messageTypeProperty); }
set { SetValue(messageTypeProperty, value); RaisePropertyChange("MessageType"); }
}
How can I change the values of the dependency properties messageType and Header? I'm unable to access those properties in either the CollectionChanged or NotifyPropertyChanged event since all those events are static. I cannot access the instance within these static event handlers.
I tried to fix the problem with a converter, but my curosity on Silverlight makes me want to use the above approach. How can I set values for those dependency properties within CollectionChanged event or NotifyPropertyChanged events?
The sender in your static fun1 method should be the instance of the class which declares the itemsProperty DependencyProperty. Therefore you can access the concrete instance with casting the sender to your class.
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
MyClass concreteInstance = sender as MyClass;
if(concreateInstance != null)
{
[...your code...]
}
}

Resources