dotnet / dotnet/wpf

.NET 8: "Specified element is already the logical child of another element." and "Document already belongs to another RichTextBox."

Open
#8,617 2 comments 1 reaction 0 assignees View on GitHub
Investigate
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

### Description

I've a WPF app with a couple modal dialogs for configuring settings. The first time either dialog is opened, used, and closed works fine. As of .NET 8, reopening either dialog results in a crash due to WPF XAML loading raising unhandled exceptions around resources in the dialogs already having owners.

### Reproduction Steps

Clone the develop branch of [this repo](https://github.com/CascadesCarnivoreProject/Carnassial), build, open a database (the default .tdb in the UnitTests directory can be used), and choose either of the Options -> Advanced... menu items. Close the dialog and choose the same menu item again.

### Expected behavior

Dialogs can be opened and closed repeatedly.

### Actual behavior

When a dialog is reopened the app crashes with one of the two application event log entries below.
```
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Windows.Markup.XamlParseException: Set property 'System.Windows.Controls.RichTextBox.Document' threw an exception.
---> System.ArgumentException: Document already belongs to another RichTextBox.
at System.Windows.Controls.RichTextBox.set_Document(FlowDocument value)
at MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(Object inst, XamlMember property, Object value)
--- End of inner exception stack trace ---
at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri)
at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri)
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.Controls.MenuItem.InvokeClickAfterRender(Object arg)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
```
```
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Windows.Markup.XamlParseException: Add value to collection of type 'System.Windows.Controls.UIElementCollection' threw an exception.
---> System.InvalidOperationException: Specified element is already the logical child of another element. Disconnect it first.
at System.Windows.FrameworkElement.ChangeLogicalParent(DependencyObject newParent)
at System.Windows.FrameworkElement.AddLogicalChild(Object child)
at System.Windows.Controls.UIElementCollection.AddInternal(UIElement element)
at System.Xaml.Schema.XamlTypeInvoker.AddToCollection(Object instance, Object item)
at MS.Internal.Xaml.Runtime.ClrObjectRuntime.Add(Object collection, XamlType collectionType, Object value, XamlType valueXamlType)
--- End of inner exception stack trace ---
at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri)
at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri)
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.Controls.MenuItem.InvokeClickAfterRender(Object arg)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at Carnassial.App.Main()
```

### Regression?

This is at least technically a regression as both code paths were reliable in .NET 4.5. The app's since been updated through 4.8, 5, 6, 7, and 8 but, as those were unreleased builds, it's likely neither dialog was opened repeatedly on any of the versions between 4.5 and 8.

### Known Workarounds

Effectively none (users aren't going to exit and restart an app each time they want to open a dialog again).

I'm guessing structural updates to the XAML may be needed but, having searched for changes around the affected `StaticResource`s which might be required when updating from WPF 4.5 to 8, I've failed to come up with relevant hits. The [current documentation](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/staticresource-markup-extension?view=netframeworkdesktop-4.8) appears to be for .NET 4.8 and doesn't discuss ownership release when including static resources in dialogs.

### Impact

Unexpected process exit. User loses any work not caught by the last autosave, autosave likely to be corrupted if exit happens to occur during a SQL backup transaction.

### Configuration

- .NET Version: 8.0.0 (CoreCLR Version: 8.0.23.53103)
- Windows 10 22H2
- x64 (Zen 3)
- App only supports Win10+ on x64.

### Other information

Both dialogs use
```XAML

```
```C#
protected void CloseWindow_Execute(object sender, ExecutedRoutedEventArgs e)
{
SystemCommands.CloseWindow(this);
}
```
for close button handling as well as
```C#
private void OkButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
```
when their OK buttons are pressed. WPF raises the same exception regardless of which pathway is used to close a dialog.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.