.net – Workaround for WPF Freezable bug

.net-4.0freezablenetwpf

I've been hitting a really bad WPF bug recently. I think it's the same as this bug on Microsoft Connect.

Our application targets .NET 4.0 Client Profile using Visual Studio 2010.

Basically, when a ViewModel triggers a change to any property or collection that causes items to move around in an ItemsControl, there's a chance that the exception below will be thrown. It doesn't always happen and seems to happen based on different triggers different times. It seems to be more likely shortly after starting the application. If you can use it for a couple minutes without hitting the exception, you'll probably never hit during that application instance.

Like the Connect bug report, I'm using {DynamicResource key} to load the SolidColorBrushes from a ResourceDictionary. Some of the dictionaries are manually loaded (for theming support). I tried manually freeze everything in those dictionaries, but it doesn't seem to have helped.

The exceptions have gotten much more frequent lately when I added a couple more UserControls to the main window that have ItemsControls in them bound to ObservableCollections. Previously, I'd only see the exception 1 time out of 50, but now I see it 4 out of 5 times I use the program.

Does anyone have any ideas for workarounds? The Connect bug indicates that this will probably be fixed in the next .NET release (whenever that is) but this bug is making our application basically unusable now.

    System.InvalidOperationException: Specified value of type 'System.Windows.Media.SolidColorBrush' must have IsFrozen set to false to modify.
       at System.Windows.Freezable.WritePreamble()
       at System.Windows.Freezable.remove_Changed(EventHandler value)
       at System.Windows.ResourceReferenceExpression.ResourceReferenceExpressionWeakContainer.RemoveChangedHandler()
       at System.Windows.ResourceReferenceExpression.ResourceReferenceExpressionWeakContainer.InvalidateTargetSubProperty(Object sender, EventArgs args)
       at System.Windows.Freezable.FireChanged()
       at System.Windows.Freezable.Freeze(Boolean isChecking)
       at System.Windows.Freezable.Freeze()
       at System.Windows.Freezable.System.Windows.ISealable.Seal()
       at System.Windows.StyleHelper.SealIfSealable(Object value)
       at System.Windows.StyleHelper.GetChildValueHelper(UncommonField`1 dataField, ItemStructList`1& valueLookupList, DependencyProperty dp, DependencyObject container, FrameworkObject child, Int32 childIndex, Boolean styleLookup, EffectiveValueEntry& entry, ValueLookupType& sourceType, FrameworkElementFactory templateRoot)
       at System.Windows.StyleHelper.GetChildValue(UncommonField`1 dataField, DependencyObject container, Int32 childIndex, FrameworkObject child, DependencyProperty dp, FrugalStructList`1& childRecordFromChildIndex, EffectiveValueEntry& entry, ValueLookupType& sourceType, FrameworkElementFactory templateRoot)
       at System.Windows.StyleHelper.GetValueFromTemplatedParent(DependencyObject container, Int32 childIndex, FrameworkObject child, DependencyProperty dp, FrugalStructList`1& childRecordFromChildIndex, FrameworkElementFactory templateRoot, EffectiveValueEntry& entry)
       at System.Windows.FrameworkElement.GetValueFromTemplatedParent(DependencyProperty dp, EffectiveValueEntry& entry)
       at System.Windows.FrameworkElement.GetRawValue(DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry& entry)
       at System.Windows.FrameworkElement.EvaluateBaseValueCore(DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry& newEntry)
       at System.Windows.DependencyObject.EvaluateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry newEntry, OperationType operationType)
       at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
       at System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp)
       at System.Windows.StyleHelper.InvalidateResourceDependentsForChild(DependencyObject container, DependencyObject child, Int32 childIndex, ResourcesChangeInfo info, FrameworkTemplate parentTemplate)
       at System.Windows.TreeWalkHelper.InvalidateStyleAndReferences(DependencyObject d, ResourcesChangeInfo info, Boolean containsTypeOfKey)
       at System.Windows.TreeWalkHelper.OnResourcesChanged(DependencyObject d, ResourcesChangeInfo info, Boolean raiseResourceChangedEvent)
       at System.Windows.FrameworkElement.OnAncestorChangedInternal(TreeChangeInfo parentTreeState)
       at System.Windows.TreeWalkHelper.OnAncestorChanged(DependencyObject d, TreeChangeInfo info)
       at System.Windows.DescendentsWalker`1._VisitNode(DependencyObject d)
       at MS.Internal.PrePostDescendentsWalker`1._VisitNode(DependencyObject d)
       at System.Windows.DescendentsWalker`1.VisitNode(FrameworkElement fe)
       at System.Windows.DescendentsWalker`1.VisitNode(DependencyObject d)
       at System.Windows.DescendentsWalker`1.WalkFrameworkElementLogicalThenVisualChildren(FrameworkElement feParent, Boolean hasLogicalChildren)
       at System.Windows.DescendentsWalker`1.IterateChildren(DependencyObject d)
       at System.Windows.DescendentsWalker`1.StartWalk(DependencyObject startNode, Boolean skipStartNode)
       at MS.Internal.PrePostDescendentsWalker`1.StartWalk(DependencyObject startNode, Boolean skipStartNode)
       at System.Windows.TreeWalkHelper.InvalidateOnTreeChange(FrameworkElement fe, FrameworkContentElement fce, DependencyObject parent, Boolean isAddOperation)
       at System.Windows.FrameworkElement.OnVisualParentChanged(DependencyObject oldParent)
       at System.Windows.Media.Visual.FireOnVisualParentChanged(DependencyObject oldParent)
       at System.Windows.Media.Visual.RemoveVisualChild(Visual child)
       at System.Windows.Media.VisualCollection.DisconnectChild(Int32 index)
       at System.Windows.Media.VisualCollection.Clear()
       at System.Windows.Controls.UIElementCollection.ClearInternal()
       at System.Windows.Controls.Panel.ClearChildren()
       at System.Windows.Controls.Panel.OnItemsChangedInternal(Object sender, ItemsChangedEventArgs args)
       at System.Windows.Controls.Panel.OnItemsChanged(Object sender, ItemsChangedEventArgs args)
       at System.Windows.Controls.ItemContainerGenerator.OnRefresh()
       at System.Windows.Controls.ItemContainerGenerator.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
       at System.Windows.Controls.ItemContainerGenerator.System.Windows.IWeakEventListener.ReceiveWeakEvent(Type managerType, Object sender, EventArgs e)
       at System.Windows.WeakEventManager.DeliverEventToList(Object sender, EventArgs args, ListenerList list)
       at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args)
       at System.Collections.Specialized.CollectionChangedEventManager.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
       at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
       at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
       at System.Windows.Controls.ItemCollection.System.Windows.IWeakEventListener.ReceiveWeakEvent(Type managerType, Object sender, EventArgs e)
       at System.Windows.WeakEventManager.DeliverEventToList(Object sender, EventArgs args, ListenerList list)
       at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args)
       at System.Collections.Specialized.CollectionChangedEventManager.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
       at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
       at System.Windows.Data.ListCollectionView.RefreshOverride()
       at System.Windows.Data.CollectionView.Refresh()
       at System.Windows.Data.CollectionView.EndDefer()
       at System.Windows.Data.CollectionView.DeferHelper.Dispose()
       at System.Windows.Controls.ItemCollection.SetCollectionView(CollectionView view)
       at System.Windows.Controls.ItemCollection.SetItemsSource(IEnumerable value)
       at System.Windows.Controls.ItemsControl.OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
       at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
       at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
       at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
       at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
       at System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp)
       at System.Windows.Data.BindingExpressionBase.Invalidate(Boolean isASubPropertyChange)
       at System.Windows.Data.BindingExpression.TransferValue(Object newValue, Boolean isASubPropertyChange)
       at System.Windows.Data.BindingExpression.ScheduleTransfer(Boolean isASubPropertyChange)
       at MS.Internal.Data.ClrBindingWorker.NewValueAvailable(Boolean dependencySourcesChanged, Boolean initialValue, Boolean isASubPropertyChange)
       at MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(Int32 k, ICollectionView collectionView, Object newValue, Boolean isASubPropertyChange)
       at MS.Internal.Data.ClrBindingWorker.OnSourcePropertyChanged(Object o, String propName)
       at MS.Internal.Data.PropertyPathWorker.System.Windows.IWeakEventListener.ReceiveWeakEvent(Type managerType, Object sender, EventArgs e)
       at System.Windows.WeakEventManager.DeliverEventToList(Object sender, EventArgs args, ListenerList list)
       at System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(Object sender, PropertyChangedEventArgs args)
       at ***.ViewModelBase.OnPropertyChanged(String name) in c:\***\ViewModelBase.cs:line 17
    .....

EDIT:
We've also tried simply suppressing any InvalidOperationExceptions thrown inside of our ViewModel base class's PropertyChanged event. That seemed to reduce the number of exceptions somewhat, but now we just hit them in ObservableCollection's CollectionChanged event.

Best Answer

To workaround this .net bug, change all of the Solid Color Brushes in your code to be freezeable. For example

<SolidColorBrush x:Key="WindowBackground" Color="Black" />

should be changed to:

<SolidColorBrush po:Freeze="True" x:Key="WindowBackground" Color="Black" />. 

For more detailed instructions see here: How can WPF objects deriving from Freezable be frozen in XAML?.

Related Topic