Lesson 9 - Drawing on Graphics in Java Swing
In the previous lesson, Birthday Reminder in Java Swing - Finishing the Application, we finished programming a birthday reminder. In that application, we tried out basic form elements as well as models and working with error states. We can already create quite sophisticated applications. Today's lesson will be devoted to drawing.
Drawing on Graphics
We'll create an application to help with selling cinema tickets. As we know,
there are many seats in the auditorium and employees of the cinema should see in
the app which seats are already taken. Maybe you'd think of using a label for
each seat. However, if the cinema had 15 rows with 30 seats in each row, we'd
have 450 labels. As you might have guessed, there's a better way than adding
JLabel1
, JLabel2
, ... Moreover, how'd we handle these
in this case? If we need to render something more complicated than just one or
two images, we'll use Graphics
. It involves placing a component
(most commonly JPanel
or Canvas
) on the form and
rendering what we need on its canvas.
Let's simplify the application as much as possible. There's no need to have it complicated. It'll only be able to display one auditorium that will be initially empty. The user clicks the taken seats with the mouse and then presses the Save button, which saves a simple txt file to the selected location with the information about the auditorium seat occupancy. We'll try the saving to learn how to work with dialogs.
Designing the Form
Create a new Java Application without the main class, add a new
CinemaJFrame
to it, and set the title to
"Auditorium occupancy record"
. Drag a JPanel
(it's in
the palette's Swing Containers section) across most of the form. Name it
canvasJPanel
. Place a JButton
with the text
"Save"
below the JPanel
and name it as
saveJButton
.
The Logic Layer
You may not be surprised that we'll add an Auditorium
class to
the app. It'll have one private field which will be a two-dimensional array of
seats. If you haven't worked with a 2D array yet, you can imagine it as a table.
A one-dimensional (classic) array is actually just one row. We then work with
the 2D array in exactly the same way as with the one-dimensional. We just have
to specify two coordinates (X and Y). In some languages, there's a 2D array data
type but not in Java. If we want a 2D array, we simply declare it as an array of
arrays. So we have an array (the rows) and there is another array (a column) in
each item of this array. So we end up with a table.
class Auditorium { private boolean[][] seats = new boolean[30][15]; }
The seats are of the boolean
type since we're only interested
whether the seat is taken or not. The width of the array is 30
and
its height is 15
.
We add 2 private constants to the class; one constant specifying the size of the rendered seat in pixels, and the other the space between the seats in pixels. Get used to using constants. When you'll want to increase the seat size, you just change the constant and won't have to mess with the rendering code.
private static final int SIZE = 16; private static final int SPACE = 2;
We can move to methods.
Rendering
The auditorium should be able to render itself. We've already mentioned that
we'll draw on a canvas. The canvas is of the Graphics
type and
we'll accept it as a parameter of a draw()
method. We'll then draw
on the canvas using the methods it provides. For now, it's only the
fillRect()
method we're interested in. It draws a rectangle filled
with a certain color. There's a lot of methods for different geometric shapes,
both filled or outlined. You can take a look at those, we'll try some of them in
the next lessons.
Using two nested loops, we'll go through all the seats in the array and
either draw a green or red square on the canvas. We'll use the outer loop to
iterate over the rows and the inner loop for the columns in the current row.
We'll determine the color by whether the seat at the given coordinate is
true
or false
. The method's code will be as
follows:
public void draw(Graphics g) { for (int j = 0; j < seats[0].length; j++) { for (int i = 0; i < seats.length; i++) { if (seats[i][j]) g.setColor(Color.RED); else g.setColor(Color.GREEN); g.fillRect(i * (SIZE + SPACE), j * (SIZE + SPACE), SIZE, SIZE); } } }
Note that we don't use the values 30
and 15
in the
loops, but seats.length
and seats[0].length
. The first
expression is the width of the array (the number of columns), the second is its
height (the number of rows). (Of course, it's up to us which dimension we choose
to be the height and which the width). Of course, we don't use a fixed size
because we may increase/decrease the array size in the future and we'd have to
search for the usage of values 30
and 15
everywhere in the code instead. It's always better to avoid these problems
and work with the array's length
directly.
The rendering itself is also worth mentioning. We use the
setColor()
method to set the color. It takes one of the
Color
class constants as a parameter. We also use the
fillRect()
method, its parameters are the coordinates of the upper
left corner of the rectangle and its height and width. Since each seat is 16
pixels wide + 2 pixels space, its coordinates must be multiplied by this value.
For example, if i
has the value of 2
(so we're drawing
the third column), we draw on the x-coordinate of 36
, not
2
The same
applies to the y-coordinate.
Later, you can try to replace fillRect()
with the
fillOval()
method. It works exactly the same way but draws an
ellipse. Definitely try to draw other shapes after you finish this
application.
Canvas
We used JPanel
as a canvas for our application. Of course, it's
rendered as a panel, not as cinema seats To change this behavior, we must inherit JPanel
and
override its paintComponent()
method to draw the auditorium
instead.
So we'll add a Canvas
class to the project. Its contents will be
as follows:
public class Canvas extends JPanel { private Auditorium auditorium; public Canvas(Auditorium auditorium) { super(); this.auditorium = auditorium; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); auditorium.draw(g); } }
We pass the auditorium instance to the class constructor. Then we overwrite
the paintComponent()
method to render the auditorium.
Note: Besides the paintComponent()
method, the component
also has the paint()
method, which we can also override and achieve
the same result. That's a little confusing at least. According to the manual, we
should override paintComponent()
as it's the Swing method,
paint()
is from AWT.
Wiring the Form With the Logic Layer
The core logic is done, let's wire it to the form. We'll go to the form code
and create a private Auditorium
instance in the class:
private Auditorium auditorium = new Auditorium();
Now it's necessary to tell NetBeans that the JPanel
, which we've
dragged on the form, shouldn't be of the JPanel
type, but our
modified Canvas
. Select the JPanel
and move to the
Code tab in the Properties window. We'll put the following value in the
Custom Creation Code property:
new Canvas(auditorium);
This will modify the creation code for this panel that NetBeans generates and would look like this otherwise:
new JPanel();
The paintComponent()
event is called by the system when the
window has to be repainted. This is, of course, the case when the application is
started, but also when it's restored from minimization or when we move over
another window over it and so on.
When we run the app, the customized panel really looks like cinema seats:
The panel is still of the JPanel
type. If we
wanted to call some specific methods on it, we'd have to cast
it. We won't need it in our case.
In the next lesson, Handling Click on Coordinates in Java Swing, we'll show how to click on a particular seat to change its state and also how to save it. As always, you can download the attached project in case something doesn't work in your app.
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 28x (27.37 kB)
Application includes source codes in language Java