Description
A possible regression appears to have been introduced after the changes in PR dotnet/wpf#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
- Create an
InkCanvas.
- Set the InkCanvas EditingMode set to Select)
- Add a child
ContentControl with DataTemplate (e.g., a TextBlock, Image, or custom control).
- Select the child element.
- Call
InkCanvas.CopySelection().
- Inspect clipboard data via
Clipboard.GetText(TextDataFormat.Xaml).
MainWindow.xaml
<Window x:Class="InkCanvasTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:InkCanvasTest"
mc:Ignorable="d"
Title="MainWindow" Height="480" Width="720">
<Window.Resources>
<Style x:Key="NoteTextItemTextBoxWithCustomCaretStyle" TargetType="{x:Type local:NoteTextItemTextBoxWithCustomCaret}">
<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>
</Style>
<DataTemplate DataType="{x:Type local:TextItem}">
<local:NoteTextItemTextBoxWithCustomCaret Style="{StaticResource NoteTextItemTextBoxWithCustomCaretStyle}"
Width="200" Height="80"
InkCanvas.Left="50" InkCanvas.Top="50"
Text="{Binding TextVal}" />
</DataTemplate>
</Window.Resources>
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="150"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,8">
<Button Name="CopyButton" Click="CopyButton_Click" Margin="0,0,8,0">CopySelection</Button>
<Button Name="ShowClipboardButton" Click="ShowClipboardButton_Click" Margin="0,0,8,0">Show Clipboard XAML</Button>
<Button Name="AddChildButton" Click="AddChildButton_Click" Margin="0,0,8,0">Add ContentControl Child</Button>
<TextBlock VerticalAlignment="Center" Foreground="Gray"> — Select a child by clicking it (InkCanvas in Select mode)</TextBlock>
</StackPanel>
<InkCanvas Name="inkCanvas"
Grid.Row="1"
Background="#FFF2F2F2"
EditingMode="Select"
Margin="0,0,0,8">
</InkCanvas>
<TextBox Name="clipboardTextBox"
Grid.Row="2"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
FontFamily="Consolas"
IsReadOnly="True"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace InkCanvasTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
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 = "<Clipboard is empty>";
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 ?? "<null>";
}
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)?
Other information
No response
Description
A possible regression appears to have been introduced after the changes in PR dotnet/wpf#11797.
When copying a selected child element in an
InkCanvas—specifically a non‑stroke item such as aContentControl—the clipboard XAML no longer contains the element’s content.Previously,
InkCanvas.CopySelection()produced clipboard data containing:InkCanvasContentControlContentControl.Content(expected)After recent Windows updates containing this fix, the clipboard now contains:
InkCanvasContentControlwith empty contentThis 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
InkCanvas.ContentControlwith DataTemplate (e.g., aTextBlock,Image, or custom control).InkCanvas.CopySelection().Clipboard.GetText(TextDataFormat.Xaml).MainWindow.xamlMainWindow.xaml.csExpected behavior
Clipboard XAML should include the selected
ContentControlwith its content preservedActual behavior
Clipboard XAML contains an empty
ContentControlwith 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
InkCanvas.CopySelection()normally.InkCanvas.XamlWriter.Save.Childrencollection.InkCanvasand 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 fromInkCanvas(clipboard-based copy/paste, inter-app copy/paste).Configuration
Which version of .NET is the code running on?
What OS and version, and what distro if applicable?
What is the architecture (x64, x86, ARM, ARM64)?
Other information
No response