![]() ![]() 16 October 2000 ![]() |
![]() |
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:
MouseMotionListener interface
is located in the package java.awt.event
. The Java API simply states "The listener interface for receiving
mouse motion events on a component. (For clicks and other mouse events, use the MouseListener.)" Since we
are waiting for mouse movements when mouse button number one (I will from now on say "the left button")
is pressed, we will use this interface. We then have to add this class as the listener using
addMouseMotionListener(..)
, and we have to
implement the two methods mouseDragged
and mouseMoved
.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.
MouseListener interface
with its accompanying adapter class, the MouseListenerAdapter
. The Java API says: "The listener interface for receiving "interesting"
mouse events (press, release, click, enter, and exit) on a component. (To track mouse moves and mouse drags, use
the MouseMotionListener.)" We are interested in two of them, the mousePressed
and the mouseReleased
methods, since we are, as stated, interested in were the left button was
pressed and were it is released. The start and the stop points will give the main corners of the border enclosing
the shape painted.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.
MouseEvent
class that encapsulates the information possible to
get from the mouse. As any event it instantiates objects at the source, this time at the mouse. This object is
then sent to every listener subscribing to these event objects. The listener may then decide what to do, or not
to do.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 thepublic void setColor(Color c) { paintColor = c; }
setColor
method in PaintBox as well, but mark the line about
colorView
away for a while, since we have not made it
yet,Note that we are instantiating a new Color object here, that we send to the PaintPanel objectpublic void setColor(int r, int g, int b) { // colorView.setBackground(new Color(r, g, b)); paintArea.setColor(new Color(r, g, b)); }
paintArea
.
|
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 The 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. |
BUTTON1_MASK
. The test is designed to work with these masks, and if the output is not
zero it is what you want, this time the left button is used. Else we will not do anything.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.
mouseDragged
method
is intended to catch them. Dragged is by definition the mouse moved with one of its buttons pressed down, actually
any of them works. From the event object catched we parse the position, its x and y, and ask the system to repaint.public void mouseDragged(MouseEvent me) { stopX = me.getX(); stopY = me.getY(); repaint(); }
paintComponent
method
mentioned. Add such a method to your PaintPanel class. It is good practise to always make
a call to the parent class that we inherited from, if the object needs a refresh, hence
super.paintComponent(g)
. Further it is good to save
the color held by the Graphics object, so we can be polite and give it back when it leaves us.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/* 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
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.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; }
paintArea
.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); }
actionPerformed
in TopButtonPanel, hence it will do
nothing when clicking the buttons, it is an empty method. Such strategy is very convenient upon evolutionary developing,
you now can see how the interface looks like, but there is no interaction. Though, we have planned for that and
added some variables and methods in PaintPanel and PaintBox that we will use now.
|
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.
paintComponent
and
make the method be aware of which shape we have chosen.
|
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.
InputEvent.BUTTON1_MASK, SwingConstants.VERTICAL
and BevelBorder.LOWERED
as examples. We
have used some more methods from the Graphics class
, but there are more methods to experiment with for the curious reader. The mouse
action interfaces have been discussed and used. Some techniques of picking options are used and how to let other
classes know the result. We have touched the GridBagLayout class, but hardly used its more complex abilities.
We have used the factory pattern, a very useful technique of OOP.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 |
|
Next Article |