Lesson 7 - Drawing on Graphics in C# .NET
In the previous lesson, Birthday Reminder - Storing Data and Conclusion, we finished creating the birthday reminder. We tried the basic form controls on it, as well as bindings and error handling. We are already able to create quite sophisticated applications. In today's C# .NET tutorial we're going to take a look at drawing.
Drawing on Graphics
We'll create an application that takes care of selling cinema tickets. As we
know, there are many seats in the cinema hall, and the cinema worker should be
able to see in the application which seats are already occupied. You might think
of clicking a PictureBox
for each seat. However, if the cinema had
15 rows with 30 seats each, we'd have 450 PictureBox
. You probably
guess there should be a better way than to start typing
PictureBox1
, PictureBox2
, ... And how those would be
handled then? In case we need to render something more complex than just one or
two pictures, we use Graphics
. The principle is we place a
single PictureBox
on the form and draw what we need on its
canvas.
We'll simplify the application considerably, it doesn't need to be complicated. It'll be able to display a single hall only, which will be initially empty. The user will click the seats to make them occupied using the mouse, and then they will press the Save button, which will create a simple text file at the selected location. The file will contain information about the occupancy of the hall. The reason of saving the data is to learn how to work with dialogues.
Form Design
Create a new Windows Forms Application, rename the form to
CinemaForm
, the title could be Cinema hall booking
.
Add a PictureBox
to cover most of the form and name it as
cinemaPictureBox
. Below the PictureBox
, place a
Button
with the saveButton
name and the
Save
text. Finally, we'll drag SaveFileDialog
onto the
form. This control won't be placed directly on the form, but in a bar below it,
because it's not an actual form control, but merely a helper component. We'll
rename it to cinemaSaveFileDialog
. You can play with the anchors as
you want. Your form should look like this:
Logic Layer
You're probably not surprised that we'll add a Cinema
class.
It'll have a 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 just a single row. With the
2D array we work exactly the same as with the 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# allows us to declare 2D arrays directly as
follows:
class Cinema { private bool[,] seats = new bool[30, 15]; }
The seats are of the bool
type, because we only care whether
it's vacant or occupied. 30
is the width of the array,
15
is the height.
We'll also add 2 private constants to the class, one defining the size of rendered seats in pixels, and the other one defining the space between the seats in pixels. Get used to constants, because when you'll want to resize the seats, you'll just change the value of a constant without having to mess with the rendering code.
private const int size = 16; private const int space = 2;
We can now move to methods.
Rendering
The cinema hall should be able to render itself. We've already mentioned that
we'll draw on a canvas. This canvas is of the Graphics
type, and
we'll request it as a Draw()
method parameter. You need to add
using System.Drawing
for the Graphics
type to use, but
it certainly didn't surprise you. We draw on the canvas using its methods. For
now, we'll be interested only in the FillRectangle()
method, which
draws a rectangle filled with a specific color. There are a lot of methods for
different geometric shapes, both filled or unfilled. You can check them out,
we'll try some of them in following lessons.
In Graphics
, there are two types of color - the fill color
(Brush
) and the outline color (Pen
). For the filled
rectangle we have to specify a Brush
. Already created instances set
to certain colors can be found in the static Brushes
class (or
Pens
for outlines, respectively), you can choose. Brushes can also
draw patterns or pictures, but that isn't important to us right now.
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 go through the rows using
the outer loop, and through the columns in the current row using the inner loop.
We'll determine the color (the brush, more precisely) by whether the seat at the
given coordinates is true
or false
. The method code
will be as follows:
public void Draw(Graphics g) { Brush brush; for (int j = 0; j < seats.GetLength(1); j++) { for (int i = 0; i < seats.GetLength(0); i++) { if (seats[i, j]) brush = Brushes.Red; else brush = Brushes.Green; g.FillRectangle(brush, i * (size + space), j * (size + space), size, size); } } }
Notice that we didn't hard-code the values 30
and
15
in the loops, but we use the GetLength()
method
instead with parameters 0
and 1
. This method is used
to get the size of a two-dimensional array. 0
is the width,
1
is the height (of course, it depends on us which dimension we
choose to represent the height and which dimension the width). Of course, we
don't specify a fixed size because in the future we may increase/decrease the
array size, and we don't want to have to look in the entire code to find where
we use the values 30
and 15
. It's always better to
avoid these problems and work with the array length.
It's worth mentioning the rendering of the rectangle itself. The first
parameter of the FillRectangle()
method is the Brush
,
which determines the filling color. The other two parameters are the coordinates
of the upper left corner of the rectangle. The last two parameters determine its
height and width. Since each seat is 16 pixels wide + 2 pixels spaced from each
other, we need to multiply its coordinate by that value. For example, if the
i
value is 2
(i.e. drawing the third column), we're
drawing at the X coordinate of 36
, instead of 2
The same applies for the Y
coordinate.
Later, you may try replacing the FillRectangle()
method with
FillOval()
. It works exactly the same, but draws an ellipse. Be
sure to try drawing other shapes after we finish the app.
Wiring the Form and Logic
The base of the logic is done, let's wire it with the form. Let's go to the form code and create a private instance of the cinema hall:
private Cinema cinema = new Cinema();
Now we'll click the PictureBox
and add it the Paint
event handler. You have to do this via the flash icon in the Properties window.
The event is called by the system when the window is to be redrawn. This is, of
course, when we start the application, but also after maximizing the window,
when we move another window over the window of our application, and so on.
In the event handler, we'll call the Draw()
method of the cinema
instance. The canvas is passed as a property of the event parameter. We'll just
pass it to the logic method that will draw on it.
private void cinemaPictureBox_Paint(object sender, PaintEventArgs e) { cinema.Draw(e.Graphics); }
We certainly don't have to be ashamed of the result:
In the next lesson, Handling Clicks at Coordinates in C# .NET, we'll show how to change the status of a seat by clicking on it, and we'll also implement saving. As always, you can download the project in the attachment below, in case something went wrong.
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 188x (229.25 kB)
Application includes source codes in language C#