patternjavaMinor
Full screen countdown display
Viewed 0 times
countdownscreendisplayfull
Problem
As a part of a bigger application, I wrote code which creates a display for a countdown of a given time. It is nothing fancy, but I would still appreciate a review.
I am especially concerned about:
```
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Countdown{
private JFrame frame;
private Timer timer;
private JLabel count;
private JLabel message;
private int[] time;
final int MIN = 0;
final int SEC = 1;
public Countdown(){
frame = new JFrame();
frame.setUndecorated(true);
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setAlwaysOnTop(true);
frame.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
frame.setVisible(false);
frame.dispose();
frame.setUndecorated(!frame.isUndecorated());
frame.revalidate();
frame.setVisible(true);
}
});
frame.add(addText());
frame.setVisible(true);
}
public JPanel addText(){
count = new JLabel();
count.set
I am especially concerned about:
- Use of
JFrame- this is the secondJFramein my app, called separately by other GUI components. I read that it is not a good idea to use multipleJFrames, however in this case I don't know what problem it could cause (and it is easier to get full screen effect, than in caseJDialog).
JFramemouseListener- is it good practice (from clean code perspective) to putActionListenercode in declaration of a component? Also, the frame in my example needs to befinalor field, does it make sense to make container afinal, if it is a 'base' component for some part of the code?
- Is my code readable? I know it is short, but I'm trying to get a grasp on clean code principles, naming, etc.
- Use of
final- is it worth to declarefinalvariables, to use in small parts of whole app, or only for global variables?
```
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Countdown{
private JFrame frame;
private Timer timer;
private JLabel count;
private JLabel message;
private int[] time;
final int MIN = 0;
final int SEC = 1;
public Countdown(){
frame = new JFrame();
frame.setUndecorated(true);
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setAlwaysOnTop(true);
frame.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
frame.setVisible(false);
frame.dispose();
frame.setUndecorated(!frame.isUndecorated());
frame.revalidate();
frame.setVisible(true);
}
});
frame.add(addText());
frame.setVisible(true);
}
public JPanel addText(){
count = new JLabel();
count.set
Solution
Let's start with 3. I think the most important improvement would be a separation of the program logic from the layout stuff. This will also support you in creating unit tests. (In addition to that, the separate
I added some comments to minor changes in the code.
Main.java
CountDownFrame.java
The import Countdown.java with the program logic is now really straight forward and easy to read.
And is also covered by tests without any layout interactions:
```
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class CountdownTest
{
@Test
public void parser_valid()
{
assertEquals( "00:00", new Countdown( "0:00", null ).toReadableString() );
assertEquals( "10:15", new Countdown( "10:15", null ).toReadableString() );
}
public void parser_invalid()
{
// TODO
// new Countdown( "", null ));
// new Countdown( "0", null ));
// new Countdown( "0:0:0", null ));
// new Countdown( "0:70", null ));
}
@Test
public void isFinished()
{
assertTrue( new Countdown( "0:00", null ).isFinished() );
assertFalse( new Countdown( "0:01", null ).isFinished() );
assertFalse( new Countdown( "1:00", null ).isF
main class is working fine for me.)I added some comments to minor changes in the code.
Main.java
public class Main
{
public static void main( String[] args )
{
new CountDownFrame( new Countdown( "00:12", "RUN" ) );
}
}CountDownFrame.java
import java.awt.*;
import javax.swing.*
public class CountDownFrame extends JFrame // extract layout to own class
{
private static final long serialVersionUID = 1L;
private Timer timer;
private JLabel count;
private JLabel message;
private Countdown countdown;
public CountDownFrame( Countdown countdown )
{
this.countdown = countdown;
setUndecorated( true );
setExtendedState( Frame.MAXIMIZED_BOTH );
setAlwaysOnTop( true );
addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
setVisible( false );
dispose();
setUndecorated( !isUndecorated() );
revalidate();
setVisible( true );
}
} );
add( initText() );
startTimer();
setVisible( true );
}
private JPanel initText() // rename and private
{
count = new JLabel();
count.setFont( new Font( "Arial Black", Font.BOLD, 200 ) );
count.setHorizontalAlignment( SwingConstants.CENTER );
count.setBackground( Color.RED );
message = new JLabel();
message.setFont( new Font( "Arial Black", Font.BOLD, 100 ) );
JPanel panel = new JPanel( new GridLayout( 2, 1 ) );
panel.add( count );
panel.add( message );
panel.setBackground( Color.WHITE );
message.setHorizontalAlignment( SwingConstants.CENTER );
return panel;
}
private void startTimer() // rename
{
count.setText( countdown.toReadableString() ); // extract method
message.setText( countdown.getMessage() );
timer = new Timer( 1000, new TimerListener() );
timer.setRepeats( true );
timer.start();
}
private class TimerListener implements ActionListener
{
@Override
public void actionPerformed( ActionEvent e )
{
countdown.decrement(); // separate layout from logic
count.setForeground( countdown.isCloseToEnd() ? Color.RED : Color.BLACK ); // extract conditions
if( countdown.isFinished() ) // extract conditions
{
count.setText( "" );
message.setText( "END" );
timer.stop();
}
else
{
count.setText( countdown.toReadableString() );
}
}
}
}The import Countdown.java with the program logic is now really straight forward and easy to read.
public class Countdown
{
private String message;
private int minutes; // no need for an array, keep it simple and readable
private int seconds;
public Countdown( String time, String message )
{
this.message = message;
String[] parts = time.split( ":" );
// TODO add some error handling
this.minutes = Integer.valueOf( parts[0] );
this.seconds = Integer.valueOf( parts[1] );
}
public boolean isCloseToEnd() // move timer logic to countdown
{
return minutes 0 ) seconds--;
else
{
seconds = 59;
minutes--;
}
return this; // just for chaining in test :)
}
public String toReadableString()
{
return String.format( "%02d:%02d", minutes, seconds ); // unify to one format
}
public String getMessage()
{
return message;
}
}And is also covered by tests without any layout interactions:
```
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class CountdownTest
{
@Test
public void parser_valid()
{
assertEquals( "00:00", new Countdown( "0:00", null ).toReadableString() );
assertEquals( "10:15", new Countdown( "10:15", null ).toReadableString() );
}
public void parser_invalid()
{
// TODO
// new Countdown( "", null ));
// new Countdown( "0", null ));
// new Countdown( "0:0:0", null ));
// new Countdown( "0:70", null ));
}
@Test
public void isFinished()
{
assertTrue( new Countdown( "0:00", null ).isFinished() );
assertFalse( new Countdown( "0:01", null ).isFinished() );
assertFalse( new Countdown( "1:00", null ).isF
Code Snippets
public class Main
{
public static void main( String[] args )
{
new CountDownFrame( new Countdown( "00:12", "RUN" ) );
}
}import java.awt.*;
import javax.swing.*
public class CountDownFrame extends JFrame // extract layout to own class
{
private static final long serialVersionUID = 1L;
private Timer timer;
private JLabel count;
private JLabel message;
private Countdown countdown;
public CountDownFrame( Countdown countdown )
{
this.countdown = countdown;
setUndecorated( true );
setExtendedState( Frame.MAXIMIZED_BOTH );
setAlwaysOnTop( true );
addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
setVisible( false );
dispose();
setUndecorated( !isUndecorated() );
revalidate();
setVisible( true );
}
} );
add( initText() );
startTimer();
setVisible( true );
}
private JPanel initText() // rename and private
{
count = new JLabel();
count.setFont( new Font( "Arial Black", Font.BOLD, 200 ) );
count.setHorizontalAlignment( SwingConstants.CENTER );
count.setBackground( Color.RED );
message = new JLabel();
message.setFont( new Font( "Arial Black", Font.BOLD, 100 ) );
JPanel panel = new JPanel( new GridLayout( 2, 1 ) );
panel.add( count );
panel.add( message );
panel.setBackground( Color.WHITE );
message.setHorizontalAlignment( SwingConstants.CENTER );
return panel;
}
private void startTimer() // rename
{
count.setText( countdown.toReadableString() ); // extract method
message.setText( countdown.getMessage() );
timer = new Timer( 1000, new TimerListener() );
timer.setRepeats( true );
timer.start();
}
private class TimerListener implements ActionListener
{
@Override
public void actionPerformed( ActionEvent e )
{
countdown.decrement(); // separate layout from logic
count.setForeground( countdown.isCloseToEnd() ? Color.RED : Color.BLACK ); // extract conditions
if( countdown.isFinished() ) // extract conditions
{
count.setText( "" );
message.setText( "END" );
timer.stop();
}
else
{
count.setText( countdown.toReadableString() );
}
}
}
}public class Countdown
{
private String message;
private int minutes; // no need for an array, keep it simple and readable
private int seconds;
public Countdown( String time, String message )
{
this.message = message;
String[] parts = time.split( ":" );
// TODO add some error handling
this.minutes = Integer.valueOf( parts[0] );
this.seconds = Integer.valueOf( parts[1] );
}
public boolean isCloseToEnd() // move timer logic to countdown
{
return minutes < 1 && seconds <= 5; // opposite condition
}
public boolean isFinished()
{
return minutes < 1 && seconds < 1;
}
public Countdown decrement()
{
if( seconds > 0 ) seconds--;
else
{
seconds = 59;
minutes--;
}
return this; // just for chaining in test :)
}
public String toReadableString()
{
return String.format( "%02d:%02d", minutes, seconds ); // unify to one format
}
public String getMessage()
{
return message;
}
}import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class CountdownTest
{
@Test
public void parser_valid()
{
assertEquals( "00:00", new Countdown( "0:00", null ).toReadableString() );
assertEquals( "10:15", new Countdown( "10:15", null ).toReadableString() );
}
public void parser_invalid()
{
// TODO
// new Countdown( "", null ));
// new Countdown( "0", null ));
// new Countdown( "0:0:0", null ));
// new Countdown( "0:70", null ));
}
@Test
public void isFinished()
{
assertTrue( new Countdown( "0:00", null ).isFinished() );
assertFalse( new Countdown( "0:01", null ).isFinished() );
assertFalse( new Countdown( "1:00", null ).isFinished() );
assertFalse( new Countdown( "3:25", null ).isFinished() );
}
@Test
public void isCloseToEnd()
{
assertTrue( new Countdown( "0:00", null ).isCloseToEnd() );
assertTrue( new Countdown( "0:01", null ).isCloseToEnd() );
assertTrue( new Countdown( "0:05", null ).isCloseToEnd() );
assertFalse( new Countdown( "0:06", null ).isCloseToEnd() );
assertFalse( new Countdown( "1:00", null ).isCloseToEnd() );
assertFalse( new Countdown( "3:25", null ).isCloseToEnd() );
}
@Test
public void decrement()
{
// TODO undefined
// assertEquals( ..., new Countdown( "0:00", null ).decrement().toReadableString() );
assertEquals( "00:00", new Countdown( "0:01", null ).decrement().toReadableString() );
assertEquals( "00:04", new Countdown( "0:05", null ).decrement().toReadableString() );
assertEquals( "00:59", new Countdown( "1:00", null ).decrement().toReadableString() );
assertEquals( "03:59", new Countdown( "4:00", null ).decrement().toReadableString() );
assertEquals( "04:00", new Countdown( "4:01", null ).decrement().toReadableString() );
}
}Context
StackExchange Code Review Q#87713, answer score: 3
Revisions (0)
No revisions yet.