So I am cruising around the Dream.In.Code boards when I stumble across an interesting post about creating a shapes tool for a graphics Java program. If you are unfamiliar with the shapes tool, the idea is that you click a button, select a shape and then draw the shape onto some window of the program. Select a square, an ellipse or some fancy star and KAPOWY! you have a masterpiece in the making. I figured “Wow, this is a perfect opportunity to show how we can implement an interface for the various shapes and…. the idea of a picture tube as well!” I will explain the concept on this entry of the Programming Underground!
So how would we go about designing a system where the user could essentially load a ton of shapes in it, allow the user to build their own crazy shapes and plug them right into the code for drawing with little effort?
Coming up with the answer, I knew there would have to be a way to have the program understand all sorts of classes that the programmer may create, yet know that they have the basic functionality to at least draw themselves given a location. We don’t care what kind of monstrosity the programmer has come up with as long as it draws. Sounds like the job of an interface!
Interfaces are great in that they allow you to define all sorts of classes and, as long as they agree to support certain functions, can play nice with the rest of the objects. For our little program we are going to create a couple shape classes. One being an Ellipse and the other being a Square. We are going to make sure that these two classes know how to draw themselves despite their drawing difference underneath the hood. The Ellipse class is going to use drawOval and the Square class is going to use drawRect respectively.
To kick off the program we will create a standard program which inherits from a JPanel. This will be our canvas for drawing on. But we could essentially pass along any type of Graphics object to our setup and have the “figures” (as we will now call them) draw anywhere. The one thing we have to make sure all figures can do is implement a method called “draw()” which will take in a Graphics object to let the figure know where to draw.
So we create an interface called iDraw to help us do that. Interfaces are like contracts which state that any class which implements the interface MUST support certain functions defined in that interface. If our interface says that a class implementing the iDraw interface MUST have a method called “draw()” defined and that the draw method has to take in a Graphics object, x/y coordinate and width/height, there is no ifs ands or buts about it.
It looks something like this…
import javax.swing.*; import java.awt.*; import java.util.ArrayList; public class Drawtest extends JPanel { // Arraylist of who knows what, but at least they know how to draw private ArrayList<iDraw> figures = new ArrayList<iDraw>(); public Drawtest() { super(); } // Add some object that implements the iDraw interface to our list of figures public void add(iDraw figure) { figures.add(figure); } public static void main(String[] args) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setTitle("Shapes example"); frame.setSize(300,300); // Create a panel to draw on Drawtest panel = new Drawtest(); // Add some shapes to our panel panel.add(new Ellipse()); panel.add(new Square()); frame.add(panel); frame.setVisible(true); } // Controls painting public void paintComponent(Graphics g) { super.paintComponent(g); // Now, we choose where to paint those shapes to using our stored shapes // Draw the first figure at 50,50 with the width and height being 20 figures.get(0).draw(g, 50, 50, 20, 20); // Again, we don't know what this figure is, but we know it can draw so draw it at 100, 100 in a bounding box of 50 x 70 figures.get(1).draw(g, 100, 100, 50, 70); } } // Class for ellipse that impliments the iDraw interface class Ellipse implements iDraw { public void draw(Graphics g, int x, int y, int w, int h) { g.drawOval(x, y, w, h); } } // Class for square that also implements the iDraw interface class Square implements iDraw { public void draw(Graphics g, int x, int y, int w, int h) { g.drawRect(x, y, w, w); } } // Interface that forces all objects that implement it to create a custom implementation of draw interface iDraw { void draw(Graphics g, int x, int y, int w, int h); }
So in the code above we have defined an interface called iDraw that states “any class implementing iDraw will have a draw() method”. We then continue by creating our two classes Ellipse and Square and say they implement iDraw. Meaning they will have to create their own draw() methods that meet their contractual obligation to the interface.
Now this is important to understand. Despite what class they might be if they implement iDraw we KNOW FOR SURE that they at least have the draw() method! A powerful assumption.
So we can go ahead and make an ArrayList of iDraw interfaced objects. Java will know nothing about the objects themselves, it only knows that each object has an iDraw interface so it can be used to call the draw() method.
In our main method we create a JFrame, then an instance of our Drawtest class and using a method called “add” to add a figure to our Arraylist of iDraw supporting objects. We can create an Ellipse and pass it to the Drawtest class. We can create a Square and give it to a Drawtest class and Java will accept them all because they support iDraw.
Here is where the cool idea of picture tubes comes in. If we create a class called “ChuckNorris” and say that it implements an iDraw interface, that means ChuckNorris will have a draw() method like the rest. In that method we could use something like drawImage() from the Graphics object (remember we pass that into the method draw) and draw a little picture of Chucky boy kicking butt! Then we can give him to the Drawtest class like any other object and he will be added with the Ellipse and the Square.
In our paintComponent method, we can then read each object and tell it where to paint and its dimensions by calling draw() and giving it the Graphics object of the JPanel along with its coordinates. That means we could draw our Ellipse at coordinates 50,50 and our Square at 100,100 or even Chucky larger than life at 23, 45 with a size of 150 x 150. Knocking out all the other figures.
Our ArrayList will allow us to store all the figures we want as long that they implement the interface. We can add other classes to it for drawing and in the paintComponent we could even loop through the figures to create a crowd for a video game. Or we can drag the image around by redrawing the same shape over and over again. We could create a rubber stamp effect by having a mouseListener following the user’s mouse and when they click it draw the appropriate image at the mouse x/y location.
Now there is a slightly different way we could have done this using a base class that we inherit our shapes from etc. The problem with that method is then we bind all classes to having to inherit from “Shape” rather than allowing the programmer to create “ANY” class. So Chucky would have had to inherit from a shape and really, I don’t think he would have liked that. He is more complex than the rest of them squares. It also limits the future possibilities for adding other classes to the mix. What about our picture tubes? What if we wanted to instead inherit from the BufferedImage class and put it into the mix? At least this way we could inherit from BufferedImage, give it an iDraw interface and draw() method and throw it into our ArrayList.
The possibilities are endless! I hope you find this code useful and feel free to part it out like you were a chop shop. All code here on the Underground is open to the public to pimp out. Thank you again for reading! 🙂