Introduction
I developed this tombola-like application in order to provide a simple yet visually attractive (and reusable) UI for a prize raffle at the CONAIS congress.
Around 500 participants attend the congress each year. At the end of the event, a small raffle of various items is held. These gifts are to be distributed among the assistants.
Thus, the organizers requested me a desktop application in which the names of the participants should be shown in a spinning label, a sort of electronic tombola, so the participants should be aware when their names would show up and when the tombola operator stops the tombola showing the name of the prize winner. The participants' names are stored in a plain text file, so the application should load the list when started. The list of winners should be saved in a plain text file at the end of the raffle.
I propose using Java as the programming language, because it has the facilities to develop desktop applications, it has multithreaded and platform-independent capabilities, and is an active and robust platform. I specifically use the OpenJDK 7 platform because it's free software. As a matter of fact, all the specified requirements could be easily met using this platform.
Using the Code
The solution is divided into the following packages:
ocb.jtombola.core
+- NamesLoader.java
+- TombolaLabel.java
ocb.jtombola.gui
+- MainForm.java
+- TombolaPanel.java
The package ocb.jtombola.core
contains the foundation classes. The NamesLoader
class performs I/O operations and the TombolaLabel
class is the spinning label simulating the tombola.
The package ocb.jtombola.gui
contains the MainForm
class, which is the main window of the application; and the TombolaPanel
class, which contains all the UI elements.
The Container Panel
The class TombolaPanel
extends JPanel
and implements ActionListener
and MouseListener
in order to manage user events:
public class TombolaPanel extends JPanel implements ActionListener, MouseListener
This class has the following main attributes used to set the background image, the congratulations label, the toggle button, and the spinning label, respectively:
private final ImageIcon background;
private final JLabel lblCongrats;
private final JButton btnGo;
private final TombolaLabel tombolaLabel;
In the constructor method of this class resides the code which outlines the overall operation of the application:
public TombolaPanel() {
isSpinning = false;
tombolaLabel = new TombolaLabel("input.txt");
tombolaLabel.setMillis(80);
tombolaLabel.setFont( new Font("Nimbus Sans L", Font.BOLD, 74) );
tombolaLabel.setForeground(Color.WHITE);
tombolaLabel.setBounds(50, 180, 930, 200);
tombolaLabel.setHorizontalTextAlignment(JLabel.CENTER);
tombolaLabel.setBorder(new LineBorder(Color.GRAY));
}
This code creates an instance of TombolaLabel
with the file name containing the list pf names as a parameter, and then customizes it. The notable method here is setMillis()
, which sets the delay in the label to show up the names. The methods setFont()
, setForeground()
, setBounds()
and setHorizontalTextAlignment()
are to customize the label. The method setBorder()
is only used to debug the size and position of the label, although this border can be personalized in your custom UI.
The btnGo
object is the toggle button used to fire up the tombola, and the lblCongrats
is a sort of congratulations message shown after stopping the tombola and showing the name of the winner. Both components, and the panel container, are customized as shown in the screenshot of the application.
The btnGo
code that starts up the tombola is:
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btnGo) {
if (isSpinning) {
isSpinning = false;
tombolaLabel.stop();
lblCongrats.setVisible(true);
} else {
isSpinning = true;
tombolaLabel.go();
lblCongrats.setVisible(false);
}
}
}
When the isSpinning
flag is on, the button stops and the congratulations message is shown up. The opposite occurs on the other case.
The Spinning Label
The class TombolaLabel
does the job. It inherits from the cool MultiLineLabel
class by Samuel Sjoberg. It also implements the Runnable
interface in order to display the spinning names in its own thread:
public class TombolaLabel extends MultiLineLabel implements Runnable
It is important to mention the volatile attribute used for controlling the tombola feature:
private volatile Thread tombolaThread;
The constructor takes the file name as an argument and creates an instance of the NamesLoader
class and loads the names. If the list if empty, then the program terminates, otherwise the list of winners and the random number generator are created:
public TombolaLabel(String fileName) {
inputList = new NamesLoader( fileName );
lstNames = inputList.getList();
if ( lstNames.isEmpty() ) {
JOptionPane.showMessageDialog(null,
"No names in the input file",
"Error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
lstWinners = new ArrayList<>();
random = new Random();
}
The relevant code to start the tombola using the toggle button is:
public void start() {
this.setUI(MultiLineLabelUI.labelUI);
tombolaThread = new Thread(this);
tombolaThread.start();
}
This method adds a slight border to the label, then creates a new thread using this class and then starts it, which in turn fires the following code:
@Override
public void run() {
Thread thisThread = Thread.currentThread();
while (tombolaThread == thisThread) {
n = random.nextInt(lstNames.size());
this.setText(lstNames.get(n));
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
In this code, the label displays a name from the whole list, picked at random. The name is shown for a number of milliseconds in which we put the thread to sleep. When the thread wakes up, the process continues until the toggle button stops the tombola invoking the following code:
public void stop() {
tombolaThread = null;
this.setUI(MultiLineShadowUI.labelUI);
lstWinners.add(lstNames.get(n));
lstNames.remove(n);
}
This simply sets the tombolaThread
to null
so the tombola stops showing the name of the winner. We must add this name to the list of winners and also not forget to remove it from the list of participants because one assistant can only win one prize. The setUI()
method adds a nice shadow highlighting the name.
This method to stop the thread is taken from the Sun (now Oracle) article, Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?
Loading the Names of the Assistants
The NamesLoader
class is relatively simple, as it loads the list of names from a given input file and stores it in a List<String>
array list:
public void loadFile() {
try {
FileReader fr = new FileReader(this.fileName);
BufferedReader br = new BufferedReader(fr);
lstNames = new ArrayList<>();
String name;
while ((name = br.readLine()) != null) {
lstNames.add(name);
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
Another relevant method in this class is:
public void saveWinners(List<String> list) {
try {
FileWriter fw = new FileWriter("winners.txt");
for (String name : list) {
fw.write(name);
fw.write(System.getProperty("line.separator"));
}
fw.close();
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
Which saves the list of winning names shown by the application.
The Main Form
The MainFrame
class inherits from the JFrame
class and implements the WindowListener
interface in order to detect when the user closes the frame:
public class MainFrame extends JFrame implements WindowListener
It has an instance of class TombolaPanel
:
TombolaPanel pnltombola;
I set the following properties to the frame in the constructor method:
public MainFrame() {
this.pnltombola = new TombolaPanel();
this.add(pnltombola);
this.setTitle("Tómbola 2016");
this.setResizable(false);
this.setSize(1024, 750);
this.setLocationRelativeTo(null);
this.addWindowListener(this);
}
When the user closes the frame:
@Override
public void windowClosing(WindowEvent we) {
pnltombola.saveWinners();
System.exit(0);
}
We make sure that the list of winners is saved before the application exits.
Points of Interest
The code proposed is very simple, yet an effective approach to handle a single thread showing a name for a number of milliseconds emulating a tombola. This time can be set via the setMillis()
method. We notice that 80 milliseconds is an ideal time to show up a name and read it by the audience, while fast enough to simulate the tombola effect. We stress-tested the JTombola with thousands of names and the performance was as expected.
The background image of the application used during the congress was drawed by a local designer, but the GIMP is really great at designing background images, like the one with the curved text that I designed for the sample application. I also used the awesome suite ImageMagick to create the (very simple) animated gif with the congratulations message via the convert -delay 100 -loop 0 yay*.png yay.gif
command. By the way, the animated screenshot was made with silencast. I include a small list of names in the sample application. Can you guess who they are?
If you wish to use the JTombola in your own raffles, just replace the background and (if needed) the congratulations images under the /img folder and that's it. The background image is 1024x750 px and the congratulations one is 268x117 px; if you wish to use other image sizes then you must modify the relevant code in the TombolaPanel
constructor and recompile the project. Changing the images of the toggle button are optional. Don't forget to replace the input.txt file with your own participants' names.
JTombola was a resounding success during the last edition of the congress, as the participants had a lot of fun (well, at least the winning participants).
History
- v1.0.0 | Initial release | 30.sep.2016