I have a xamDiagram whose diagram items are populated from a view model using TwoWay data binding, as per the XamDiagram documentation.
For the most part, this works as intended, in that I can modify the diagram item objects in the view model or their respective objects in the diagram, and the TwoWay binding ensures that changes save in both directions. However, there is a really strange case in which the data binding will fail when updating the data in the view model and throw an exception when calling NotifyPropertyChanged on the object.
Note: My data objects in the view model contain most of the same properties that exist on the DiagramItem/Node/Connections in the diagram (e.g., Fill, Position, Stroke, Width, Height, ShapeType)
The exception generally looks like this:
Exception thrown: 'System.Collections.Generic.KeyNotFoundException' in mscorlib.dllException thrown: 'System.Reflection.TargetInvocationException' in mscorlib.dllSystem.Windows.Data Error: 8 : Cannot save value from target back to source. BindingExpression:Path=Content.Position; DataItem='DiagramNode' (Name=''); target element is 'DiagramNode' (Name=''); target property is 'Position' (type 'Point') KeyNotFoundException:'System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary. at System.Collections.Generic.Dictionary`2.get_Item(TKey key) at Infragistics.Controls.Charts.XamDiagram.PropertyChangedWeakCollectionChanged(IFastItemsSource fastItemsSource, Object sender, PropertyChangedEventArgs args) at Infragistics.ItemSourceEventProxy.propertyChanged(Object sender, PropertyChangedEventArgs e) at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e) at GalaSoft.MvvmLight.ObservableObject.RaisePropertyChanged(String propertyName) at Sandbox.Custom.SandboxSketchDiagram.Items.SketchDiagramNode.set_Position(Point value) in C:\Users\afink\Sandbox\Sandbox\Custom\SandboxSketchDiagram\Items\SketchDiagramNode.cs:line 26'
Exception thrown: 'System.Collections.Generic.KeyNotFoundException' in mscorlib.dll
Exception thrown: 'System.Reflection.TargetInvocationException' in mscorlib.dll
System.Windows.Data Error: 8 : Cannot save value from target back to source. BindingExpression:Path=Content.Position; DataItem='DiagramNode' (Name=''); target element is 'DiagramNode' (Name=''); target property is 'Position' (type 'Point') KeyNotFoundException:'System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at Infragistics.Controls.Charts.XamDiagram.PropertyChangedWeakCollectionChanged(IFastItemsSource fastItemsSource, Object sender, PropertyChangedEventArgs args)
at Infragistics.ItemSourceEventProxy.propertyChanged(Object sender, PropertyChangedEventArgs e)
at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)
at GalaSoft.MvvmLight.ObservableObject.RaisePropertyChanged(String propertyName)
at Sandbox.Custom.SandboxSketchDiagram.Items.SketchDiagramNode.set_Position(Point value) in C:\Users\afink\Sandbox\Sandbox\Custom\SandboxSketchDiagram\Items\SketchDiagramNode.cs:line 26'
where SketchDiagramNode.cs:line 26 is where NotifyPropertyChanged() is being executed in the object being updated in the view model. Again, this only happens occasionally.
SketchDiagramNode.cs:line 26
Also, here is what the data binding looks like for the items in the ItemsSource collection in my diagram:
<Style x:Key="DiagramItemStyle" TargetType="ig:DiagramItem"> <Setter Property="Fill" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Fill, Mode=TwoWay}" /> <Setter Property="Visibility" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Visibility, Mode=TwoWay}" /> <Setter Property="IsEnabled" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Visibility, Converter={StaticResource ResourceKey=VisibilityToBoolConverter}}" /> <Setter Property="Stroke" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Stroke, Mode=TwoWay}" /> <Setter Property="StrokeThickness" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.StrokeThickness, Mode=TwoWay}" /> <Setter Property="StrokeDashArray" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.StrokeDashArray, Mode=TwoWay}" /> <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Foreground, Mode=TwoWay}" /> <Setter Property="FontFamily" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.FontFamily, Mode=TwoWay}" /> <Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.FontSize, Mode=TwoWay}" /> <Setter Property="FontStyle" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.FontStyle, Mode=TwoWay}" /> <Setter Property="FontWeight" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.FontWeight, Mode=TwoWay}" /> <Setter Property="Opacity" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Opacity, Mode=TwoWay}" /> <Setter Property="ZIndex" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.ZIndex, Mode=TwoWay}" /> </Style> <Style x:Key="DiagramNodeStyle" TargetType="ig:DiagramNode" BasedOn="{StaticResource ResourceKey=DiagramItemStyle}"> <Setter Property="Position" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Position, Mode=TwoWay}" /> <Setter Property="ShapeType" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.ShapeType, Mode=TwoWay}" /> <Setter Property="Width" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Width, Mode=TwoWay}" /> <Setter Property="Height" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Height, Mode=TwoWay}" /> <Setter Property="MaintainAspectRatio" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.MaintainAspectRatio, Mode=TwoWay}" /> </Style>
I have tried to create a minimal reproducible example project to recreate the error, but I have yet to succeed in reproducing the error on a new test project, so I still don't know what is causing it or what I may be doing wrong.
I know this could be pretty difficult to diagnose without any kind of example code to reproduce it, but I was wondering if you guys knew of anything that I could be generally doing wrong when using two-way binding on the ItemsSource collection in the xamDiagram that could cause this exception to be thrown?
Hello Andrew,
Thank you for your update on this matter.
I am glad that downloading the source code and symbols helped you to discover the issue that was causing these issues and that it sounds like you were able to resolve them.
Please let me know if you have any other questions or concerns on this matter.
Thanks for the recommendation to download the Infragistics symbols and source code! Being able to step into the Infragisitics code was very helpful, and after three days of headaches, I have finally tracked down the issue.
My data object class (SketchDiagramItem) had overridden Equals and GetHashCode methods because I had been using that for testing for equality in a couple places.
When the PropertyChanged event on my SketchDiagramNode is raised, the Infragistics.Controls.Charts.XamDiagram.PropertyChangedWeakCollectionChanged event handler would get executed, and it would find the target object's corresponding DiagramNode in the xamDiagram by accessing it in the XamDiagram._createdNodes dictionary, which is of type Dictionary<SketchDiagramNode, DiagramNode>. So it retrieves the corresponding DiagramNode using the target object as the key.
And since I had overridden the GetHashCode method on the target object, which is what the dictionary used for comparision, there must have been something wrong with the overridden GetHashCode method that caused the retrieval to throw a KeyNotFound exception because the comparison didn't happen successfully.
I never figured out what was wrong with the overridden GetHashCode method, but when I removed it and let it use the default comparison by object reference, the exception went away and everything worked fine.
And naturally, the reason I was unable to reproduce the error in the test project was because I didn't copy over the overridden GetHashCode and Equals methods because I wasn't explicitly using them in the test project. I didn't think about the fact that other parts of the XamDiagram might be using them for comparison.
Thank you for confirming that the exception that is happening is a handled exception.
Since this exception is handled, it is expected that you will see the UI get somewhat on the choppy side when it is thrown and handled internally. This is more caused by the .NET debugger than anything else though, and if you run without debugging, this choppiness should not occur.
Regarding a way to determine why this specific exception is being thrown, I would agree with you that it does sound like it is related to a property not existing on the target object when passed to the event arguments, but I too am unsure why this would be happening, and I would need a reproducible scenario that I could run locally to truly be able to try to explain it.
Since you do have a scenario local to your end that can reproduce the issue, perhaps something you could do in this case is download the Infragistics source code for the version you are using. As long as you are registered to the Infragistics product and have an active subscription, you can do this by signing into your account on the Infragistics website and clicking on the 19.2 version you are registered to on your My Keys and Downloads page. This will open a new set of tabs where you can download the source code and symbols for the WPF product and run against them if you would like to see the exact code-path that it is taking and the variables that are being passed to cause this exception.
Hello,
Now that you mention it, it is a handled exception. It does not break the application, but instead logs the exception in the Output window. As it is a handled exception, the larger issue is that when the exception is thrown, the application takes a performance hit.
Based on the test project I sent you, which has the diagram toolbox on the left column, the diagram in the center column, and a properties editor on the right column, if you select a diagram node in the diagram, it will populate the properties editor with that diagram item's properties. Then if you change, for example, the Fill color using the properties editor, then attempt to drag the node around in the diagram, it starts to throw the aforementioned (handled) exception, and as you drag it, the application takes a pretty substantial performance hit, it the UI gets pretty choppy. And the performance is only impacted for that single diagram node. Assuming that you haven't changed any properties on the other nodes in the diagram, you can drag them around and/or change their position without any impact on performance.
However, this behavior is only occurring on my full application. I have been trying to recreate the error in the test project, but no luck yet.
In the meantime, here is a bit more info about the exception being thrown:
I am able to catch the exception when I add a try/catch block (so I assume it's getting handled somewhere further up in the call stack), and it occurs when calling RaisePropertyChanged in the SketchDiagramNode Position setter (but it also throws the exception in the setter for most of the other properties on SketchDiagramNode and its base class, SketchDiagramItem).
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) { try { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } catch(KeyNotFoundException e) { } }
And every time, it's the same exception:
KeyNotFoundException:'System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary. at System.Collections.Generic.Dictionary`2.get_Item(TKey key) at Infragistics.Controls.Charts.XamDiagram.PropertyChangedWeakCollectionChanged(IFastItemsSource fastItemsSource, Object sender, PropertyChangedEventArgs args) at Infragistics.ItemSourceEventProxy.propertyChanged(Object sender, PropertyChangedEventArgs e) at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)
Is there any way for me to determine why this specific exception might be thrown? It sounds as if the property that is being passed to the event args does not exist on the target object, but I don't see how that could happen, as I'm using the CallerMemberName attribute to obtain the property name.
I'm not sure what is going on in the 2nd and 3rd lines of the stack trace:
at Infragistics.Controls.Charts.XamDiagram.PropertyChangedWeakCollectionChanged(IFastItemsSource fastItemsSource, Object sender, PropertyChangedEventArgs args) at Infragistics.ItemSourceEventProxy.propertyChanged(Object sender, PropertyChangedEventArgs e)
This is about as much information as I've been able to get about this exception without being able to reproduce it in the test project. I know there's the possibility that the error is something specific to my full application (I will continue trying to determine whether that's the case), but based on the exception I'm getting, do you think it's possible that the error could be coming from something related to Infragistics, or do you think it's safe to rule out the Infragistics-related code as the culprit?
Thank you for your update on this matter and the sample project.
I have been investigating the sample project you have provided, and I see nothing wrong with the way you are doing things in it. Using your sample project, I have run it a few times, creating a diagram with multiple nodes and connections, but I cannot seem to reproduce any exception that is breaking the application.
Of the 5 – 10 times I have tried this, there was a single time when running the application that I did see a handled exception in the Output window of Visual Studio, when erratically dragging a node in the diagram, but I cannot reliably reproduce this by any means. I also tried updating the sample to the latest version of Infragistics for WPF 2020.1, since version 2019.2’s development period has expired, and I was not able to reproduce this either.
This leads me to wonder if the exception you are seeing is a handled one and you are just breaking on it, or if this is actually an unhandled, application breaking exception. A good way to test this would be to know if you can reproduce this behavior when running your full application without a debugger? If not, this very likely points to the exception you are seeing likely being a handled exception.