JTombola





3.00/5 (5投票s)
带有彩票式旋转标签的桌面应用程序
引言
我开发了这个类似抽奖机的应用程序,为 CONAIS 大会 的奖品抽奖提供了一个简单而具有视觉吸引力(且可重用)的用户界面。
每年约有 500 名与会者参加此次大会。活动结束时,会举行一个小型的各种物品抽奖。这些奖品将分发给与会者。
因此,组织者要求我开发一个桌面应用程序,其中参与者的姓名将显示在一个旋转的标签上,这是一种电子抽奖机,这样与会者就可以知道他们的名字何时出现,以及抽奖操作员何时停止抽奖机并显示获奖者的姓名。参与者姓名存储在一个纯文本文件中,因此应用程序启动时应加载该列表。获奖者名单应在抽奖结束时保存到纯文本文件中。
我建议使用 Java 作为编程语言,因为它具有开发桌面应用程序的便利性,它具备多线程和平台无关的功能,并且是一个活跃而健壮的平台。我特别使用了 OpenJDK 7 平台,因为它是免费软件。事实上,使用该平台可以轻松满足所有指定的要求。
Using the Code
解决方案分为以下几个包
ocb.jtombola.core
+- NamesLoader.java
+- TombolaLabel.java
ocb.jtombola.gui
+- MainForm.java
+- TombolaPanel.java
ocb.jtombola.core
包包含基础类。NamesLoader
类执行 I/O 操作,TombolaLabel
类是模拟抽奖机的旋转标签。
ocb.jtombola.gui
包包含 MainForm
类,它是应用程序的主窗口;以及 TombolaPanel
类,它包含所有 UI 元素。
容器面板
TombolaPanel
类继承自 JPanel
并实现 ActionListener
和 MouseListener
以管理用户事件。
public class TombolaPanel extends JPanel implements ActionListener, MouseListener
此类具有以下主要属性,分别用于设置背景图片、祝贺标签、切换按钮和旋转标签。
private final ImageIcon background;
private final JLabel lblCongrats;
private final JButton btnGo;
private final TombolaLabel tombolaLabel;
此类构造方法中包含概述应用程序整体运行的代码。
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));
/* Customization of TombolaPanel */
/* Customization of btnGo */
/* Customization of lblCongrats */
}
这段代码创建了一个 TombolaLabel
实例,并将包含姓名列表的文件名作为参数传入,然后对其进行自定义。此处值得注意的方法是 setMillis()
,它设置标签显示姓名的延迟。setFont()
、setForeground()
、setBounds()
和 setHorizontalTextAlignment()
方法用于自定义标签。setBorder()
方法仅用于调试标签的大小和位置,尽管此边框可以在自定义 UI 中进行个性化设置。
btnGo
对象是用于启动抽奖机的切换按钮,lblCongrats
是一种祝贺消息,在停止抽奖机并显示获奖者姓名后显示。这两个组件以及面板容器都已按照应用程序截图所示进行了自定义。
启动抽奖机的 btnGo
代码如下:
@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);
}
}
}
当 isSpinning
标志开启时,按钮会停止,并显示祝贺消息。反之亦然。
旋转标签
TombolaLabel
类完成了这项工作。它继承自 Samuel Sjoberg 的炫酷的 MultiLineLabel
类。它还实现了 Runnable
接口,以便在其自己的线程中显示旋转的姓名。
public class TombolaLabel extends MultiLineLabel implements Runnable
重要的是要提到用于控制抽奖机功能的易失性属性。
private volatile Thread tombolaThread;
构造函数以文件名作为参数,创建 NamesLoader
类的实例并加载姓名。如果列表为空,则程序终止;否则,将创建获奖者列表和随机数生成器。
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();
}
使用切换按钮启动抽奖机的相关代码如下:
public void start() {
this.setUI(MultiLineLabelUI.labelUI);
tombolaThread = new Thread(this);
tombolaThread.start();
}
此方法为标签添加了一个细微的边框,然后创建一个使用此类的新线程并启动它,从而触发以下代码。
@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();
}
}
}
在此代码中,标签会显示从整个列表中随机选择的一个姓名。姓名会显示一段时间(以毫秒为单位),在这段时间内我们将线程休眠。当线程唤醒时,过程会继续,直到切换按钮停止抽奖机,调用以下代码。
public void stop() {
tombolaThread = null;
this.setUI(MultiLineShadowUI.labelUI);
lstWinners.add(lstNames.get(n));
lstNames.remove(n);
}
这会简单地将 tombolaThread
设置为 null
,以便抽奖机停止显示获奖者的姓名。我们必须将此姓名添加到获奖者列表中,并且不要忘记将其从参与者列表中移除,因为一名与会者只能赢得一件奖品。setUI()
方法添加了一个漂亮的阴影来突出显示姓名。
停止线程的方法取自 Sun(现为 Oracle)的文章 Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?。
加载与会者姓名
NamesLoader
类相对简单,因为它从给定的输入文件中加载姓名列表并将其存储在 List<String>
数组列表中。
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();
}
}
此类中的另一个相关方法是
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();
}
}
其中保存了应用程序显示的获奖者姓名列表。
主窗体
MainFrame
类继承自 JFrame
类,并实现 WindowListener
接口以检测用户何时关闭框架。
public class MainFrame extends JFrame implements WindowListener
它有一个 TombolaPanel
类的实例。
TombolaPanel pnltombola;
我在构造方法中为框架设置了以下属性:
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);
}
当用户关闭框架时
@Override
public void windowClosing(WindowEvent we) {
pnltombola.saveWinners();
System.exit(0);
}
在应用程序退出之前,我们确保获奖者列表已保存。
关注点
建议的代码非常简单,但却是处理单个线程显示姓名一段时间(以毫秒为单位)以模拟抽奖机的有效方法。此时间可以通过 setMillis()
方法设置。我们注意到 80 毫秒是显示姓名并供观众阅读的理想时间,同时足够快以模拟抽奖机效果。我们对 JTombola 进行了数千名参与者的压力测试,性能符合预期。
大会期间使用的应用程序背景图是由本地设计师绘制的,但 GIMP 在设计背景图方面非常出色,例如我为示例应用程序设计的带有 弯曲文本 的背景图。我还使用了强大的 ImageMagick 套件,通过 convert -delay 100 -loop 0 yay*.png yay.gif
命令创建了(非常简单的)带有祝贺消息的 动画 GIF。顺便说一句,动画截图是用 silencast 制作的。我在示例应用程序中包含了一个小的姓名列表。你能猜出他们是谁吗?
如果您希望在自己的抽奖活动中使用 JTombola,只需替换 /img 文件夹下的背景图片和(如果需要)祝贺图片即可。背景图片尺寸为 1024x750 像素,祝贺图片尺寸为 268x117 像素;如果您希望使用其他图片尺寸,则必须修改 TombolaPanel
构造函数中的相关代码并重新编译项目。更改切换按钮的图片是可选的。不要忘记将 input.txt 文件替换为您自己的参与者姓名。
JTombola 在上一届大会期间大获成功,与会者们玩得很开心(至少获奖者们是如此)。
历史
- v1.0.0 | 初始发布 | 2016 年 9 月 30 日