JDialog是swing的一个组件。他有两种用法。
第一种就是使用系统提供的静态方法。在JOptionPane中提供了四种静态方法:
方法名
描述
showMessageDialog
消息对话框,向用户展示一个消息,没有返回值。
showConfirmDialog
确认对话框,询问一个问题是否执行。
showInputDialog
输入对话框,要求用户提供某些输入。
showOptionDialog
选项对话框,上述三项的大统一,自定义按钮文本,询问用户需要点击哪个按钮。
这四种方法重载了很多次,有不同的参数。可根据需要选用。
第二种就是继承JDialog,自定义一个模态框。今天,我们就来着重介绍下自定义一个模态框,阻止用户登录的例子。
众所周知,用户在登录某个系统的时候,输完账号密码后点击登录,如果没有反应,对服务器方面知识空白的绝大多数用户会认为这是卡住了,可能会多次点击登录。但事实上每一次点击都会向系统发出请求,服务器不能及时处理,就是因为业务繁忙或者网络故障等等,如果用户多次请求,服务器就要多次响应。
如图是我编写的一个登录界面,当我连续点击三次登录按钮后,会有如下输出:
如图,客户端连着向服务器发送了三次连接请求,也就意味着服务器需要处理这三次请求。
近些年来,为了减轻服务器的工作压力,推出了不少方案,比如分布式服务器等等。但是如果能从源头上减轻服务器的负担,岂不是美滋滋。如果能有效阻止用户向服务器多次发送没有意义的请求,就能有效地从源头减轻服务器的负担。
JDialog模态框,有一个boolean类型的参数modal,这个参数就是控制模态框显示之后,还能不能对其父窗口进行操作,如果选择为false,则该模态框为非模态的,在弹出模态框后还能继续对其父窗口操作。如果选择为true,弹出模态框后不能对父窗口进行操作。我们要用的就是这种模态的。
首先,自己编写一个继承了JDialog的模态框,然后再编写一个类,该类中产生一个线程,在线程中调用模态框。
package com.mec.server_client.common;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Font;
import java.awt.Frame;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
public class MecDialog extends JDialog {
private static final long serialVersionUID = 2309852253785194778L;
private static final String TITLE = "晨哥温馨提示";
private static final Color topicColor = new Color(0, 0, 0);
private static final Font normalFont = new Font("宋体", Font.PLAIN, 16);
private static final Color backcolor = new Color(0x88, 0x88, 0x88);
private static final int PADDING = 15;
private Container container;
public MecDialog(Frame owner, boolean modal) {
super(owner, modal);
container = getContentPane();
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
setUndecorated(true);
}
MecDialog(Dialog owner, boolean modal) {
super(owner, modal);
container = getContentPane();
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
setUndecorated(true);
}
public MecDialog initDialog(String message) {
JPanel jpnlBackground = new JPanel(new BorderLayout());
container.add(jpnlBackground);
TitledBorder ttbdDialog = new TitledBorder(TITLE);
ttbdDialog.setTitleColor(topicColor);
ttbdDialog.setTitleFont(normalFont);
ttbdDialog.setTitlePosition(TitledBorder.TOP);
ttbdDialog.setTitleJustification(TitledBorder.CENTER);
jpnlBackground.setBorder(ttbdDialog);
jpnlBackground.setBackground(backcolor);
JLabel jlblMessage = new JLabel(message, JLabel.CENTER);
jlblMessage.setFont(normalFont);
jlblMessage.setForeground(topicColor);
jlblMessage.setSize(message.length() * normalFont.getSize(),
normalFont.getSize() + 4);
jpnlBackground.add(jlblMessage, BorderLayout.CENTER);
int height = 5 * PADDING + jlblMessage.getHeight();
int width = 10 * normalFont.getSize() + jlblMessage.getWidth();
setSize(width, height);
jpnlBackground.setSize(width, height);
setLocationRelativeTo(null);
return this;
}
public void showDialog() {
setVisible(true);
}
public void closeDialog() {
dispose();
}
}
package com.mec.server_client.common;
public class WaittingDialog implements Runnable {
private volatile MecDialog dialog;
public WaittingDialog(MecDialog dialog, String response) {
this.dialog = dialog;
ClientConversation.putDialogLock(response, dialog);
new Thread(this, "Waitting Dialog").start();
}
@Override
public void run() {
dialog.showDialog();
}
}
在客户端需要向服务器发送用户的登录请求时,启动该线程,在客户端显示这个不可关闭的模态框,然后在客户端收到服务器的响应后释放客户端的屏幕。以此来保证当用户点击登陆后,服务器未及时响应时,阻止用户多此点击登录按钮的操作。
说起来挺简单的,但是在实现过程中还遇到了一些麻烦。
在此先说一下关于线程的相关事宜。线程在调用后(start之后),并不是直接启动,而是让线程进入就绪态,竞争cpu,在竞争到cpu之后线程才能运行(run方法运行)。
那么在启动了模态框之后,也就是运行了waittingDialog的构造方法,在线程进入就绪态竞争cpu还没有真正运行的时候,访问服务器的操作已经完成,并且接着执行了关闭模态框的方法,然后run()方法才开始运行,启动模态框,那么启动了之后就无法关闭了。
这个问题的难点就在调用了模态框之后这个线程就阻塞了,无法在执行后续代码。因此无法在服务器返回响应后执行关闭模态框的方法。
在经过多此尝试后,总结出了解决问题的方法。那就是isActive()方法。这个方法可以检测一个线程的run()方法是否运行。因此可以在客户端处理服务器返回响应的时候检测waittingDialog线程的run()方法是否运行,用一个循环持续检测,当检测到该run()方法运行后,再继续执行后续代码,关闭该模态框。
如果出现刚刚的问题,当还未运行run()方法,服务器就返回处理结果时,进入检测, 持续循环,知道模态框被调用后才能继续往下执行。
MecDialog dialog = dialogMap.get(action);
if (dialog != null) {
while (!dialog.isActive()) {
;
}
dialog.closeDialog();
dialogMap.remove(action);
}