InkCanvas.CopySelection returns empty ContentControl XAML when copying non‑stroke child elements
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
### Description
A possible regression appears to have been introduced after the changes in PR [dotnet/wpf#11797](https://github.com/dotnet/wpf/pull/11797).
When copying a selected child element in an `InkCanvas`—specifically a non‑stroke item such as a `ContentControl`—the clipboard XAML no longer contains the element’s content.
Previously, `InkCanvas.CopySelection()` produced clipboard data containing:
- The `InkCanvas`
- The selected `ContentControl`
- The `ContentControl.Content` (expected)
After recent Windows updates containing this fix, the clipboard now contains:
- The `InkCanvas`
- A `ContentControl` with empty content
This breaks scenarios where applications rely on copying embedded UI elements inside an `InkCanvas`.
In NET 10 Runtime 10.0.5, the CopySelection succeeds and the clipboard XAML contains the ContentControl with its Content preserved.
### Reproduction Steps
1. Create an `InkCanvas`.
2. Set the InkCanvas EditingMode set to Select)
3. Add a child `ContentControl` with DataTemplate (e.g., a `TextBlock`, `Image`, or custom control).
4. Select the child element.
5. Call `InkCanvas.CopySelection()`.
6. Inspect clipboard data via `Clipboard.GetText(TextDataFormat.Xaml)`.
`MainWindow.xaml`
```
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NoteTextItemTextBoxWithCustomCaret}">
<Grid Background="RosyBrown" >
<TextBox x:Name="PART_TextBox"
Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent"
BorderThickness="0"
Padding="2"/>
<Canvas x:Name="PART_Canvas" IsHitTestVisible="False">
<Border x:Name="PART_BorderCaret" Background="Black" Width="1" Height="16" Visibility="Collapsed" />
</Canvas>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
CopySelection
Show Clipboard XAML
Add ContentControl Child
— Select a child by clicking it (InkCanvas in Select mode)
```
`MainWindow.xaml.cs`
```
using System.Windows;
using System.Windows.Controls;
namespace InkCanvasTest
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
//public TextItem _TextItem;
public MainWindow()
{
InitializeComponent();
var _TextItem = new TextItem();
_TextItem.TextVal = "Hello, custom caret!";
var contentControl = new ContentControl { Content = _TextItem };
InkCanvas.SetLeft(contentControl, 50);
InkCanvas.SetTop(contentControl, 50);
inkCanvas.Children.Add(contentControl);
}
private void CopyButton_Click(object sender, RoutedEventArgs e)
{
// Call CopySelection — user should have selected a child (click it) before pressing this.
try
{
inkCanvas.CopySelection();
MessageBox.Show("CopySelection called. Use 'Show Clipboard XAML' to inspect clipboard XAML.");
string content = Clipboard.GetText(TextDataFormat.Xaml);
MessageBox.Show("CopySelection threw: " + content);
}
catch (Exception ex)
{
MessageBox.Show("CopySelection threw: " + ex);
}
}
private void ShowClipboardButton_Click(object sender, RoutedEventArgs e)
{
try
{
var data = Clipboard.GetDataObject();
if (data == null)
{
clipboardTextBox.Text = "";
return;
}
// Prefer DataFormats.Xaml if present, otherwise show available formats
if (data.GetDataPresent(DataFormats.Xaml))
{
//var xaml = data.GetData(DataFormats.Xaml) as string;
var xaml = data.GetData(DataFormats.Xaml) as string;
clipboardTextBox.Text = xaml ?? "";
}
else if (data.GetDataPresent(DataFormats.XamlPackage))
{
// XamlPackage is a binary package; show the available formats and note it's present
clipboardTextBox.Text = $"Clipboard contains {DataFormats.XamlPackage} (non-string data). Available formats:\r\n{string.Join(", ", data.GetFormats())}";
}
else
{
clipboardTextBox.Text = "Clipboard does not contain XAML format. Available formats:\r\n" + string.Join(", ", data.GetFormats());
}
}
catch (Exception ex)
{
clipboardTextBox.Text = ex.ToString();
}
}
private void AddChildButton_Click(object sender, RoutedEventArgs e)
{
var _TextItem = new TextItem();
_TextItem.TextVal = "Custom Caret Added child " + DateTime.Now.ToLongTimeString();
var cc = new ContentControl { Width = 160, Height = 80 };
cc.Content = _TextItem;
cc.BorderBrush = System.Windows.Media.Brushes.Black;
cc.BorderThickness = new Thickness(1);
InkCanvas.SetLeft(cc, 240);
InkCanvas.SetTop(cc, 50);
inkCanvas.Children.Add(cc);
}
}
public class NoteTextItemTextBoxWithCustomCaret : Control
{
private Canvas? canvas;
private Border? borderCaret;
private TextBox? innerTextBox;
public NoteTextItemTextBoxWithCustomCaret()
{
this.Focusable = true;
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
nameof(Text),
typeof(string),
typeof(NoteTextItemTextBoxWithCustomCaret),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
static NoteTextItemTextBoxWithCustomCaret()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NoteTextItemTextBoxWithCustomCaret),
new FrameworkPropertyMetadata(typeof(NoteTextItemTextBoxWithCustomCaret)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
canvas = GetTemplateChild("PART_Canvas") as Canvas;
innerTextBox = GetTemplateChild("PART_TextBox") as TextBox;
borderCaret = GetTemplateChild("PART_BorderCaret") as Border;
if (innerTextBox != null)
{
// Ensure the TextBox text is bound to the control's Text property via template binding,
// but guard here that we still have the reference to attach handlers.
innerTextBox.SelectionChanged += InnerTextBox_SelectionChanged;
innerTextBox.SizeChanged += InnerTextBox_SizeChanged;
innerTextBox.LostFocus += (s, o) =>
{
if (borderCaret != null) borderCaret.Visibility = Visibility.Collapsed;
};
innerTextBox.GotFocus += (s, o) =>
{
if (borderCaret != null) borderCaret.Visibility = Visibility.Visible;
};
}
if (borderCaret != null)
{
borderCaret.Width = 1;
borderCaret.Visibility = Visibility.Collapsed;
}
}
private void InnerTextBox_SizeChanged(object? sender, SizeChangedEventArgs e)
{
if (innerTextBox == null || canvas == null || borderCaret == null)
return;
canvas.Width = innerTextBox.Width;
canvas.Height = innerTextBox.Height;
UpdateCaretPosition();
}
private void InnerTextBox_SelectionChanged(object? sender, RoutedEventArgs e)
{
UpdateCaretPosition();
}
private void UpdateCaretPosition()
{
if (innerTextBox == null || canvas == null || borderCaret == null)
return;
try
{
Rect caretRect = innerTextBox.GetRectFromCharacterIndex(innerTextBox.CaretIndex);
if (!double.IsInfinity(caretRect.X) && caretRect.Right < innerTextBox.ActualWidth)
{
Canvas.SetLeft(borderCaret, caretRect.X);
}
else if (caretRect.Right > innerTextBox.ActualWidth)
{
Canvas.SetLeft(borderCaret, Math.Max(0, innerTextBox.ActualWidth - borderCaret.Width));
}
if (!double.IsInfinity(caretRect.Y))
{
Canvas.SetTop(borderCaret, caretRect.Y);
}
}
catch
{
// GetRectFromCharacterIndex can throw if layout not ready — swallow here.
}
}
}
public class TextItem
{
public string TextVal { get; set; }
}
}
```
### Expected behavior
Clipboard XAML should include the selected `ContentControl` with its content preserved
### Actual behavior
Clipboard XAML contains an empty `ContentControl` with no child content.
### Regression?
It work previously on .NET Framework 4.6.2 before August Updates. and Dot NET 10 Runtime 10.0.5
### Known Workarounds
- Call `InkCanvas.CopySelection()` normally.
- Retrieve the clipboard XAML and deserialize it into an `InkCanvas`.
- Serialize the selected child element using `XamlWriter.Save`.
- Deserialize the serialized child and add it to the InkCanvas’s `Children` collection.
- Serialize the corrected `InkCanvas` and write it back to the clipboard.
### Impact
This can affect copy/paste workflows that expect visual children (e.g., `ContentControls`) to preserve their content. Impact: user-visible regression for any scenario that copies non-stroke children from `InkCanvas` (clipboard-based copy/paste, inter-app copy/paste).
### Configuration
Which version of .NET is the code running on?
- .NET Framework 4.6.2 with August 2026 Updates and Dot NET 10.0.303
What OS and version, and what distro if applicable?
- Windows 11 25H2 (OS Build 26200.9168)
What is the architecture (x64, x86, ARM, ARM64)?
- x86
### Other information
_No response_
Contributor guide
Research direction
Start with the InkCanvas.CopySelection() behavior described in the reproduction, using the provided MainWindow.xaml and MainWindow.xaml.cs sample. Run the sample, select the ContentControl child, copy it, and inspect Clipboard.GetText(TextDataFormat.Xaml). Done means the clipboard XAML preserves the selected ContentControl's content, including templated content.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100