16 October 2000 Simon Gronlund is earning his Master of Science in Computer Science at the Royal Institute of Technology, Sweden, as an adult student. He also teaches Java and computer-related courses on a senior high school level. When he isn't tampering with his Warp 4 PC, he spends his spare time with his two boys and his wife. If you have a comment about the content of this article, please feel free to vent in the OS/2 eZine discussion forums |
|
Into Java, Part 10
Perceiving that I was writing the tenth
installment of this how-to-program-with-Java I had to sit back and meditate over
how far we actually have come and what the goal will be. This far we are acquainted
with the basic data types of Java, that is most programming languages use similar
data types but not that easy as with Java. We rapidly learned a lot on object oriented
programming (OOP), what a class is and how to instantiate objects from classes,
what inheritance is and how to take advantage of that powerful feature. We have
discussed the event-listener model and will continue with that for a while. |
The modeling will be:
We are interested in the mouseDragged method
since we are interested only in the actions when the mouse's left button is pressed,
and this method is invoked as long as this condition is true. Hence this method
will have the responsibility to update the shape being painted, from the point the
left button was pressed until it is released.
The second method,
mouseMoved, is simply ignored
since we are not interested in mouse movements other than for painting. Thus we
will implement this as an empty method.
There is of course an adapter to
use if we would like to, but having only two methods to implement, of which one
is an empty method, I see no benefit from using that adapter class. Still, if you
are interested, it is named, not surprisingly, MouseMotionAdapter. If you like to recall what adapters are,
please visit installment No 7.
Saving us some coding we will add
an instance of the adapter class as listeners, using the
addMouseListener method.
The other methods we simply ignore,
the adapter will take care of them, but I will mention them for the purpose of this
article. They are three, mouseClicked, mouseEntered and mouseExited. Though their names are pretty straight forward
I will mention that the two latter are raised when the mouse is entering the component
that acts as a listener to mouse events. It is of course possible to have several
such components in an application, each will pay or lose attention as the mouse
moves.
Naturally there is another interface
to use, MouseInputListener, that implement both of the former interfaces in one.
And it has of course an adapter, MouseInputAdapter. This time it is more convenient not to use
it, but that depends and you are the judge.
This class is used by all three kind
of mouse listeners and hence contains information as how many times a mouse button
was clicked and the mouse's (x,y) position. The parent class to this can answer
questions as isAltDown, isShiftDown and many more. Combined with nested if-else
clauses we may figure out many other things, as if it is the left, middle or right
button that is used, we will look into that.
The short summary is, the mouse raises
mouse events. Our canvas has to subscribe to them and will hence be the listener.
If the event is good to us we will use it to paint something. Now we will begin
with the main frame.
|
One of the best methods to develop
as long as you are a beginner, and I still consider myself as such <grin>,
is the evolutionary strategy, begin with as little as possible, and then add to
it, We will do that today, so lets start with the code above. It is plain GUI handicraft
and some stuff we are used to. If you only add a curly brace at the end you may
almost compile this code, but we must add the skeleton of the three classes indicated, SliderPanel, TopButtonPanel and PaintPanel.
But try to compile as soon as you think that is possible and follow the creation
and, naturally, the typos.
The SliderPanel is almost the same
as last month's issue, only change from ColorBox
to PaintBox within that code and that is all. Recycling as its best.
|
As discussed this class will extend
a JPanel to be used as a canvas to paint on. Since we now would like to use the
parent class' ability to use double buffering we call the parent's constructor and
ask for that, super(true). Look it up under JPanel - constructor in the Java
API, please. Since it will implement the MouseMotionListener interface we have to
implement the two methods mouseMoved and mouseDragged, both made empty so far, but the former one
will continue being empty. And when we will use that interface we have to add this
class as a mouse motion listener too.
Finally we see an intention to add
the MouseAdapter. You can omit that for now, but later we will continue with it.
So far we can not compile anything, we only have one finished class and two half-finished.
This class will receive the color updates from the color chooser, will it not? Hence
we have to add a method for that, a setColor. That method will update a variable
holding the actual color. Please note that the opposite is possible, let this class
ask the owner class for the color, that in turn will ask the SliderPanel. But ponder
over that for a while, why will that not be good this time?
Though we have not said much about
how the painting will be done we said it will be painted as you drag the mouse,
hence the shape will be repainted over and over again until you release the left
mouse button. That would be numerous ask-for-the-color calls to an object residing
some place else. Thus it is this time better to hold the color variable and
only update it when the color chooser is used. This is true only if we forecast
that we will do painting more frequently than updating the colors. So, there are
two possible ways to go and which is best? That depends!
Anyway, add the variable private Color paintColor at
the beginning of the body of the class. Within the constructor you may set it to
black, paintColor = Color.black; Now we can add a tiny method anywhere within the class'
body,
The SliderPanel class asks for the same method in the PaintBox class so let us change to the PaintBox class for a while and add the setColor method in PaintBox as well, but mark the line about colorView away for a while, since we have not made it yet, public void setColor(Color c) { paintColor = c; }
Note that we are instantiating a new Color object here, that we send to the PaintPanel object paintArea. public void setColor(int r, int g, int b) { // colorView.setBackground(new Color(r, g, b)); paintArea.setColor(new Color(r, g, b)); }
|
This class implements the ActionListener,
to be able to listen to the buttons. Hence the last line so far will be the actionPerformed method,
so we follow the contract of the implementation. As the last month, we receive a
link, a reference, to the owner class, the PaintBox. Further the constructor instantiates
the declared buttons and the label used to tell which shape is chosen. The GridLayout
is used since it works well with this kind of equal shaped objects, as these buttons
are.
A new gadget used is the shape.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
that uses a practical OOP
concept, the factory pattern. BorderFactory is indeed a factory that can create objects
for special purposes, this time a bevel-edged border, in fact a lowered one. So
far we can try to compile, perhaps you have to add a few curly braces.
|
On the contrary to that easy one
we have the init method
of the same class, it will contain quite a few new classes and ideas, but do not
hesitate.
|
The most work need to be done at
the east coast. And since it was rather tricky to get that side look nice I thought
this time would be perfect to scare you off from using the
GridBagLayout again. The
"Core Java 2" by Horstmann & Cornell states, "the mere
mention of the word "grid bag layout" has been known to strike fear in
the hearts of Java programmers". Still, the most basic use of it is not that
scary.
We will use a container panel that
will have two objects added to it, the SliderPanel known to us, and a simple panel
to view the chosen color. Then you create an instance of the GridBagLayout class
and set it as the layout manager of this container. Further we must have an instance
of GridBagConstraints,
that you may view as a holder of values used by the layout manager. It is, with
its values, shown to the manager as objects are added to the container using this
manager.
The first object is the SliderPanel.
It will be placed in the first column, in the first row, that is 0, 0 in most languages
of this world. Then we simply add the object and show our set of values. Continue
with the panel colorView,
initially black, that is is still in the first column but in the second row. The ipadx and ipady are
some magic values that set the size of the color panel. Add the object to the container
and show our hand. Finally add the container to the content pane's east side. Not
that horrible, was it?
Continue with the TopButtonPanel,
that need the reference back to this class. And finally the PaintPanel, that need
no reference. This constructor is done and finished. And you may remove the comment
mark from the setColor method
since now the colorView exists.
And then you add the following lines to the code indicated earlier. private int startX; private int startY; private int stopX; private int stopY;
|
Bitwise operators There are four bitwise operators, named and , or , xor and
not respectively. I will show only one, the and operator that
is used this time. Think of two rather short integers, in binary representation,
that are ANDed with each other x = 0000 1111 0101The result reflects only those bits that are 1 in both x and y. It is rather obvious what or will do. If y is a mask, you may test
if the result of that mask on x gives the desired output, in the case above it is
17 in the decimal representation. |
Note that when we release the mouse
button we must call the repaint method. It is the system that decides when it is time
to render the application again, if it was hidden behind another application for
a while, if you resized the frame or whatever. But if we like to force a repaint
we may never call the paint or paintComponent methods ourselves. No, we call the repaint method
and when the system thinks it is about time to listen to you, it calls the paint and paintComponent methods,
and conveniently sends a Graphics object along that we may play with. Wait a second
and you will see.
Now we are done with the starting
and ending points, only the interactive painting remains. But so far you may put
a System.out.println("Whatever " +
startX); or similar, were
ever you want and see that this really works.
public void mouseDragged(MouseEvent me) { stopX = me.getX(); stopY = me.getY(); repaint(); }
Now a line may be painted. Hefty! We clearly see that as we drag the mouse the line is painted from the starting point to were we are now. Move the mouse faster and we can almost see that the update struggles hard to keep in pace. Maybe you use a CPU meter, I think it will rise a bit. That is because we try to repaint every single step the mouse moves. An improvement would be to waste quite a few events and repaint only a few a second, I leave that to your home study. But what about those other shapes? public void paintComponent(Graphics g) { super.paintComponent(g); Color oldColor = g.getColor(); g.setColor(paintColor); g.drawLine(startX, startY, stopX, stopY); g.setColor(oldColor); }
Before we do that we can plan ahead
and add a few things to the PaintPanel class. First we may add a few more variables
to the class' body.
The public static variables may be used by other classes, but then by their descriptive names, as BUTTON1_MASK is, or LOWERED. Internally they have integer values, but here we really know what these are substituting, developers of other classes may not even see our code, but may still use these descriptive aliases. We will see how in the TopButtonPanel. /* static class variables to determine shapes */ public static int LINE = 0; public static int RECTANGLE = 1; public static int OVAL = 2; private boolean filledShape = true; private int shape = 1; // default is rectangle
The boolean
filledShape is toggled by
the button "Filled"/"Not filled", also located at the TopButtonPanel.
Finally shape = 1 is a default value, that will be used by the paintComponent in
a few moments. But how will filledShape and shape be set by the user interaction? We know that
the messages will come from the TopButtonPanel, but how will it be forwarded from
there to here?
We need to add two methods to both
the PaintPanel and the PaintBox. We start with the PaintPanel since we are there
now. The methods will receive one parameter each and set the value of filledShape and shape.
That is all, the parameters are catched and used to set the variables. Both variables are initiated at construction time, else it may cause troubles upon runtime. public void setFilled(boolean b) { filledShape = b; } public void setShape(int s) { shape = s; }
Now this side will work as expected, there are methods to call, and they will forward the parameters to the proper place. public void setFilled(boolean b) { paintArea.setFilled(b); } public void setShape(int s) { paintArea.setShape(s); }
|
We are used to the first line since
previous issues, it will get a reference to the source of the action, that is any
of the buttons. Then we test for which button it is. The first test itself inlines
another test. Since the filledButton will toggle itself and toggle the variable
in PaintPanel we have to do this. The first block is used when the button is marked
"Filled" but we want to set it "Not filled", that is, the variable filled is true but
we want to set it false. And the second block is the opposite.
If the source was not filledButton we
continue with the other test cases until we find the correct button. Note that the
final block does not need a test since we have only added these four objects to
the action listener, and if it is not one of the three, it must be the fourth. Further
recall that if any one of the buttons is used more frequently than the others, put
that one first in the chain, since no more tests are done when one was found true
when using if-else-tests. A common mistake is to write several if-tests one after
another. It works, but then every test is actually done. Hence else-if is
the smartest coding style whenever only one option out of several is true.
Now we clearly can see how we use
the static aliases. In this class we do not need to know that
PaintPanel.LINE is actually
an integer and what value it is given. The code is clear and very readable, your
fellow coding the PaintPanel class, or if you found that class but not its source
code having only the class file, you can still write your code as long as you have
the class' API. Further, even if you have the source code at hand, you do not have
to look it up to figure out which int value you used.
Using that kind of static aliases -- public static int VARIABLE = 0 -- is very common and you will find it many times in the
Java API. Then look at the "Field Summary" of the class referred to and
there they are.
|
We recognize six of the lines, five
at the beginning, g.drawLine now being enclosed by an if-clause, and the last line.
In this method we can use the static constants if we like, but only to spell out
that they are in fact nothing but integers, though disguised using unmistakable
names, I have used their true integer values in the if-tests here,
shape being zero equals
the PaintPanel.LINE.
Since lines are painted using start
point to stop point, we do not need to do anything with the values used. But the
other shapes used are painted from the start point, but then the other values are
the width and the height. Hence we have to subtract the start point from the stop
values. Remember points are counted from the upper left corner of the actual component.
The start point is always okay, but the width have to be the horizontal distance
from the start point to the end point, not the horizontal distance from the upper
left corner to the mouse stop point, as stopX and stopY
actually are. You may perfectly
well comment these lines out and see what happens, no harm is done.
Then we will test if a rectangle
or an oval is chosen, and thereafter if the form will be filled or not. That is
all folks, I hope the application pleases you.
|
This is the second time I have not
linked to ready written classes, since I think you will learn more from typing yourself,
and it is much more instructive to read the error messages from javac yourself and
figure out what is wrong, than using ready made, error free code from a file not
your own. But if I am wrong, or if there are other comments, feel welcome to let
me know, preferably using the forum since many more may be interested in the discussion.
The code above have some limitations,
hence you will have this home work to do:
|
|
Previous Article |
Home |
Next Article |