Problem description:
Multibinding converter running on UIElements that have been deleted. I have an ItemsControl bound to an ObservableCollection and when it is cleared, converters are still executing the convert method.
Actual behavior:
I get an exception from one of the converters due to it running on a "phantom" element. The exception is one I am throwing but this only is thrown as a guard in the actual project. It should not be thrown after the observable collection is cleared because there should be nothing there for it to be checking.
Expected behavior:
Converters do not run on objects that no longer exist.
Minimal repro:
https://github.com/DanJBower/ConverterBug
To get the error, click Clear then Resize.
Expected behavior:
Converters do not run on objects that no longer exist.
That is the actual behaviour. The objects exist until they are collected by GC, i.e. if you call GC.Collect before clicking Resize, no exception is thrown.
Your code should be able to deal with elements outside of the tree - that can happen at any time, including common virtualization.
Ah
Thanks for pointing that out 馃檪
@miloush A user on stack overflow left a comment on my question about the same issue SO link.
In this case, the error cannot be caused by the garbage collector. XAML uses Binding - it uses weak references. 小onverter-CanvasPositionScaleConverter can only be called by an object from the Squares collection. Objects removed from this collection cannot in any way call the CanvasPositionScaleConverter
Are you sure the garbage collection is definitely the cause of this issue? Is there any way to validate this with the debugger?
I also tried updating the Clear method to use GC.Collect to see if it fixed it as you said, but on both my laptop and Desktop PC it still throws the error on both machines.
private void Clear(object sender, RoutedEventArgs e)
{
Squares.Clear();
GC.Collect();
}
If it is not the garbage collector, do you know what else it could be?
That is too soon, WPF wouldn't get a chance to sort out the tree changes yet. When I tested it, I put the GC.Collect on a third button. You can also confirm that by running it after the work is done:
private void Clear(object sender, RoutedEventArgs e)
{
Squares.Clear();
Dispatcher.BeginInvoke(new Action(GC.Collect), DispatcherPriority.ContextIdle);
}
As regards to the weak references argument, I don't see how that is relevant. WR ensures that the objects _can_ be collected, it does not cause them to be collected neither does it magically unsubscribe from the events when the object becomes unrooted (how would it know).
@miloush Thanks for the help.
It makes a lot more sense now and I took your advice of updating the converter so it can handle invalid inputs gracefully.