- Joined
- Jun 27, 2006
- Messages
- 23,048
- Thread Author
-
- #1
In the last post, we explored a brief history of pen computing and introduced you to how easy it is to get started with Windows Ink in your Universal Windows Platform app. You saw that you can enable inking by adding a single line of code, an InkCanvas, to your app to enable inking. You also saw that adding another single line of code, the InkToolbar, gives the user additional pen-related tools like pen-stroke color and stroke type.
In this post, weāll dig deeper into how we can further customize the pen and ink experience to make your application a delightful inking experience for the user. Letās build a Coloring Book application!
Customizing The Inking Experience
Getting Started
To get started, letās put in an InkCanvas on the page:
<InkCanvas x:Name="myInkCanvas"/>
By default, the InkCanvasās input is set to only accept strokes from a Pen. However, we can change that by setting the InputDeviceTypes property of the InkCanvasās InkPresenter. In the page constructor, we want to configure the InkCanvas so that it works for pen, mouse and touch:
myInkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Pen
| Windows.UI.Core.CoreInputDeviceTypes.Mouse
| Windows.UI.Core.CoreInputDeviceTypes.Touch;
As we did in the last article, weāll add an InkToolbar and bind it to myInkCanvas, but this time weāre going to put it within a CommandBar. This is so we can keep it next the other buttons that weāll add later, like Save and Share.
<CommandBar Name="myCommandBar" IsOpen="True" >
<CommandBar.Content>
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}"/>
</CommandBar.Content>
</CommandBar>
Note: If you see a XAML designer error when you add the InkToolbar, you can safely ignore this as it is a known issue that is being worked on. Your code will run fine.
However, this time, we also want to provide the user with some additional InkToolbar options. We have two main ways to do this using the InkToolbar, we can use a
Built-in InkToolbar pens
Letās start with an example of a built-in option, the InkToolbarBallPointPenButton. This is an āout-of-the-boxā InkToolbar button that, when selected in the InkToolbar, activates the BallPointPen. To add this, you place it within the InkToolbarās content, like so:
<CommandBar Name="myCommandBar" IsOpen="True" >
<CommandBar.Content>
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}">
<InkToolbarBallpointPenButton Name="penButton" />
</InkToolbar>
</CommandBar.Content>
</CommandBar>
If you ran the app now, your InkToolbar would look like this:
Custom InkToolbar Pens
Creating a custom pen is rather straightforward and requires very little code. Letās start with the basic requirement: We need to create a class that inherits from InkToolbarCustomPen and give it some attributes that define how it will draw. Letās take this step by step and make a custom highlighter marker.
First, letās add a new class to your project. Name the class āMarkerPen,ā add the following using statements and inherit from InkToolbarCustomPen:
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
class MarkerPen : InkToolbarCustomPen
{
}
In this class, we only need to override the CreateInkDrawingAttributesCore method. Add the following method to the class now:
protected override InkDrawingAttributes CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
{
}
Within that method we can start setting some drawing attributes. This is done by making an instance of InkDrawingAttributes and setting some properties. Here are the attributes Iād like the pen to have:
Hereās how we can fulfill those requirements:
InkDrawingAttributes inkDrawingAttributes = new InkDrawingAttributes();
// Set the PenTip (can also be a rectangle)
inkDrawingAttributes.PenTip = PenTipShape.Circle;
// Set the default color to Red
SolidColorBrush solidColorBrush = brush as SolidColorBrush;
inkDrawingAttributes.Color = solidColorBrush?.Color ?? Colors.Red;
// Make sure it draws as a highlighter
inkDrawingAttributes.DrawAsHighlighter = true;
// Set the brush stroke
inkDrawingAttributes.Size = new Windows.Foundation.Size(strokeWidth * 2, strokeWidth * 2);
return inkDrawingAttributes;
Thatās it, your custom pen is done. Hereās the completed class:
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
class MarkerPen : InkToolbarCustomPen
{
protected override InkDrawingAttributes CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
{
InkDrawingAttributes inkDrawingAttributes = new InkDrawingAttributes();
inkDrawingAttributes.PenTip = PenTipShape.Circle;
SolidColorBrush solidColorBrush = brush as SolidColorBrush;
inkDrawingAttributes.Color = solidColorBrush?.Color ?? Colors.Red;
inkDrawingAttributes.DrawAsHighlighter = true;
inkDrawingAttributes.Size = new Windows.Foundation.Size(strokeWidth * 2, strokeWidth * 2);
return inkDrawingAttributes;
}
}
Now, letās go back to the page where you have your InkToolbar and InkCanvas. We want to create Resources section for your page that contains a StaticResource instance of the custom pen. So, just above the root Grid element, add the following Resources code:
<Page ...>
<Page.Resources>
<local:MarkerPen x:Key="MarkerPen"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
...
</Grid>
</Page>
A quick note about XAML Resources: The pageās resources list is a key/value dictionary of objects that you can reference using the resourceās key. Weāve created an instance of our MarkerPen class, local:MarkerPen, and given it a key value of āMarkerPenā (if you want to learn more about XAML resources, see here).
We can now use that key in a InkToolbarCustomPenButtonās CustomPen property. This is better explained by the code. Letās break it down:
In your InkToolbar, add an InkToolbarCustomPen and give it a name:
<InkToolbar>
<InkToolbarCustomPenButton Name="markerButton"></InkToolbarCustomToolButton>
</InkToolbar>
The InkToolbarCustomPen has a CustomPen property:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="">
We can now set that CustomPen property using the key of our resource:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}">
Now, letās set the SymbolIcon for the button:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}">
<SymbolIcon Symbol="Highlight" />
</InkToolbarCustomPenButton>
Next, letās add an InkToolbarPenConfigurationControl:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}">
<SymbolIcon Symbol="Highlight" />
<InkToolbarCustomPenButton.ConfigurationContent>
<InkToolbarPenConfigurationControl />
</InkToolbarCustomPenButton.ConfigurationContent>
</InkToolbarCustomPenButton>
Letās take a look at what the InkToolbarPenConfigurationControl does for you. Even with a custom implementation of a pen, you still get to use the out-of-the-box Windows Ink components. If the user clicks on your pen after itās selected, theyāll get a fly-out containing options to change the color and the size of the pen!
However, thereās one little tweak we want to make. By default, you get Black and White as the only colors in the flyout:
We want a lot of colors, and fortunately, the BallpointPenButton you added earlier has a palette full of colors. We can just use that same palette for our custom pen by binding to it:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}" Palette="{x:Bind penButton.Palette}" >
Now, hereās what the pen configuration control looks after binding the Palette:
Whew, okay, the toolbar is coming along nicely! Hereās what we have so far for our CommandBar:
<CommandBar Name="myCommandBar" IsOpen="True">
<CommandBar.Content>
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}">
<InkToolbarBallpointPenButton Name="penButton" />
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}" Palette="{x:Bind penButton.Palette}" >
<SymbolIcon Symbol="Highlight" />
<InkToolbarCustomPenButton.ConfigurationContent>
<InkToolbarPenConfigurationControl />
</InkToolbarCustomPenButton.ConfigurationContent>
</InkToolbarCustomPenButton>
</InkToolbar>
</CommandBar.Content>
</CommandBar>
Now, letās start adding some commands.
Custom InkToolbar Tool Buttons
The first thing youād really want in a drawing application is the ability to undo something. To do this weāll want to add another button to the toolbar; this is easily done using an InkToolbarCustomToolButton. If youāre familiar with adding buttons to a CommandBar, youāll feel right at home.
In your InkToolbar, add an InkToolbarCustomToolButton and give it a name, āundoButton.ā
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}" Palette="{x:Bind penButton.Palette}" >
...
<InkToolbarCustomToolButton Name="undoButton"></InkToolbarCustomToolButton>
</InkToolbar>
The button has your familiar button properties, such as a Click event and supporting a SymbolIcon for content, so letās add those as well.
Hereās what your XAML should look like:
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}" Palette="{x:Bind penButton.Palette}">
...
<InkToolbarCustomToolButton Name="undoButton" Click="Undo_Click" >
<SymbolIcon Symbol="Undo"/>
</InkToolbarCustomToolButton>
</InkToolbar>
Now, letās go to the buttonās click event handler. Here we can do the following to undo strokes that were applied to the InkPresenter, here are the steps:
First, make sure you add the following using statement to the code-behind:
using Windows.UI.Input.Inking;
Then get all the strokes in the InkPresenterās StrokeContainer:
IReadOnlyList<InkStroke> strokes = myInkCanvas.InkPresenter.StrokeContainer.GetStrokes();
Next, verify that there are strokes to undo before proceeding:
if (strokes.Count > 0)
If there are strokes, select the last one in the container:
strokes[strokes.Count - 1].Selected = true;
Finally, delete that selected stroke using DeleteSelected():
myInkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
As you can see, itās pretty easy to get access to the strokes that were made by the user and just as easy to remove a stroke. Here is the complete event handler:
private void Undo_Click(object sender, RoutedEventArgs e)
{
// We can get a list of the strokes that are in the InkPresenter
IReadOnlyList<InkStroke> strokes = myInkCanvas.InkPresenter.StrokeContainer.GetStrokes();
// Make sure there are strokes to undo
if (strokes.Count > 0)
{
// select the last stroke
strokes[strokes.Count - 1].Selected = true;
// Finally, delete the stroke
myInkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
}
}
Final InkCanvas configuration
Before we conclude the drawing logic, we need to make sure the page loads with some InkDrawingAttributes presets and InkPresenter configuration. To do this, we can hook into the InkCanvasās Loaded event.
We can do this in the XAML:
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}" Palette="{x:Bind penButton.Palette}" Loaded="InkToolbar_Loaded">
The attributes are set in a similar way that we set them for the custom pen, instantiate an InkDrawingAttributes object and set some properties. However, this time, weāre passing those attributes to the InkPresenter.
Additionally, a few other things thing should be addressed:
Hereās the code for the InkCanvasās Loaded event handler:
private void InkToolbar_Loaded(object sender, RoutedEventArgs e)
{
// Create an instance of InkDrawingAttributes
InkDrawingAttributes drawingAttributes = new InkDrawingAttributes();
// We want the pen pressure to be applied to the user's stroke
drawingAttributes.IgnorePressure = false;
// This will set it to that the ink stroke will use a Bezier curve instead of a collection of straight line segments
drawingAttributes.FitToCurve = true;
// Update the InkPresenter with the attributes
myInkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
// Set the initial active tool to our custom pen
myInkToolbar.ActiveTool = markerButton;
// Finally, make sure that the InkCanvas will work for a pen, mouse and touch
myInkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Pen
| Windows.UI.Core.CoreInputDeviceTypes.Mouse
| Windows.UI.Core.CoreInputDeviceTypes.Touch;
}
Saving, Sharing and Loading
Now that youāve got a decent working area, we want to be able to save, load and share the userās work. In the last post, we showed a simple way to save and load the canvas. However, in our Coloring Book app, we want to have the image and the ink data saved separately so that we can easily share the image for display and sharing purposes, but save, load and edit inking data as well.
Saving Ink Data
As we did in the last post, you can save the ink strokes to a file using the StrokeContainerās SaveAsync method. What weāll do differently here is right after weāve saved the ink file, weāll also save a parallel image file in the cache. Although weāre able to embed the stroke data into the gif we saved, having a temporary image stored in the cache makes sharing and displaying the image in the app more convenient.
So, at the end of your Link Removed, you want to create a new (or get an existing) StorageFile for the image:
// Save inked image.
StorageFile myInkedImageFile = await folder.CreateFileAsync(Constants.inkedImageFile, CreationCollisionOption.ReplaceExisting);
await Save_InkedImagetoFile(myInkedImageFile);
Next, we pass the myInkedImageFile StorageFile reference to the Link Removed method, which saves the image to the file:
private async Task Save_InkedImagetoFile(StorageFile saveFile)
{
if (saveFile != null)
{
ā¦
using (var outStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
{
await Save_InkedImageToStream(outStream);
}
ā¦
}
}
And finally, we get that bitmap from the canvas into the file in the Save_InkedImageToStream method; this is where we leverage Win2D to get a great looking bitmap from the canvas:
private async Task Save_InkedImageToStream(IRandomAccessStream stream)
{
var file = await StorageFile.GetFileFromApplicationUriAsync(((BitmapImage)myImage.Source).UriSource);
CanvasDevice device = CanvasDevice.GetSharedDevice();
var image = await CanvasBitmap.LoadAsync(device, file.Path);
using (var renderTarget = new CanvasRenderTarget(device, (int)myInkCanvas.ActualWidth, (int)myInkCanvas.ActualHeight, image.Dpi))
{
using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
{
ds.Clear(Colors.White);
ds.DrawImage(image, new Rect(0, 0, (int)myInkCanvas.ActualWidth, (int)myInkCanvas.ActualHeight));
ds.DrawInk(myInkCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
}
}
You might ask, why is there a separate method for getting the stream instead of doing it in one place? The first reason is that we want to be a responsible developer and make sure our method names define what action the methods perform. But more importantly, we want to reuse this method later to share the userās art. With a stream, itās not only easier to share, you can even Link Removed.
Sharing the result
Now that the image is saved, we can share it. The approach here is the same as other UWP sharing scenarios. You want to use the DataTransferManager; you can find many example of how to use this here in the Official UWP samples on GitHub.
For the purposes of this article, weāll focus only on the DataTransferManagerās DataRequested method. You can see the full sharing code for this Link Removed). This is where the Save_InkedImageToStream method gets to be reused!
private async void DataRequested(DataTransferManager sender, DataRequestedEventArgs e)
{
DataRequest request = e.Request;
DataRequestDeferral deferral = request.GetDeferral();
request.Data.Properties.Title = "A Coloring Page";
request.Data.Properties.ApplicationName = "Coloring Book";
request.Data.Properties.Description = "A coloring page sent from my Coloring Book app!";
using (InMemoryRandomAccessStream inMemoryStream = new InMemoryRandomAccessStream())
{
await Save_InkedImageToStream(inMemoryStream);
request.Data.SetBitmap(RandomAccessStreamReference.CreateFromStream(inMemoryStream));
}
deferral.Complete();
}
Loading Ink Data from a file
In our Coloring Book app, we want the user to continue working on previous drawings as if they never stopped. Weāre able to save the ink file and capture and save the image of the work, but we also need to load the ink data properly.
In the last post we covered how to load up the stroke from the file; letās review this now.
// Get a reference to the file that contains the inking stroke data
StorageFile inkFile = await folder.GetFileAsync(Constants.inkFile);
if (inkFile != null)
{
IRandomAccessStream stream = await inkFile.OpenAsync(Windows.Storage.FileAccessMode.Read);
using (var inputStream = stream.GetInputStreamAt(0))
{
// Load the strokes back into the StrokeContainer
await myInkCanvas.InkPresenter.StrokeContainer.LoadAsync(stream);
}
stream.Dispose();
}
Thatās all there is to loading sketchās ink data. All the strokes, and the inkās attributes, will be loaded into the InkCanvas and the user can continue working on his or her creation.
In the next post, weāll look at some other real-world applications of Windows Ink and how inking can empower educational and enterprise applications. Weāll also take a look at some of the new hardware and APIs available that make using Windows Ink a go-to item for design professionals.
Resources
Continue reading...
In this post, weāll dig deeper into how we can further customize the pen and ink experience to make your application a delightful inking experience for the user. Letās build a Coloring Book application!
Customizing The Inking Experience
Getting Started
To get started, letās put in an InkCanvas on the page:
<InkCanvas x:Name="myInkCanvas"/>
By default, the InkCanvasās input is set to only accept strokes from a Pen. However, we can change that by setting the InputDeviceTypes property of the InkCanvasās InkPresenter. In the page constructor, we want to configure the InkCanvas so that it works for pen, mouse and touch:
myInkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Pen
| Windows.UI.Core.CoreInputDeviceTypes.Mouse
| Windows.UI.Core.CoreInputDeviceTypes.Touch;
As we did in the last article, weāll add an InkToolbar and bind it to myInkCanvas, but this time weāre going to put it within a CommandBar. This is so we can keep it next the other buttons that weāll add later, like Save and Share.
<CommandBar Name="myCommandBar" IsOpen="True" >
<CommandBar.Content>
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}"/>
</CommandBar.Content>
</CommandBar>
Note: If you see a XAML designer error when you add the InkToolbar, you can safely ignore this as it is a known issue that is being worked on. Your code will run fine.
However, this time, we also want to provide the user with some additional InkToolbar options. We have two main ways to do this using the InkToolbar, we can use a
- Built-in InkToolbar pen button
- Custom InkToolbar pen button
Built-in InkToolbar pens
Letās start with an example of a built-in option, the InkToolbarBallPointPenButton. This is an āout-of-the-boxā InkToolbar button that, when selected in the InkToolbar, activates the BallPointPen. To add this, you place it within the InkToolbarās content, like so:
<CommandBar Name="myCommandBar" IsOpen="True" >
<CommandBar.Content>
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}">
<InkToolbarBallpointPenButton Name="penButton" />
</InkToolbar>
</CommandBar.Content>
</CommandBar>
If you ran the app now, your InkToolbar would look like this:
Link Removed
Custom InkToolbar Pens
Creating a custom pen is rather straightforward and requires very little code. Letās start with the basic requirement: We need to create a class that inherits from InkToolbarCustomPen and give it some attributes that define how it will draw. Letās take this step by step and make a custom highlighter marker.
First, letās add a new class to your project. Name the class āMarkerPen,ā add the following using statements and inherit from InkToolbarCustomPen:
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
class MarkerPen : InkToolbarCustomPen
{
}
In this class, we only need to override the CreateInkDrawingAttributesCore method. Add the following method to the class now:
protected override InkDrawingAttributes CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
{
}
Within that method we can start setting some drawing attributes. This is done by making an instance of InkDrawingAttributes and setting some properties. Here are the attributes Iād like the pen to have:
- Act like a highlighter
- Has a round pen tip shape
- Has a red stroke color as the default color
- Be twice as thick as the userās stroke
Hereās how we can fulfill those requirements:
InkDrawingAttributes inkDrawingAttributes = new InkDrawingAttributes();
// Set the PenTip (can also be a rectangle)
inkDrawingAttributes.PenTip = PenTipShape.Circle;
// Set the default color to Red
SolidColorBrush solidColorBrush = brush as SolidColorBrush;
inkDrawingAttributes.Color = solidColorBrush?.Color ?? Colors.Red;
// Make sure it draws as a highlighter
inkDrawingAttributes.DrawAsHighlighter = true;
// Set the brush stroke
inkDrawingAttributes.Size = new Windows.Foundation.Size(strokeWidth * 2, strokeWidth * 2);
return inkDrawingAttributes;
Thatās it, your custom pen is done. Hereās the completed class:
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
class MarkerPen : InkToolbarCustomPen
{
protected override InkDrawingAttributes CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
{
InkDrawingAttributes inkDrawingAttributes = new InkDrawingAttributes();
inkDrawingAttributes.PenTip = PenTipShape.Circle;
SolidColorBrush solidColorBrush = brush as SolidColorBrush;
inkDrawingAttributes.Color = solidColorBrush?.Color ?? Colors.Red;
inkDrawingAttributes.DrawAsHighlighter = true;
inkDrawingAttributes.Size = new Windows.Foundation.Size(strokeWidth * 2, strokeWidth * 2);
return inkDrawingAttributes;
}
}
Now, letās go back to the page where you have your InkToolbar and InkCanvas. We want to create Resources section for your page that contains a StaticResource instance of the custom pen. So, just above the root Grid element, add the following Resources code:
<Page ...>
<Page.Resources>
<local:MarkerPen x:Key="MarkerPen"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
...
</Grid>
</Page>
A quick note about XAML Resources: The pageās resources list is a key/value dictionary of objects that you can reference using the resourceās key. Weāve created an instance of our MarkerPen class, local:MarkerPen, and given it a key value of āMarkerPenā (if you want to learn more about XAML resources, see here).
We can now use that key in a InkToolbarCustomPenButtonās CustomPen property. This is better explained by the code. Letās break it down:
In your InkToolbar, add an InkToolbarCustomPen and give it a name:
<InkToolbar>
<InkToolbarCustomPenButton Name="markerButton"></InkToolbarCustomToolButton>
</InkToolbar>
The InkToolbarCustomPen has a CustomPen property:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="">
We can now set that CustomPen property using the key of our resource:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}">
Now, letās set the SymbolIcon for the button:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}">
<SymbolIcon Symbol="Highlight" />
</InkToolbarCustomPenButton>
Next, letās add an InkToolbarPenConfigurationControl:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}">
<SymbolIcon Symbol="Highlight" />
<InkToolbarCustomPenButton.ConfigurationContent>
<InkToolbarPenConfigurationControl />
</InkToolbarCustomPenButton.ConfigurationContent>
</InkToolbarCustomPenButton>
Letās take a look at what the InkToolbarPenConfigurationControl does for you. Even with a custom implementation of a pen, you still get to use the out-of-the-box Windows Ink components. If the user clicks on your pen after itās selected, theyāll get a fly-out containing options to change the color and the size of the pen!
However, thereās one little tweak we want to make. By default, you get Black and White as the only colors in the flyout:
Link Removed
We want a lot of colors, and fortunately, the BallpointPenButton you added earlier has a palette full of colors. We can just use that same palette for our custom pen by binding to it:
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}" Palette="{x:Bind penButton.Palette}" >
Now, hereās what the pen configuration control looks after binding the Palette:
Link Removed
Whew, okay, the toolbar is coming along nicely! Hereās what we have so far for our CommandBar:
<CommandBar Name="myCommandBar" IsOpen="True">
<CommandBar.Content>
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}">
<InkToolbarBallpointPenButton Name="penButton" />
<InkToolbarCustomPenButton Name="markerButton" CustomPen="{StaticResource MarkerPen}" Palette="{x:Bind penButton.Palette}" >
<SymbolIcon Symbol="Highlight" />
<InkToolbarCustomPenButton.ConfigurationContent>
<InkToolbarPenConfigurationControl />
</InkToolbarCustomPenButton.ConfigurationContent>
</InkToolbarCustomPenButton>
</InkToolbar>
</CommandBar.Content>
</CommandBar>
Now, letās start adding some commands.
Custom InkToolbar Tool Buttons
The first thing youād really want in a drawing application is the ability to undo something. To do this weāll want to add another button to the toolbar; this is easily done using an InkToolbarCustomToolButton. If youāre familiar with adding buttons to a CommandBar, youāll feel right at home.
In your InkToolbar, add an InkToolbarCustomToolButton and give it a name, āundoButton.ā
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}" Palette="{x:Bind penButton.Palette}" >
...
<InkToolbarCustomToolButton Name="undoButton"></InkToolbarCustomToolButton>
</InkToolbar>
The button has your familiar button properties, such as a Click event and supporting a SymbolIcon for content, so letās add those as well.
Hereās what your XAML should look like:
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}" Palette="{x:Bind penButton.Palette}">
...
<InkToolbarCustomToolButton Name="undoButton" Click="Undo_Click" >
<SymbolIcon Symbol="Undo"/>
</InkToolbarCustomToolButton>
</InkToolbar>
Now, letās go to the buttonās click event handler. Here we can do the following to undo strokes that were applied to the InkPresenter, here are the steps:
First, make sure you add the following using statement to the code-behind:
using Windows.UI.Input.Inking;
Then get all the strokes in the InkPresenterās StrokeContainer:
IReadOnlyList<InkStroke> strokes = myInkCanvas.InkPresenter.StrokeContainer.GetStrokes();
Next, verify that there are strokes to undo before proceeding:
if (strokes.Count > 0)
If there are strokes, select the last one in the container:
strokes[strokes.Count - 1].Selected = true;
Finally, delete that selected stroke using DeleteSelected():
myInkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
As you can see, itās pretty easy to get access to the strokes that were made by the user and just as easy to remove a stroke. Here is the complete event handler:
private void Undo_Click(object sender, RoutedEventArgs e)
{
// We can get a list of the strokes that are in the InkPresenter
IReadOnlyList<InkStroke> strokes = myInkCanvas.InkPresenter.StrokeContainer.GetStrokes();
// Make sure there are strokes to undo
if (strokes.Count > 0)
{
// select the last stroke
strokes[strokes.Count - 1].Selected = true;
// Finally, delete the stroke
myInkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
}
}
Final InkCanvas configuration
Before we conclude the drawing logic, we need to make sure the page loads with some InkDrawingAttributes presets and InkPresenter configuration. To do this, we can hook into the InkCanvasās Loaded event.
We can do this in the XAML:
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind myInkCanvas}" Palette="{x:Bind penButton.Palette}" Loaded="InkToolbar_Loaded">
The attributes are set in a similar way that we set them for the custom pen, instantiate an InkDrawingAttributes object and set some properties. However, this time, weāre passing those attributes to the InkPresenter.
Additionally, a few other things thing should be addressed:
- Give the custom pen the same color palette as the ballpoint pen
- Set the initial active tool
- Make sure that users can also use the mouse
Hereās the code for the InkCanvasās Loaded event handler:
private void InkToolbar_Loaded(object sender, RoutedEventArgs e)
{
// Create an instance of InkDrawingAttributes
InkDrawingAttributes drawingAttributes = new InkDrawingAttributes();
// We want the pen pressure to be applied to the user's stroke
drawingAttributes.IgnorePressure = false;
// This will set it to that the ink stroke will use a Bezier curve instead of a collection of straight line segments
drawingAttributes.FitToCurve = true;
// Update the InkPresenter with the attributes
myInkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
// Set the initial active tool to our custom pen
myInkToolbar.ActiveTool = markerButton;
// Finally, make sure that the InkCanvas will work for a pen, mouse and touch
myInkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Pen
| Windows.UI.Core.CoreInputDeviceTypes.Mouse
| Windows.UI.Core.CoreInputDeviceTypes.Touch;
}
Saving, Sharing and Loading
Now that youāve got a decent working area, we want to be able to save, load and share the userās work. In the last post, we showed a simple way to save and load the canvas. However, in our Coloring Book app, we want to have the image and the ink data saved separately so that we can easily share the image for display and sharing purposes, but save, load and edit inking data as well.
Saving Ink Data
As we did in the last post, you can save the ink strokes to a file using the StrokeContainerās SaveAsync method. What weāll do differently here is right after weāve saved the ink file, weāll also save a parallel image file in the cache. Although weāre able to embed the stroke data into the gif we saved, having a temporary image stored in the cache makes sharing and displaying the image in the app more convenient.
So, at the end of your Link Removed, you want to create a new (or get an existing) StorageFile for the image:
// Save inked image.
StorageFile myInkedImageFile = await folder.CreateFileAsync(Constants.inkedImageFile, CreationCollisionOption.ReplaceExisting);
await Save_InkedImagetoFile(myInkedImageFile);
Next, we pass the myInkedImageFile StorageFile reference to the Link Removed method, which saves the image to the file:
private async Task Save_InkedImagetoFile(StorageFile saveFile)
{
if (saveFile != null)
{
ā¦
using (var outStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
{
await Save_InkedImageToStream(outStream);
}
ā¦
}
}
And finally, we get that bitmap from the canvas into the file in the Save_InkedImageToStream method; this is where we leverage Win2D to get a great looking bitmap from the canvas:
private async Task Save_InkedImageToStream(IRandomAccessStream stream)
{
var file = await StorageFile.GetFileFromApplicationUriAsync(((BitmapImage)myImage.Source).UriSource);
CanvasDevice device = CanvasDevice.GetSharedDevice();
var image = await CanvasBitmap.LoadAsync(device, file.Path);
using (var renderTarget = new CanvasRenderTarget(device, (int)myInkCanvas.ActualWidth, (int)myInkCanvas.ActualHeight, image.Dpi))
{
using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
{
ds.Clear(Colors.White);
ds.DrawImage(image, new Rect(0, 0, (int)myInkCanvas.ActualWidth, (int)myInkCanvas.ActualHeight));
ds.DrawInk(myInkCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
}
}
You might ask, why is there a separate method for getting the stream instead of doing it in one place? The first reason is that we want to be a responsible developer and make sure our method names define what action the methods perform. But more importantly, we want to reuse this method later to share the userās art. With a stream, itās not only easier to share, you can even Link Removed.
Sharing the result
Now that the image is saved, we can share it. The approach here is the same as other UWP sharing scenarios. You want to use the DataTransferManager; you can find many example of how to use this here in the Official UWP samples on GitHub.
For the purposes of this article, weāll focus only on the DataTransferManagerās DataRequested method. You can see the full sharing code for this Link Removed). This is where the Save_InkedImageToStream method gets to be reused!
private async void DataRequested(DataTransferManager sender, DataRequestedEventArgs e)
{
DataRequest request = e.Request;
DataRequestDeferral deferral = request.GetDeferral();
request.Data.Properties.Title = "A Coloring Page";
request.Data.Properties.ApplicationName = "Coloring Book";
request.Data.Properties.Description = "A coloring page sent from my Coloring Book app!";
using (InMemoryRandomAccessStream inMemoryStream = new InMemoryRandomAccessStream())
{
await Save_InkedImageToStream(inMemoryStream);
request.Data.SetBitmap(RandomAccessStreamReference.CreateFromStream(inMemoryStream));
}
deferral.Complete();
}
Loading Ink Data from a file
In our Coloring Book app, we want the user to continue working on previous drawings as if they never stopped. Weāre able to save the ink file and capture and save the image of the work, but we also need to load the ink data properly.
In the last post we covered how to load up the stroke from the file; letās review this now.
// Get a reference to the file that contains the inking stroke data
StorageFile inkFile = await folder.GetFileAsync(Constants.inkFile);
if (inkFile != null)
{
IRandomAccessStream stream = await inkFile.OpenAsync(Windows.Storage.FileAccessMode.Read);
using (var inputStream = stream.GetInputStreamAt(0))
{
// Load the strokes back into the StrokeContainer
await myInkCanvas.InkPresenter.StrokeContainer.LoadAsync(stream);
}
stream.Dispose();
}
Thatās all there is to loading sketchās ink data. All the strokes, and the inkās attributes, will be loaded into the InkCanvas and the user can continue working on his or her creation.
In the next post, weāll look at some other real-world applications of Windows Ink and how inking can empower educational and enterprise applications. Weāll also take a look at some of the new hardware and APIs available that make using Windows Ink a go-to item for design professionals.
Resources
- Windows Ink 1: Introduction to Ink and Pen
- Pen Interactions and Windows Ink in a UWP Application (documentation)
- Store and Retrieve Ink data (documentation)
- Link Removed
- Use Windows Pen and Ink to build more engaging enterprise apps (video)
- Link Removed
- Using Ink in Your UWP App Channel 9
Continue reading...