PowerShell and WPF: Ink Canvas

Today’s PowerShell and WPF topic is all about the Ink Canvas, which is a WPF control that gives you the opportunity to draw on it, just like MS Paint! There really isn’t a lot to this control and we can quickly provision it in a short amount of time. Of course, adding some fun features like changing the color of the pen and using the eraser and highlighter will require a little more work in order for it to work properly.

With that out of the way, let’s get started on building the ink canvas to use! If you have Visual Studio available, then you can quickly put together a UI and then copy the XAML code and merge it into a PowerShell script. In my case, I am building the XAML manually because there really isn’t a lot of add for this example.

[xml]$xaml = @"
<Window 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window" WindowStyle = 'ToolWindow'
ShowInTaskbar = "True" >    
<InkCanvas x:Name="InkCanvas">
</InkCanvas>
</Window>
"@

As I mentioned, this is a basic control with pretty much no options set. I’ve cast the here-string as XML and will now take this and open it up using XMLNodeReader and then connect to the Window control.

$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

I only have to connect to a single control here, which happens to be the InkCanvas itself.

#region Connect to Controls
$InkCanvas = $Window.FindName('InkCanvas')
#endregion Connect to Controls

Now if I just show the window, we can instantly start drawing in it.

[void]$Window.ShowDialog()

image

Changing the Ink Color and Size

By default, the ink color is black. This can be changed through the $inkcanvas.DefaultDrawingAttributes.Color property. This can be a wide range of colors depending on your style. In this case, I will add a line to change it to red and then we can see the results.

$inkcanvas.DefaultDrawingAttributes.Color = ‘red’

image

We can even increase or decrease the size of the ink stylus to have a wider or narrower drawing stroke size. This is handled under the $InkCanvas.DefaultDrawingAttributes.Width and $InkCanvas.DefaultDrawingAttributes.Height properties. Adjusting these values will have a nice effect on the size as shown below:

Larger

image

Smaller

image

Setting up a Highlighter

Another nice thing that you can take advantage of with the InkCanvas is that it has a highlighter that you can use…after you select it that is.

$InkCanvas.DefaultDrawingAttributes.IsHighlighter = $True
$InkCanvas.DefaultDrawingAttributes.Color = 'Yellow'
$InkCanvas.DefaultDrawingAttributes.StylusTip = 'Rectangle'

Note here that I changed the shape of the stylus from a ellipses to a rectangle to make it more ‘highlighter like’ and also set the color to yellow. The result is a more transparent stroke that shows up when being used.

image

Let’s merge both a drawing and the highlighter into one thing and see what we can do.

image

As you can see, we can both use the ink and highlighter to accomplish a mix of things.

Erasing out Work

Of course, with drawing various things here, we certainly could use a way to erase our work or the entire canvas if we need to. First we can clear the board by calling the following method under the Strokes property object;

$InkCanvas.Strokes.Clear()

No real need for a screenshot here as a cleared canvas is pretty easy to envision.

The eraser part is the next thing to look at. We can set this under the $InkCanvas.EditingMode property by specifying one of the two options for the eraser:

  • EraseByPoint
    • Standard eraser where you drag the mouse around to erase parts of the stroke. Cursor is in a square shape.

EraseByPoint

  • EraseByStroke
    • This will actually erase the entire stroke vs. just a part of it. Cursor looks like an eraser.

EraseByStroke

With a couple of ways to erase our work, it shouldn’t be hard to clean up some accidental mistakes!

Selecting our Drawings

We can also select our drawings as well and copy, cut, resize and move them around! We first have to tell the InkCanvas that we want to select something by using the Select() method on the InkCanvas. We also need to specify an object of System.Windows.Ink.StrokeCollection.

$InkCanvas.Select((new-object System.Windows.Ink.StrokeCollection))

You will see the cursor change into a large plus (+) sign and we are now free to drag an outline of the drawing to then take action against using one of the following methods;

$InkCanvas.CopySelection()
$InkCanvas.CutSelection()
$InkCanvas.Paste()

As you can see, we are able to copy, cut or paste our ink image on the canvas. Moving and resizing occurs once we have selected an item.

SelectMethod

Saving and Loading Ink Drawings

The last thing to really cover here is saving and loading our ink drawings.

Saving involves setting up a FileStream that we can then apply to the Save() method of the $InkCanvas.Strokes property collection. We can make this process easier by creating a SaveDialog and giving it a default extension of .isf for our ink drawings.

$SaveDialog = New-Object  Microsoft.Win32.SaveFileDialog
$SaveDialog.Filter = "isf files (*.isf)|*.isf"
$Result = $SaveDialog.ShowDialog()
If ($Result){
    $FileStream = New-Object System.IO.FileStream -ArgumentList $SaveDialog.FileName, 'Create'
    $InkCanvas.Strokes.Save($FileStream)
    $FileStream.Dispose()
}

A similar approach can be done when we want to re-open an existing ink drawing by using the OpenDialog instead.

$OpenDialog = New-Object Microsoft.Win32.OpenFileDialog
$OpenDialog.Filter = "isf files (*.isf)|*.isf"
$Result = $OpenDialog.ShowDialog()
If ($Result) {
    $FileStream = New-Object System.IO.FileStream -ArgumentList $OpenDialog.FileName, 'Open'
    $StrokeCollection = New-Object System.Windows.Ink.StrokeCollection -ArgumentList $FileStream
    $InkCanvas.Strokes.Add($StrokeCollection)
    $FileStream.Dispose()
}

Our end result is something that looks like the following image:

Save_Open

I have the source code below so you can take this and have some fun with it. One last thing is that I added some keyboard shortcuts to make things such as changing the ink color, eraser, etc… more easy to do. Look for the event for Add_KeyDown.

Source Code

$Window.Add_KeyDown({ 
    $Key = $_.Key  
    If ([System.Windows.Input.Keyboard]::IsKeyDown("RightCtrl") -OR [System.Windows.Input.Keyboard]::IsKeyDown("LeftCtrl")) {
        Switch ($Key) {
            "C" {$InkCanvas.CopySelection()}
            "X" {$InkCanvas.CutSelection()}
            "P" {$InkCanvas.Paste()}
            "S" {
                $SaveDialog = New-Object  Microsoft.Win32.SaveFileDialog
                $SaveDialog.Filter = "isf files (*.isf)|*.isf"
                $Result = $SaveDialog.ShowDialog()
                If ($Result){
                    $FileStream = New-Object System.IO.FileStream -ArgumentList $SaveDialog.FileName, 'Create'
                    $InkCanvas.Strokes.Save($FileStream)
                    $FileStream.Dispose()
                }                
            }
            "O" {
                $OpenDialog = New-Object Microsoft.Win32.OpenFileDialog
                $OpenDialog.Filter = "isf files (*.isf)|*.isf"
                $Result = $OpenDialog.ShowDialog()
                If ($Result) {
                    $FileStream = New-Object System.IO.FileStream -ArgumentList $OpenDialog.FileName, 'Open'
                    $StrokeCollection = New-Object System.Windows.Ink.StrokeCollection -ArgumentList $FileStream
                    $InkCanvas.Strokes.Add($StrokeCollection)
                    $FileStream.Dispose()
                }                
            }
        }
    } Else {
        Switch ($Key) {
            "C" {
                $InkCanvas.Strokes.Clear()
            }
            "S" {
                $InkCanvas.Select((new-object System.Windows.Ink.StrokeCollection))
                #$InkCanvas.Select($InkCanvas.Strokes)
            }
            "N" {
                $Color = $Script:ColorQueue.Dequeue()
                $Script:ColorQueue.Enqueue($Color)
                $InkCanvas.DefaultDrawingAttributes.Color = $Color
            }
            "Q" {
                $This.Close()
            }
            "E" {
                Switch ($InkCanvas.EditingMode) {
                    "EraseByStroke" {
                        $InkCanvas.EditingMode = 'EraseByPoint'
                    } 
                    "EraseByPoint" {
                        $InkCanvas.EditingMode = 'EraseByStroke'
                    }
                    "Ink" {
                        $InkCanvas.EditingMode = 'EraseByPoint'
                    }
                }            
            }
            "D" {
                If ($InkCanvas.DefaultDrawingAttributes.IsHighlighter) {
                    $InkCanvas.DefaultDrawingAttributes.StylusTip = 'Ellipse'
                    $InkCanvas.DefaultDrawingAttributes.Color = 'Black'
                    $InkCanvas.DefaultDrawingAttributes.IsHighlighter = $False
                    $InkCanvas.DefaultDrawingAttributes.Height = $Script:OriginalInkSize.Height
                    $InkCanvas.DefaultDrawingAttributes.Width = $Script:OriginalInkSize.Width
                    $Script:OriginalHighLightSize.Width = $InkCanvas.DefaultDrawingAttributes.Width
                    $Script:OriginalHighLightSize.Height = $InkCanvas.DefaultDrawingAttributes.Height
                }
                $InkCanvas.EditingMode = 'Ink'            
            }
            "H" {
                If (-NOT $InkCanvas.DefaultDrawingAttributes.IsHighlighter) {
                    $Script:OriginalInkSize.Width = $InkCanvas.DefaultDrawingAttributes.Width
                    $Script:OriginalInkSize.Height = $InkCanvas.DefaultDrawingAttributes.Height
                }
                $InkCanvas.EditingMode = 'Ink'
                $InkCanvas.DefaultDrawingAttributes.IsHighlighter = $True
                $InkCanvas.DefaultDrawingAttributes.Color = 'Yellow'
                $InkCanvas.DefaultDrawingAttributes.StylusTip = 'Rectangle'
                $InkCanvas.DefaultDrawingAttributes.Width = $Script:OriginalHighLightSize.Width            
                $InkCanvas.DefaultDrawingAttributes.Height = $Script:OriginalHighLightSize.Height 
            }
            "OemPlus" {
                Switch ($InkCanvas.EditingMode) {
                    "EraseByPoint" {
                        If ($InkCanvas.EraserShape.Width -lt 20) {
                            $NewSize = $InkCanvas.EraserShape.Width + 2
                            $Rectangle = New-Object System.Windows.Ink.RectangleStylusShape -ArgumentList $NewSize,$NewSize
                            $InkCanvas.EraserShape = $Rectangle
                            $InkCanvas.EditingMode = 'None'
                            $InkCanvas.EditingMode = 'EraseByPoint'
                        }                
                    }
                    "Ink" {
                        If ($InkCanvas.DefaultDrawingAttributes.Height -lt 20) {
                            $InkCanvas.DefaultDrawingAttributes.Height = $InkCanvas.DefaultDrawingAttributes.Height + 2
                            $InkCanvas.DefaultDrawingAttributes.Width = $InkCanvas.DefaultDrawingAttributes.Width + 2
                            $InkCanvas.EditingMode = 'None'
                            $InkCanvas.EditingMode = 'Ink'
                        }
                    }
                }
                    
            }
            "OemMinus" {
                Switch ($InkCanvas.EditingMode) {
                    "EraseByPoint" {
                        If ($InkCanvas.EraserShape.Width -gt 2) {
                            $NewSize = $InkCanvas.EraserShape.Width - 2
                            $Rectangle = New-Object System.Windows.Ink.RectangleStylusShape -ArgumentList $NewSize,$NewSize
                            $InkCanvas.EraserShape = $Rectangle
                            $InkCanvas.EditingMode = 'None'
                            $InkCanvas.EditingMode = 'EraseByPoint'
                        }
                    }
                    "Ink" {
                        If ($InkCanvas.DefaultDrawingAttributes.Height -gt 2) {
                            $InkCanvas.DefaultDrawingAttributes.Height = $InkCanvas.DefaultDrawingAttributes.Height - 2
                            $InkCanvas.DefaultDrawingAttributes.Width = $InkCanvas.DefaultDrawingAttributes.Width - 2
                            $InkCanvas.EditingMode = 'None'
                            $InkCanvas.EditingMode = 'Ink'
                        }
                    }
                }            
            }
        }
    }
        
})
This entry was posted in powershell and tagged , , , . Bookmark the permalink.

Leave a comment