Homework 5: Event-Driven Software in Java Swing
Due Date: See class schedule
The learning goals of this assignment are to gain experience in using the “coordinated callbacks” design pattern we discussed in class, to gain understanding of the fundamentals of common GUI toolkit structures, and to get practice using Java.
For this assignment we’ll be creating a simple Java-based drawing program, similar to one we did in class during the Processing lectures, which allows the user to draw rectangles on the screen. We’ll also be implementing the common “rubber banding” interaction technique, which shows a live preview of an in-progress rectangle while it’s being drawn, giving the user feedback about its ultimate position and size on screen.
Note: while this assignment is quite short in terms of number of lines of code, the way the code will be structured may be unfamiliar to you if you’re not used to asynchronous, event-driven programming. In particular, this assignment will require multiple callback functions (called listeners in Java) that cooperate together to implement the required behavior. This, coupled with the compiled nature of Java itself, means you should not put this assignment off until the last minute.
Your program will create a window that contains a blank canvas area, and a button that allows the user to clear the canvas. When the user clicks and drags the mouse inside the canvas area, the program should interactively display a rectangle via “rubber banding” (that is, once the user establishes the first point of the rectangle, the size of the rectangle tracks the user’s mouse position for as long as the mouse button is held). When the user releases the mouse button, the new rectangle is displayed in its final position. The user can then perform the same interaction again to add further rectangles to the canvas.
Clicking the “clear” button will remove all of the rectangles, allowing the user to start over. Closing the window should terminate the program. Your window should be resizable and otherwise “behave nicely” as one would expect a normal program to do (for example, if you shrink and enlarge the window, any previously drawn content should still be visible).
From an implementation perspective, here’s how you should approach this problem.
First, create the window and get the basic window layout working. You should use a Swing JFrame component for your window. My recommendation is to then use BorderLayout as your layout manager, with your canvas (described below) in the CENTER position and your button (which will be a Swing JButton) either in the NORTH or SOUTH position. Make sure your window displays properly, is closable, resizable, and so forth. You will probably want to set a preferred size for your canvas as well. Test that the button’s listener is being called correctly.
My recommendation is to put the code that creates and sets up the overall user interface in a class called HW5 that has a public static void main() method that launches the application.
Next, create the class that will serve as your drawing canvas. Note that for this assignment, you must create a custom class for your canvas. My recommendation is to subclass the JPanel component. In order for your new Canvas component to work with the rest of your application, it will need to do several things:
- Implement the MouseListener and MouseMotionListener interfaces, which define methods that will be called when mouse events happen. (Several of the methods defined by these interfaces may not be used in your program, but you’ll still have to provide implementations of them; just make them “empty” methods that do nothing in these cases.)
- The canvas should register itself as the listener for mouse and mouse motion events. A good place to do this is in the Canvas’s constructor, where you might do addMouseListener(this) and addMouseMotionListener(this).
- Decide on the external API that your Canvas class will expose—this is the set of public methods that may be called from outside the Canvas code. For example, you will certainly want some way for the clear button to clear out your canvas, so you might add a public clear() method that’s called from the button’s listener. There may be others depending on how you architect your code.
- Implement the paintComponent() method—this is the code that Swing will call when your Canvas is drawn. This is the only place you should put your drawing code.
The trickiest part of this assignment is coordinating among the various listener functions to do the drawing. Remember that a drawing starts when the mouse is first pressed, is rendered interactively as the mouse is dragged around, and then is completed when the mouse is released. This means that your mouse pressed listener will likely need to record the starting coordinates of the new rectangle, your mouse dragged listener will need to update the current ending coordinates of the rectangle, and your mouse released listener will need to finalize the new rectangle’s coordinates.
Remember that you can’t draw directly in your listeners! Instead, your listener code will update data structures that indicate what to draw, and then call the repaint() method. This signals to Swing that it should start the redrawing process, and ultimately your Canvas’s paintComponent() method will be called. In that method, you should render all of the rectangles drawn by the user, including any past rectangles as well as any current in-progress one.
A few hints for this assignment:
- You may find it helpful to create a new custom class that contains the data for each rectangle, and a method that draws it. For example, when I implemented this homework, I created a class called DrawableRectangle that keeps track of the rectangle’s coordinates, and has a method called draw() that will paint the rectangle on the screen.
- You’ll need to keep a data structure, often called a “display list,” that keeps track of what will be drawn in your paintComponent method. For example, if you define your own class to keep information about each rectangle, you might use an ArrayList to keep track of all of these, such as :
ArrayList<DrawableRectangle> displayList = new ArrayList<DrawableRectangle>();
Then, in your paintComponent() method, you’d just iterate over all of these, calling the draw method on each that you define.
Turn-in Instructions
Please create a ZIP file with the code for this assignment. This ZIP file should contain both your .java source code and the resulting .class files that are generated by the Java compiler. Just ZIP the folder that contains your code and submit this, optionally with a README file if there’s anything we need to know about your program.
We will expect there to be a main method on your HW5 class that we can use to start the program.