Lesson 11 - Drawing on Canvas in C# .NET WPF
In the previous exercise, Solved tasks for C# .NET WPF lessons 6-10, we've practiced our knowledge from previous lessons.
In the previous lesson, Solved tasks for C# .NET WPF lessons 6-10, we finished our birthday reminder. We tried the basic form controls on it, as well as bindings and error handling. We're already able to create quite sophisticated applications. In today's C# .NET WPF tutorial, we're going to draw.
Drawing on Canvas
We'll create an application to manage the sales of cinema tickets. As we
know, there are many seats in the cinema hall, and the application should show
the cinema worker which seats are already occupied. You might be thinking of
adding a Button
control for each seat. However, if the cinema had
15 rows with 30 seats each, we'd have 450 Button
controls. You
probably expect there should be a better way than start typing
Button1
, Button2
... And how would they be handled
then? If we need to draw something more complex than just one or two pictures,
we can use Canvas
. The point is we place a single
Canvas
on the form and draw all we need on it.
We'll simplify the cinema application, it doesn't need to be complex. It'll be able to display only a single hall, which will be empty at the start. The user clicks on the seats to make them occupied, and then presses the "Save" button, which saves all the information about the occupancy of the hall to a simple txt file in the chosen location. We'll do the saving to learn how to work with dialogs.
Form Design
Create a new WPF Application, set the window title to
Cinema hall administration
. Set the
WindowStartupLocation
property to CenterScreen
. This
will ensure that the window will always appear in the center of the screen when
the application is launched.
For the form design, we'll use the Grid
control we already know
very well from the previous lessons. The layout of the application isn't very
complicated, so we can split the grid into two rows. The first row will contain
the Canvas
and the second one will contain a Button
with the Save
text. Add a Click
event to the button
and generate a Code Behind method that is called when the user clicks the
button. Your form should look like this:
And here is its corresponding XAML code:
<Window x:Class="Cinema.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:Cinema" mc:Ignorable="d" Title="Cinema hall administration" WindowStartupLocation="CenterScreen" Height="450" Width="600"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="350"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Canvas Name="MyCanvas" Margin="20 50 0 0"></Canvas> <Button Name="Save" Click="Save_Click" Content="Save" Grid.Row="1" Height="20" Width="100"></Button> </Grid> </Window>
Logical Layer
You are probably not surprised that we're going to add a Cinema
class to our application. It'll have single private field, a two-dimensional
array of seats. If you haven't worked with a 2D array yet, you can think of
it as a table. A one-dimensional (classic) array is actually just a single row.
We then work with the 2D array exactly the same as with a one-dimensional array,
except that we have to specify two coordinates (X and Y). In many languages, 2D
arrays are created as array of arrays, but C# can define 2D arrays directly as
follows:
class Cinema { private bool[,] seats = new bool[30, 15]; }
The seats are of the bool
type, because we only need to know
whether they are vacant or occupied. 30
is the width of the array,
15
is its height.
We'll also add two private constants to the class, the first one defining the size of the rendered seat in pixels, and the other one defining the space between the seats also in pixels. Get used to constants. When you decide to change the seat size, then you'll just need to overwrite a single constant, without having to mess with the entire rendering code.
private const int size = 16; private const int space = 2;
We can now move on to methods.
Insert Rectangles
We've already mentioned that we're going to draw on the canvas. Individual
rectangles representing the seats will be represented as objects that we'll
place on the canvas using a DrawRectangles()
method. The canvas is
of the Canvas
type, and we'll request it as a parameter in the
method. You'll need to add using System.Windows.Shapes
to make the
Rectangle
class available. When we'll place individual rectangles
on the Canvas
, we'll always set their width and height to the
size
constant, and then set the appropriate color using the
Fill
property and the static Brushes
class. It
contains pre-made instances set to certain colors, just pick any.
Using two nested loops, we'll go through all the seats in the array and draw
either a green or a red square on the canvas. We'll use the outer loop to go
through the rows, and the inner loop to go through each column in the current
row. The color (more precisely the brush) is determined by whether the seat at
the given coordinates is true
or false
. The method's
code will be as follows:
public void DrawRectangles(Canvas MyCanvas) { for (int j = 0; j < seats.GetLength(1); j++) { for (int i = 0; i < seats.GetLength(0); i++) { Rectangle rectangle = new Rectangle { Height = size, Width = size, }; rectangle.Fill = seats[i, j] ? Brushes.Red : Brushes.Green; MyCanvas.Children.Add(rectangle); Canvas.SetLeft(rectangle, i * (size + space)); Canvas.SetTop(rectangle, j * (size + space)); } } }
Notice that we don't use the values of 30
and 15
in
the loops, but we use the GetLength()
method with parameters
0
and 1
instead. This method is used to obtain the
size of a two-dimensional array. 0
is the width, and 1
is the height (of course, it depends on us which dimension we treat as the
height or the width).
Of course, we don't hard-code the array size because in the
future we may want to increase / decrease it without having to search the entire
code for the values 30
and 15
. It's always better to
avoid these problems and work with the array length instead.
It's worth mentioning the rendering of the rectangles themselves. We can see
that we render them by adding each Rectangle
instance as a child to
our Canvas
. Then, in order to display the rectangles exactly where
we want, it's necessary to set up their positions. We use the static
Canvas
class, which contains the SetLeft()
and
SetTop()
methods, which represent the coordinates of the upper-left
corner of the rectangle. Since every seat is 16
pixels wide +
2
pixels of empty space, we need to multiply its coordinates by
that value. If the i
had the value of 2
(when drawing
the third column), we'd draw the rectangle at the X coordinate of
36
, rather than the 2
The same applies to the Y
coordinate.
Wiring the Form and the Logic Layer
The base of the logic is complete, now let's wire it to the form. We'll go to
the Code Behind of the form and create a private Cinema
instance
there:
private Cinema cinema = new Cinema();
Now it's really simple. On the cinema instance, we'll call the
DrawRectangles()
method and pass our Canvas
to it,
which we defined in the form, as a parameter. To render the seats when we launch
the application, we'll put this code in the form constructor in the Code
Behind.
public MainWindow()
{
InitializeComponent();
cinema.DrawRectangles(MyCanvas);
}
We really don't have to be ashamed of the result:
In the next lesson, Handling Rectangle Clicks in C# .NET WPF, we'll show how to change the seat state by clicking on it, and implement saving to a file. As always, you can download the project below, in case something went south for you.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 159x (578.49 kB)
Application includes source codes in language C#