模态框可以通过继承JDialog并且设置构造参数boolean modal = true即可。
但是 当此模态框setVisible(true)时候,会导致当前线程阻塞。
那么问题来了:现有一需求是点击登陆按钮,则会向服务器发送登陆请求以及获取数据的请求,待服务器成功返回响应即可登陆主界面。但是由于如果有人多次点击登陆按钮,就会发很多次请求,这样服务器的压力就会变大。如何改进程序较少服务器的压力呢?
分析:方法有很多,服务器可以判断响应请求是否重复从而减少多余的响应,客户端可以让用户点击完按钮后,使得按钮不可再点,从而较少多余的请求。相比之下,第二种方法是从根本解决问题的。所以在这我选择第二中方法。
思路:在点击登陆后弹出模态框,等待响应回来后自动关闭模态框。
实现:
首先对于模态框可以是通过继承JDialog并设置参数得到。
public class MyDialog 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;
private volatile boolean getByShow;
public MyDialog(Frame owner, boolean modal) {
super(owner, modal);
getByShow = false;
container = getContentPane();
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
setUndecorated(true);
}
MyDialog(Dialog owner, boolean modal) {
super(owner, modal);
container = getContentPane();
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
setUndecorated(true);
}
public boolean isGetByShow() {
return getByShow;
}
public void setGetByShow(boolean getByShow) {
this.getByShow = getByShow;
}
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;
}
void showDialog() {
setVisible(true);
}
void closeDialog() {
dispose();
}
}
由于模态框setVisible(true)时候,会导致当前线程阻塞。所以为了之后可以关闭模态框,我们使用线程去控制模态框的开启。
public class WaittingDialog implements Runnable {
public WaittingDialog(MyDialog dialog, String response) {
ClientConversation.putDialogLock(response, dialog);
new Thread(this, "WD-" + response).start();
}
@Override
public void run() {
String response = Thread.currentThread().getName().substring(3);
MecDialog dialog = null;
synchronized (ClientConversation.class) {
dialog = ClientConversation.getDialogLock(response);
if (dialog == null) {
return;
}
dialog.setGetByShow(true);
}
if (dialog != null) {
dialog.showDialog();
}
}
}
WaittingDialog构造中需要两个参数,第一个是我们的模态框,第二个是点击按钮后的响应Action。在构造中将响应与dialog加到客户端会话层中的map中的原因是:当登陆请求发到服务器会话层,服务器会话层返回相应给客户端会话层后,通过action取得对应的dialog从而关闭相应dialog。
下面是客户端通过客户端会话层发送请求的部分代码:
public void sendRequest(String request, String response, String parameter,
RootPaneContainer parentView, String message) {
if (parentView instanceof Frame) {
new WaittingDialog(
new MecDialog((Frame) parentView, true)
.initDialog(message), response);
} else if (parentView instanceof JDialog) {
new WaittingDialog(
new MecDialog((JDialog) parentView, true)
.initDialog(message), response);
}
sendRequest(request, response, parameter);
}
public void sendRequest(String request, String response, String parameter) {
conversation.send(new StandardNetMessage()
.setFrom(conversation.getId())
.setTo(INetConstance.SERVER)
.setCommand(ENetCommand.REQUEST)
.setAction(request + ":" + response)
.setMessage(parameter));
}
服务器会话层接受请求并返回客户端会话层部分代码:
case REQUEST:
String action = message.getAction();
int commaIndex = action.indexOf(':');
String request = action.substring(0, commaIndex);
String response = action.substring(commaIndex + 1);
String parameter = message.getMessage();
if (this.action != null) {
try {
Object result = this.action.dealRequest(request, parameter);
String returnString = gson.toJson(result);
send(new StandardNetMessage()
.setFrom(INetConstance.SERVER)
.setTo(id)
.setCommand(ENetCommand.RESPONSE)
.setAction(response)
.setMessage(returnString));
} catch (Exception e) {
e.printStackTrace();
}
}
break;
客户端会话层处理响应部分代码:
case RESPONSE:
String action = message.getAction();
String parameter = message.getMessage();
synchronized (ClientConversation.class) {
MecDialog dialog = dialogMap.get(action);
if (dialog.isGetByShow()) {
boolean isActive = false;
while (!isActive) {
isActive = dialog.isActive();
}
}
dialog.closeDialog();
dialogMap.remove(action);
}
client.getAction().dealResponse(action, parameter);
break;
问题来了:由于模态框是由线程控制的,这样若不加相应的同步控制,可能会发生无法关闭模态框问题,如点击登陆按钮后,模态框线程启动进入就绪态,并未到执行态,模态框还未显示时,服务器响应已经返回给客户端,客户端执行相应的关闭模态框操作,然后模态框线程就入执行态显示模态框,这样就关闭不了模态框了。
分析方法:使用同步块使得从map中取dialog互斥,并且给MyDiag加入一个volatile 的标志getByshow。此时有两种情况:1.在控制模态框的线程(得到锁)取得dialog后判断为空?return null :dialog.setGetByShow(true)然后释放锁。如果此时服务器已经返回响应,并且(得到锁)取得dialog,判断标志为真后,继续等待dialog为active后再关闭dialog,从map中移除该dialog然后(释放锁)。2服务器已经返回响应,并且(得到锁)取得dialog,判断标志为假后直接执行关闭dialog,并且移除并(释放锁)。控制模态框的线程(得到锁)发现取到dialog为null则直接return,不执行模态框的显示。
具体实现请看以上代码,更多细节请查看我的其他文章。