Into Java - Part X

From EDM2
Jump to: navigation, search
Into Java / Part
I II III IV V VI VII VIII IX X XI XII
XIII IV XV XVI XVII XVIII XIX XX XXI XXII XXIII

By Simon Gronlund

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.

To all these theoretical stuff we have made ourselves tiny applications, some of them not useful at all, other with some value added to them. What will the future bring to us? We have to look into the extremely valuable exception handling model of Java, very soon. What is computer programming without reading and writing files to the hard disk or whatever you want to read from or write to? How to do multithreading will come, and much more. There is in fact no end to this series as both Java and the entire computer universe evolves that fast. So, stay tuned to OS/2 e-Zine.

This month we will make ourselves a tiny PaintBox, not even close to the old PaintBrush, but still our application will give us some knowledge on the Graphics class and how the mouse event-listener model works. Though Swing introduced advanced graphical tools to us, some details you have to implement yourself, and we will learn more on that now.

The Mouse Listener Interfaces

This far we have used two Swing components that encapsulates the most of the event handling and we only have had to catch the ActionEvent object sent from JButtons and JSliders. This time we will continue somewhat closer to the underlying mechanisms when catching the mouse events and see how complex such handling can be from only a mere PaintBox. Our goal is to use the mouse to draw the borders of simple objects as lines, rectangles and ovals, as is done in most applications. As usual we will first have to choose the kind of object we will draw, but that will be handled by a plain panel of buttons.

The modeling will be:

  • a main frame that only holds the components
  • a color chooser, reuse the one from the September issue
  • a panel of buttons to choose the kind of object to draw
  • a panel that is used as the painting canvas

The last panel will be the one listening to the mouse movements and draw the result simultaneously, hence that panel will inherit from JPanel and will implement MouseMotionListener interface and also use the MouseListener interface internally. This class will be by far the most complex this installment and also of all installments so far, so let us begin.

MouseMotionListener interface

The 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 Part VII.

MouseListener interface

The other interesting interface used when listening to the mouse is the 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

Finally we have to look at the 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.

PaintBox class

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.

PaintPanel

The PaintPanel is the heart of this installment and we will start with a small beginning.

PaintPanel class

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,

public void setColor(Color c) {
    paintColor = c;
}

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(int r, int g, int b) {
    // colorView.setBackground(new Color(r, g, b));
    paintArea.setColor(new Color(r, g, b));
}

Note that we are instantiating a new Color object here, that we send to the PaintPanel object paintArea.

TopButtonPanel

So far we will only type the beginning of the last class, TopButtonPanel, that will hold some buttons and stuff.

TopButtonPanel class

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.

TopButtonPanel view

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.

firstInit

Last time I menaced to lift out part of the main frame's constructor and put that code in a private method of its own. That is actually a good idea since that part of the code is more or less the same each time we make ourselves a GUI application. So from now on I will not repeat this code, only point to it and suggest changes to it. This time there is not much to say about this method, only that it of course is located within the PaintBox class.

PaintBox class

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.

PaintBox class

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.

Back to PaintPanel

Any shape we like to paint has a start point and a stop point, and since this object need to remember them for later use we must catch these values and store them. Hence you now have to add four variables to the declaration list first in the body of the PaintPanel class.

private int startX;
private int startY;
private int stopX;
private int stopY;

And then you add the following lines to the code indicated earlier.

PaintPanel class

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 0101
y = 1010 0001 0011
x & y = 0000 0001 0001

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.

The mouse event contains valuable information from the event source, but since we are only interested in some of these we set up a filter and though it looks magic it is not. The modifier is an integer that is ANDed to another integer, named 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

This code is pretty simple, as the mouse is dragged the mouse raises events and the 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

But now a question comes to our minds, what will tell the system what shape we are thinking of? The 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.

public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Color oldColor = g.getColor();
    g.setColor(paintColor);

    g.drawLine(startX, startY, stopX, stopY);

    g.setColor(oldColor);
}

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?

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.

/* 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 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.

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.

public void setFilled(boolean b) {
    filledShape = b;
}

public void setShape(int s) {
    shape = s;
}

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.

Add to PaintBox

Now as we have the receiver of parameters, we have to implement the forwarding methods. Else the TopButtonPanel will not know were to send the user input. This works almost the same as the methods above, but they are constructed to forward the parameters to the PaintPanel object, named paintArea.

public void setFilled(boolean b) {
    paintArea.setFilled(b);
}

public void setShape(int s) {
    paintArea.setShape(s);
}

Now this side will work as expected, there are methods to call, and they will forward the parameters to the proper place.

Back to TopButtonPanel

Until now we have not added anything to the 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.

TopButtonPanel class

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</font> 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.

Again back to PaintPanel

All the other classes are now complete, but compiling and running the application will behave as expected, the color will change, but we can only paint a line. We have to change the PaintPanel's paintComponent and make the method be aware of which shape we have chosen.

PaintPanel class

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.

Summary

Today we have written three of static constants ourselves, used 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:

  • Why can rectangles and ovals only be painted from upper left to lower right?
    • How to correct that bug?
  • Why is it only possible to paint one shape, and that one is lost when you paint another?
    • How to improve the application to let any number of shapes being painted?
  • Is it possible to let the outlines from ovals be painted simultaneously, until it is completed?

Next time we will add to this frame to answer the questions. We will also discuss the OOP topic of overriding, since we have used the technique but it is not explained yet. CU around.