In this post, we are going to create a program that allows you to draw on a canvas. It will be somewhat similar to the classic program paint, except that it will have way less features. This program will track when you press down on the mouse (or tap on the screen) and track the movements that you make. It will stop when you let go of the mouse button (or stop pressing on the screen). This will allow you to draw simple shapes and pictures. Also, to make it a little bit more exciting, we'll add undo, redo, and clear. These are fairly simple to implement and are extremely helpful to the user.
User Interface
Let's start off with the user interface. Here's the full code to create the UI:
At first, it might look like a fair bit of code, but it really isn't going to be that bad. We start by using a Grid to store all of our UI components in. With Grids, you can specify the number of rows and columns that you want, along with the size of each row and column. In this case, all we want is two rows. The two rows are going to store the command bar (or menu) at the top of the screen and a huge canvas that is going to take up the rest of the screen. You'll notice that when I defined the rows, I didn't give them an absolute size. In the first case, I set the size to Auto. That tells the computer to look at all of the UI elements within the row and figure out what the height should be. In the second case, I set the size to be '*'. That means to take up the rest of the available space. So, this way when the window's size changes, we don't need to do anything. We've told the computer how we want to handle it already. The first so many pixels go to the command bar and the rest of them go to the canvas.
In the command bar, we have three AppBarButtons, that all look very similar. They have a Label, an Icon, and a Click event handler. All of the icons that we are using are built in, so we don't need to go searching (or creating) icons to use. The Click event handler tells the computer what to do when the button is clicked.
Next we have a canvas. This is the heart of our application. This gives the user a place to draw on. For the user to do the drawing, we are going to need to handle three events: PointerPressed, PointerMoved, and PointerReleased. The PointerPressed event fires every time that the user presses down on the mouse (or presses down on the screen). We are going to want to listen to this event in order to determine when to start drawing on the canvas. The PointerMoved event fires whenever the mouse is moved. This fires whether or not the mouse button is being pressed and held. So, we'll need to do that differentiation. Finally, we'll listen to the PointerReleased event. This fires when the mouse button is released and we'll use this to know when to stop drawing.
The only other part to the canvas that is interesting is the background property. At first glance, it might seem weird to set the background and then set it to transparent. This is for detecting whether or not the mouse click is within the canvas (also referred to as hit testing). Without setting a background, hit testing is not done, which is a problem for us since we want to respond to mouse clicks within the canvas.
Logic
Here's the full code for the logic behind the UI:
First, we declare three variables: _line, _undo, and _redo. The _line variable tracks the current line that we are drawing. _undo and _redo track the undo and redo stacks, respectfully. When the user finishes drawing a line, we'll move that line from the _line variable to the top of the _undo stack.
The first event handler that we'll look at is the MainCanvas_PointerPressed event handler. This happens when the user presses down on the mouse button. In this case, we want to initiate drawing a new line on the canvas. So, we'll create a new line and store it in our _line variable that stores the line that we are currently drawing. When we create it, we'll just set some basic properties on the line like the line color (Stroke) and the thickness of the line (StrokeThickness). Feel free to change these settings to different values that better suit your needs. (Or create a UI around them and make them configurable by the user!) Once the line is created, we'll add the starting position to the list of points that the line has and then add the line to the canvas so that the user can see it.
When the mouse is moved, the MainCanvas_PointerMoved event handler will be called. Now, this event will be called every time the mouse moves – not just when the mouse is pressed. So, the first thing that we need to do is to check if the mouse button is down. We do this by seeing if there is a line that is currently being drawn. So, if the _line variable is not null, then there is a line being drawn. Once we know that this is an event that we want to be listening to, we get the current location of the mouse and add that to the polyline.
The MainCanvas_PointerMoved event will get called a lot. So much so, that it is difficult to place a break point in there because it will be called almost instantly. This means that this function can be difficult to debug. This also means that this function needs to be very fast!
When the mouse button is released, the MainCanvas_PointerReleased event handler is called. In this event, we finish up the drawing of the line. We do this by setting the _line variable to null, indicating that there is no longer a current line being drawn. We also add the line to the undo stack so that it is the next line that is going to be undone when the user clicks the undo button. Finally, we clear the redo stack.
Let's take a moment to step back and think about this for a minute. With those three small functions, we have built the bulk of our application. They handle tracking the user's movements in order to draw lines on the screen. That's quite incredible how little code that took us to accomplish.
Now we have three command bar button event handlers that are really meant to increase the user experience.
For undo, first we check to see if there are any lines on the undo stack. If there are, then we take the top one, remove it from the canvas and add it on to the back of the redo stack. The code tracks nicely with how we would describe the operation in words.
Redo is very similar to undo. If there is anything on the redo stack, we take the top one, remove it from the canvas, and add it to the undo stack.
Finally, we have a button to clear the canvas. Along with clearing the canvas, we want to clear the undo and redo stacks.
Conclusion
In this blog post, we created a small program that lets you draw shapes or pictures on a canvas. Along with basic drawing functionality we added the ability to undo, redo, or clear the canvas. All of this took less than a hundred lines of code.
GitHub
The full source code for this tutorial is also on GitHub: