WPF Standard Control Demo App › InkCanvas

InkCanvas Graphics

InkCanvas is a surface to draw on with the mouse or a pen. What is drawn is kept as strokes that can be erased, selected, and saved.

What a drag does in each editing mode

An InkCanvas collects what is drawn in Strokes, a StrokeCollection. A drag with the real mouse added one stroke.

EditingMode decides what the mouse does; the default is Ink. A left-to-right drag drew a stroke with Ink and nothing with None. Across a line, EraseByStroke removed the whole line and EraseByPoint cut it in two. With Select, a click on the line selected it. ActiveEditingMode, which is read-only, is the mode in use: during a mouse drag it was Ink, the same as EditingMode. EditingModeInverted is the mode for the inverted end of a pen; the default is EraseByStroke. The mouse does not use it: while dragging with the default, ActiveEditingMode stayed Ink.

With GestureOnly and InkAndGesture, the same left-to-right drag was recognized as the gesture Right, and no stroke remained in either mode. Recognizing gestures needs a recognizer on the machine; IsGestureRecognizerAvailable was True on the measuring machine. To keep strokes that are not meant as gestures in the InkAndGesture mode, set Cancel to True in the Gesture handler, which kept the drag as a stroke; limiting the recognized gestures with SetEnabledGestures to a circle only also kept it.

The pen, and the background behind it

DefaultDrawingAttributes is the pen for new strokes: by default black, about 2 × 2 (exactly 2.0031496062992127 in both directions), with an ellipse tip, and not a highlighter. Each stroke gets its own copy. After the pen’s Color and Width were changed in place, as the demo app does, the first stroke stayed black and about 2 wide, and the next one was red and 10 wide. Changing the default pen affects only later strokes; to recolor a stroke, change that stroke’s own DrawingAttributes.

The Background property’s own default is null, but the default style sets it to the system window color (SystemColors.WindowBrush), white on the measuring machine, so the InkCanvas is not transparent. Even with Background set to null a drag still drew a stroke: the InkCanvas takes input over its whole area, so one without a background can be laid over a picture to annotate it.

Check the pen color against the background. The demo app starts with the pen color AntiqueWhite and the background AliceBlue, whose contrast ratio is only 1.09 : 1, so pick a darker pen color before drawing.

Commands, saving, and undo

ApplicationCommands sent to the InkCanvas depend on the mode and the strokes. SelectAll could not execute in the Ink mode even with a stroke. In the Select mode it could not with no strokes, could with one, and selected it; after that, Copy could execute. Switch to Select before running SelectAll. Undo could not execute: the InkCanvas does not handle it, so keep your own history for undo.

Strokes can be saved, for example to keep a signature. Two strokes saved in ISF (Ink Serialized Format) with Save took 66 bytes and loaded back as two strokes. Strokes.Clear() removes them all.

case measured
defaults: EditingMode, EditingModeInverted, ActiveEditingMode, Background Ink, EraseByStroke, Ink, #FFFFFFFF
pen: Color; Width, Height; StylusTip, IsHighlighter; recognizer Black; 2.0031496062992127, same; Ellipse, False; True
Background after style (source) / SystemColors.WindowBrush; metadata #FFFFFFFF (Style) / #FFFFFFFF; null
demo's start: EditingMode, EditingModeInverted Ink, InkAndGesture
pen color, background; contrast ratio AntiqueWhite (#FFFAEBD7), AliceBlue (#FFF0F8FF); 1.09 : 1
Ink, real drag: strokes; ActiveEditingMode while dragging; stroke color, width 1; Ink; Black, 2
DefaultDrawingAttributes changed in place (as the demo does): 1st stroke; 2nd stroke Black, 2; Red, 10
same DrawingAttributes object: default and 1st stroke; 1st and 2nd False; False
Background null, real drag left to right: strokes; Gesture event 1; none
None, real drag left to right: strokes; Gesture event 0; none
GestureOnly, real drag left to right: strokes; Gesture event 0; Right
InkAndGesture, real drag left to right: strokes; Gesture event 0; Right
InkAndGesture, Gesture handler sets Cancel, real drag left to right: strokes; Gesture event 1; Right
InkAndGesture, SetEnabledGestures(Circle), real drag left to right: strokes; Gesture event 1; NoGesture
EraseByStroke, one line from x 50 to 250, real drag across x 150: strokes 0
EraseByPoint, one line from x 50 to 250, real drag across x 150: strokes 2 (x 49 to 146 and x 154 to 251)
Select, real click on the line: selected strokes; Copy can execute 1; True
Ink mode, SelectAll can execute: no strokes; one stroke; executed: selected False; False; 0
Select mode, SelectAll can execute: no strokes; one stroke; executed: selected False; True; 1
after SelectAll: Copy can execute; Undo can execute True; False
2 strokes saved as ISF and loaded: bytes; strokes 66; 2

Defaults, the demo’s start, drawing, modes, and commands. Measured on .NET 10 / Windows 11.

Trying it in the demo app

The InkCanvas page of the demo app, with the control list on the left and the first section, EditingMode / ActiveEditingMode / EditingModeInverted / DefaultDrawingAttributes / Strokes / Background

The InkCanvas page of the demo app has one section with EditingMode, EditingModeInverted, ActiveEditingMode, the pen color and width, Background, and Copy, Cut, Paste, Select All, and Clear All buttons. The buttons send ApplicationCommands to the InkCanvas, and Clear All calls Strokes.Clear(). The pen color AntiqueWhite and the background AliceBlue are the second item of Colors and the first of Brushes in its combo boxes, and EditingModeInverted starts at InkAndGesture, the fourth value of the enumeration, rather than the default. The “Show Code” link under the section displays its XAML. The following XAML is part of that section (InkCanvasUsageControl.xaml), with the styles, the pen settings, the other buttons, and the surrounding GroupBoxes left out and the namespace declarations added. The InkCanvas is given a height of 200 in place of the demo app’s layout. markupextensions is the prefix for the demo app’s own markup extensions:

<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:markupextensions="clr-namespace:WPFStandardControlDemoApp.Common.MarkupExtensions">
  <ComboBox x:Name="EditingModeComboBox"
            DisplayMemberPath="Name"
            ItemsSource="{markupextensions:EnumBindingSource EnumType=InkCanvasEditingMode}"
            SelectedValuePath="Value"
            SelectedIndex="1" />
  <ComboBox x:Name="EditingModeInvertedComboBox"
            DisplayMemberPath="Name"
            ItemsSource="{markupextensions:EnumBindingSource EnumType=InkCanvasEditingMode}"
            SelectedValuePath="Value"
            SelectedIndex="3" />
  <ComboBox x:Name="BackgroundBrushComboBox"
            DisplayMemberPath="Name"
            ItemsSource="{markupextensions:StaticBindingSource TargetType=Brushes}"
            SelectedValuePath="Value"
            SelectedIndex="0" />

  <Button Command="ApplicationCommands.SelectAll"
          CommandTarget="{Binding ElementName=DemoInkCanvas}"
          Content="Select All" />

  <InkCanvas x:Name="DemoInkCanvas"
             Height="200"
             Background="{Binding SelectedValue, ElementName=BackgroundBrushComboBox}"
             EditingMode="{Binding SelectedValue, ElementName=EditingModeComboBox}"
             EditingModeInverted="{Binding SelectedValue, ElementName=EditingModeInvertedComboBox}">
    <InkCanvas.DefaultDrawingAttributes>
      <DrawingAttributes Color="Black" />
    </InkCanvas.DefaultDrawingAttributes>
  </InkCanvas>
</StackPanel>

Source code and how it was measured

Every behavior on this page was measured by running it on .NET 10 / Windows 11, using InkCanvasDemoScene in this site’s screenshot tool, on an InkCanvas 300 × 200. Strokes were drawn, erased, and selected by dragging and clicking with the real mouse; a pen was not used. Copy, Cut, and Paste were not run, so that the measuring machine’s clipboard was left alone; only whether they could execute was read. The demo app’s starting values were found the same way its markup extensions list them.

View InkCanvas source code on GitHub →