Advanced Java Unit 6: Review of Graphics and Events This is a review of the basics of writing a java program that has a graphical interface. To keep things simple, all of the graphics programs will follow a similar pattern: Our main class will extend JFrame. We will add one JPanel object to the JFrame and use the null layout to simplify placement of buttons, text fields, etc. When/if we need somewhere to draw then we will write a class that extends JPanel, create one object of that class and add it to the JPanel that is in the JFrame. Example 1. Copy DemoProgram and Convert classes and then execute the main method. Here is the code from the DemoProgram along with explanations for some of the code. import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Demo_Frame extends JFrame { private JTextField txtmph, txtfps; // instance variables so the inner class can use them public static void main(string[] args) { // main method here to be more concise Demo_Frame f = new Demo_Frame(); f.display(); public Demo_Frame(){ setdefaultcloseoperation(exit_on_close); // quits when X is clicked settitle( "Convert" ); JPanel jp = new JPanel(); jp.setbackground( Color.WHITE ); jp.setpreferredsize( new Dimension( 225, 165 ) ); // sets panel s width, height jp.setlayout( null ); // see page 3 txtmph = new JTextField(); txtmph.setbounds( 25, 25, 100, 25 ); JLabel lblmph = new JLabel("MPH"); lblmph.setbounds( 150, 25, 40, 25 ); // sets x, y, width, and height JButton button = new JButton( "Convert MPH to FPS" ); button.setbounds( 25, 65, 150, 35 ); button.addactionlistener( new ButtonListener() ); The argument to addactionlistener must be an object that implements the ActionListener interface. The ButtonListener class is defined below. 1
txtfps = new JTextField(); txtfps.setbounds( 25, 115, 100, 25 ); txtfps.seteditable( false ); JLabel lblfps = new JLabel("FPS"); lblfps.setbounds( 150, 115, 40, 25 ); jp.add( txtmph ); jp.add( lblmph ); jp.add( button ); jp.add( txtfps ); jp.add( lblfps ); // adds each component to the panel getcontentpane().add( jp ); // adds the panel to the frame pack(); // causes the frame to wrap and resize around the panel public void display() { EventQueue.invokeLater(new Runnable() { public void run() { setvisible(true); ); When using Swing classes, all code that modifies the user interface must/should run in the event dispatching thread. See the link below for more details. private class ButtonListener implements ActionListener{ public void actionperformed(actionevent e) { String input = txtmph.gettext(); if (!isinteger( input ) ) txtfps.settext( "Invalid Input" ); else { int mph = Integer.parseInt( input ); double fps = Convert.mphToFPS( mph ); txtfps.settext( fps + "" ); private boolean isinteger( String s ){ try { int num = Integer.parseInt(s); catch( NumberFormatException ex ) { return false; return true; This is an inner class. It has access to its outer class s instance variables and methods. We use an inner class so that it has access to its outer class s instance variables and methods. It is private because the outer class is the only class that should use this inner class. For an explanation about the code in the display method. http://stackoverflow.com/questions/4908824/explain-what-the-following-code-does 2
What is this? jp.setlayout( null ); Java typically uses Layout Manager objects that are responsible for managing the positions and sizes of different components. For example, the GridLayout manager will keep all components the same size and in a grid arrangement even if the frame/panel is resized. Calling the setlayout method with an argument of null means there is no layout manager and we are responsible for positioning everything. Use the setbounds method to position and size any components added to a JPanel. The Coordinate System used by java is similar (but not identical) to the one used in algebra and geometry. -y Each panel has its own coordinate system where the upper-left hand corner is the origin. -x +x The top edge of the panel is the positive x- axis and the left edge of the panel is the positive (not the negative) y-axis. Coordinates can have negative values. +y Here s a figure that shows some of the subclasses of JComponent. Any method that JComponent has, such as setbackground, are inherited by JButton, JPanel, and so on. JComponent AbstractButton JPanel JLabel JTextComponent JButton JTextField 3
Ex 1. Create the program that finds the roots of a quadratic equation that is in standard form. The coefficients A, B, and C are all integer values (and assume that the user enters integer values). Complete and use the following class to handle the finding the roots. public class Roots{ public static double [] findroots(int a, int b, int c){ If there are no real roots, return null. If there is only one root, return an array of length 1. If there are two roots, return an array of length 2. Hint. I strongly recommend that you first find the discriminant and then take it from there. Regarding the layout The labels, text fields, and button all have a height of 25 pixels. There is a vertical separation of 5 pixels between each row except around the button where I made the vertical separation a little larger. You don t have to use these numbers in formatting the page. 4
Ex 2. This project is a bit more complicated. It is similar to TicTacToe except It is played on a four by four grid. A player must get four in a row (vertically, horizontally, or along one of the two diagonals. IMPORTANT. When a player clicks on their sixth square, the oldest square (the one they clicked on first) will be cleared. No player will ever have more than five marks on the grid at any one time. To be honest, it s not that great a game. Feel free to limit each player to a max of 6 or 7 squares instead of 5 if you think that plays better. Here s an outline of the two classes. import javax.swing.*; import java.awt.*; import java.awt.event.*; public class TicTac16_Frame extends JFrame { private JButton[][] btns = new JButton[4][4]; private JButton btnstart = new JButton( "Start" ); private Game16_Logic game = new Game16_Logic(); public static void main(string[] args) { TicTac16_Frame f = new TicTac16_Frame(); f.display(); public void display() { EventQueue.invokeLater(new Runnable() { public void run() { setvisible(true); ); public TicTac16_Frame(){ Set up the frame. Create the 17 buttons and place them on the JPanel that is added to the JFrame Change the background color of each button (does not have be orange) setbackground( Color.ORANGE ); Be sure to add listeners to each button. Pay attention to the listener constructor When adding the listener to btnstart, the row and column values do not matter. 5
private class ButtonListener implements ActionListener{ private int row, col; public ButtonListener( int r, int c ){ row = r; col = c; public void actionperformed(actionevent e) { JButton b = (JButton) e.getsource(); // useful method if ( b.equals( btnstart ) ){ game.init(); reset the buttons to their original look else if (!game.makemove( row, col ) ) return; // if the move did not occur, then do nothing Call the status method from the Game16_Logic Update all the buttons because two buttons may change with one click You may display Xs and Os or the actual numbers, whichever seems better int [][] results = game.haswinner(); if ( results!= null ) { change the background of the winning buttons to green NOTE. You may want to add helper methods to TicTac16_Frame or ButtonListener. For the game logic, I place odd numbers wherever player one has clicked and even numbers wherever player 2 clicks. At a certain point I remove the smallest even or odd number on the grid (depending on who s turn it is). Also, the player with the smaller value is the next one to make a move. Since I start player1 at 1, player1 is the first one to move. public class Game16_Logic{ private int [][] squares = new int[4][4]; private int player1 = 1; // these are the next values to place private int player2 = 2; private boolean game_on = false; public void init(){ squares = new int[4][4]; player1 = 1; player2 = 2; game_on = true; public int[][] status(){ return squares.clone(); // returns a copy of the grid contents 6
public boolean makemove( int row, int col ){ if (!game_on!legalmove( row, col ) ) return false; // indicates that the move was legal if (player1 < player2 ){ if ( player1 > 9 ) // player1 is always odd clearsquare( player1-10 ); squares[row][col] = player1; player1 += 2; else { if ( player2 > 10 ) // player2 is always even clearsquare( player2-10 ); squares[row][col] = player2; player2 += 2; return true; private void clearsquare( int num ){ find the element in the grid that has a value of num and set it to zero private boolean legalmove( int row, int col ){ To return true, (1) row and col must be valid values and (2) squares[row][col] must currently have a value of zero (indicating that the square is unoccupied). Otherwise the method returns false. public int[][] haswinner(){ If there are four square in a row (horizontally, vertically, or diagonally) then return a two-dimensional array containing the locations of these squares. Otherwise return null. You want to use helper methods. 7
Simple Animation. Copy and run the following program. This uses a Timer object to generate action events at regular intervals. Explanations for certain statements can be found on the next page. import javax.swing.*; import java.awt.*; import java.awt.event.*; public class DemoTimerFrame extends JFrame { private JLabel lbl = new JLabel( "0" ); private JButton btnstart = new JButton( "Start" ); private JButton btnstop = new JButton( "Stop" ); private JPanel jp = new JPanel(); private int count = 0; private javax.swing.timer timer; A public static void main(string[] args) { DemoTimerFrame f = new DemoTimerFrame(); f.display(); public DemoTimerFrame(){ setdefaultcloseoperation(exit_on_close); settitle( "Timer" ); jp.setbackground( Color.WHITE ); jp.setpreferredsize( new Dimension( 150, 150 ) ); jp.setlayout( null ); btnstart.setbounds( 10, 5, 130, 30 ); lbl.setbounds( 60, 50, 90, 30 ); Font f = new Font("SansSerif", Font.BOLD, 36); lbl.setfont( f ); btnstop.setbounds( 10, 100, 130, 30 ); btnstart.addactionlistener( new Respond() ); btnstop.addactionlistener( new Respond() ); B timer = new javax.swing.timer( 100, new TimerListener() ); jp.add( btnstart ); jp.add( lbl ); jp.add( btnstop ); getcontentpane().add( jp ); pack(); C public void display() { EventQueue.invokeLater(new Runnable() { public void run() { setvisible(true); ); // continued on the next page 8
private class TimerListener implements ActionListener { public void actionperformed(actionevent e) { count++; lbl.settext( count + "" ); private class Respond implements ActionListener{ public void actionperformed(actionevent e) { if ( e.getsource().equals( btnstart ) ) timer.start(); else timer.stop(); D E Explanations. A private javax.swing.timer timer; Java has two different Timer classes; one is in the java.util package and one is in the javax.swing package. This syntax makes it clear to the compiler that we want the Timer class from the javax.swing package. We will not use the other Timer class. If you are curious why Java has two Timer classes: http://stackoverflow.com/questions/15556646/why-are-there-twotimer-classes-in-javaone-under-javax-swing-one-under-java-ut B Font f = new Font("SansSerif", Font.BOLD, 36); lbl.setfont( f ); This demonstrates how to change the font type of a JLabel. You could use the same approach on the buttons. Look up the Font class for more information. C new javax.swing.timer( 100, new TimerListener() ); The constructor for the Timer class has two parameters. First, the amount of time, in milliseconds, between events. Second, an object that will respond to the Action Events that a Timer object generates. D private class TimerListener implements ActionListener A Timer object generates Action events at regular intervals therefore the object that responds to those events must implement the ActionListener interface. I used a separate inner class for the timer events because I didn t think the same object should handle action events from buttons and action events from a timer. E timer.start(); timer.stop(); The start method causes the Timer object to start generating action events at the specified interval. The stop method causes the Timer object to stop generating action events. 9
Ex 3. This is really cool. You will revise your earlier program so that when the Start Animated Play button is clicked, the computer will randomly click on the buttons until someone one wins. Step 0. Do NOT change Game16_Logic class. Step 1. Copy and rename the TicTac16_Frame. Step 2. Add a timer object with its own private listener class. Every time its actionperformed method is called, randomly select one of the unoccupied squares. Then use this statement: btns[ r ][ c ].doclick(); where r and c are the random row and column that you selected. The doclick event acts the same way as if the user clicked on that button. Step 3. When the Start button is clicked, start the timer. When someone wins, stop the timer. Mouse Events and Drawing on a JPanel. Here is a short demonstrations of how to handle mouse events and draw on a panel. In this example, all the work is being done is a class that extends JPanel. The DemoMouseDrawFrame class is online. It has the main method, creates a frame and adds two DrawMousePanel objects to it. 10
import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.arraylist; public class DrawMousePanel extends JPanel { private ArrayList<Point> pts = new ArrayList<Point>(); public DrawMousePanel( Color c ){ setbackground( c ); setborder( BorderFactory.createLineBorder(Color.BLACK ) ); setlayout( null ); Mickey m = new Mickey(); addmouselistener( m ); addmousemotionlistener( m ); @Override public void paintcomponent( Graphics g ){ super.paintcomponent( g ); for ( int k = 0; k < pts.size()-1; k += 2 ){ Point a = pts.get(k); Point b = pts.get(k+1); g.drawline( a.x, a.y, b.x, b.y ); We must call the panel s addmouselistener method so that we can respond when the mouse is pressed on the panel. We must call the panel s addmousemotionlistener method so that we can respond when the mouse is dragged on the panel. The paintcomponent method is called by the JVM whenever (1) the JVM determines that the panel needs to be redrawn or (2) we call the repaint() method. private class Mickey extends MouseAdapter { public void mousepressed( MouseEvent e ){ Point p = new Point( e.getx(), e.gety() ); pts.add( p ); pts.add( p ); // the second point is replaced as the mouse is dragged public void mousedragged( MouseEvent e ){ Point here = new Point( e.getx(), e.gety() ); pts.set( pts.size()-1, here ); repaint(); // causes the JVM to call the paintcomponent method The MouseListener interface has five methods: mousepressed, mousereleased, mouseclicked, mouseentered, mouseexited The MouseMotionListener interface has two methods: mousemoved, mousedragged The MouseAdapter class is an abstract class that implements both of these interfaces. 11
Ex 4. Here s how the program should run. - Wherever the user right-clicks on the yellow panel, a bubble appears on the panel and starts moving up. - If the user left-clicks on the bubble, the bubble disappears and they get 5 points. - If the user left-clicks and does not hit any bubble then they lose 5 points. - If a bubble goes off the top of the panel, the score should decrease by 2 points and bubble reappear at the bottom of the panel. There are four classes. The Bubble class encapsulates the idea of a bubble. Each bubble knows its center coordinates, diameter, color, and speed. There is one Bubble constructor: public Bubble( int x, int y ) The parameters initialize the bubbles location. Its speed is random between 5 and 10 (pixels per update). Its diameter is random between 5 and 25 pixels. Its color is random. Red, green, blue, and alpha are all random values between 50 and 150. The Bubble class has five methods: public void draw( Graphics g ) draws itself public boolean contains( int x, int y ) returns true if (x,y) falls within the Bubble; otherwise returns false public Point getlocation() returns the location of its center public void move() moves the Bubble up by whatever its speed is. If the bubble has moved past the top of the field then the score decreases by two and the bubble is moved to the bottom of the field. public void move( int y ) changes the Bubble s y coordinate to the value of the parameter 12
The BubbleBath class represents the idea of a collection of bubbles in a field with a specified height. It also has an instance variable to keep score. There is one BubbleBath constructor: public BubbleBath( int h ) The parameter initializes the height of the BubbleBath field. The BubbleBath class has 5 methods public void add( int x, int y ) Adds a bubble centered at this location public boolean check( int x, int y ) Iterates through the collection of bubbles in the field. If the point (x, y) lies within a bubble, that bubble will be deleted and the score increases by 5 points. It is possible that multiple bubbles could be removed if (x, y) lies within multiple overlapping bubbles. If (x, y) does not lie within any bubbles then the score decreases by 5 points. Returns true if at least one bubble was removed; otherwise returns false. public void updatebath() Iterates through the collection of bubbles in the field and calls each bubble s move() method public ArrayList<Bubble> getbubbles() Returns an array list of all the bubbles in the bubble bath. public int getscore() Returns the score The BubblePanel class extends JPanel. It has a BubbleBath object, a reference to its enclosing frame, and a Timer object. There is one BubblePanel constructor: public BubblePanel( Color c, BubbleFrame jf ) { c and f initialize the two corresponding instance variables. Add a mouse listener to the panel Create a timer with a listener start the timer @Override public void paintcomponent( Graphics g ){ super.paintcomponent( g ); Call BubbleBath s getbubbles method Iterate through the bubbles and call their draw methods There are two inner classes private class Mickey extends MouseAdapter { public void mousepressed( MouseEvent e ){ if ( SwingUtilities.isRightMouseButton( e ) ) { 13
Add a bubble to the bubble bath at this location repaint(); else if ( SwingUtilities.isLeftMouseButton( e ) ) { Call the BubbleBath s check method. If it returns true call repaint frame.updatescore( bb.getscore() ); private class TimerListener implements ActionListener { public void actionperformed(actionevent e) { bb.updatebath(); frame.updatescore( bb.getscore() ); repaint(); Finally there is the BubbleFrame class. public class BubbleFrame extends JFrame{ private JLabel lblscore = new JLabel( "Score: 0" ); public static void main(string[] args) { BubbleFrame f = new BubbleFrame(); f.display(); public BubbleFrame(){ Set up the frame that contains one JPanel Create a BubblePanel and add that to the main JPanel Add the JLabel to the main JPanel public void updatescore( int num ){ lblscore.settext( "Score: " + num ); public void display() { EventQueue.invokeLater(new Runnable() { public void run() { setvisible(true); ); 14