1)模態框的簡單介紹
模態對話框(Modal Dialogue Box,又叫做模式對話框),是指在用戶想要對對話框以外的應用程序進行操作時,必須首先對該對話框進行響應。如單擊【確定】或【取消】按鈕等將該對話框關閉。一般來說,Windows應用程序中,對話框分爲模態對話框和非模態對話框兩種。二者的區別在於當對話框打開時,是否允許用戶進行其他對象的操作。(以上內容來自百度百科)
舉例來說,當我們登錄某個界面時,輸入信息之後點擊登錄按鈕,有時候會出現暫時無響應或者是還沒有完成響應的情況下,用戶再次或多次點擊登錄按鈕,不斷地向服務器發送“請求”。而服務器此時沒有辦法做到識別出用戶正在進行重複操作,進而阻止用戶操作,當服務器完成剛開始第一次的“請求”返回響應時,用戶可能已經進行了無數次的點擊操作,但是服務器此時已經不需要對這些重複的“請求”做出響應。爲了阻止用戶的類似無效操作,減少服務器的負擔,給出一個解決辦法,在用戶第一次發出登錄請求時,彈出一個模態框,上面顯示正在等率,請稍後······的提示,以此提醒用戶服務器正在對你的操作進行反應,請稍等。這樣給出提示之後,用戶就不會再進行多次無效的操作。這就是模態框起到的作用。
當我們點擊某個事件之後,彈出來一個窗口,而當我們再次對這個窗口之外的位置進行點擊或者任何操作時,該窗口會發出“叮叮叮”的聲音,並且阻擋其它的任何操作,這個窗口就是模態框了。而模態框又分爲模態和非模態,上面的例子表示的是模態,非模態指的是,彈出一個窗口之後,把該窗口挪過去,依然可以繼續進行後續的操作,這種說的就是非模態了。
模態框有個專門的類JDialog,爲了簡單說明模態框,這裏給出一個類似適配器的類MDialog:
public class MDialog extends JDialog{
private static final long serialVersionUID = -6367411118125656047L;
public MDialog(Dialog owner, boolean modal) {
super(owner, modal);
}
public MDialog(Dialog owner, String title, boolean modal) {
super(owner, title, modal);
}
public MDialog(Frame owner, boolean modal) {
super(owner, modal);
}
public MDialog(Frame owner, String title, boolean modal) {
super(owner, title, modal);
}
//對模態框外觀顯示的初始化設置
public ModelDialog setCaption(String context) {
Font font = new Font("宋體", Font.BOLD, 16);//字體
int width = (context.length() + 4) * font.getSize();//寬度
int height = 5 * font.getSize();//高度
setSize(width, height);
setLocationRelativeTo(getOwner());
//前面界面中用的一般都是參數爲null,默認位置;
//在這裏參數寫成getOwner()的意思是將模態框的位置設置成相對於父窗口的中間位置
setLayout(null);
setUndecorated(true);
//給模態框去掉邊框,避免出現“x”關閉按鈕
JPanel jpnlMessage = new JPanel();
jpnlMessage.setSize(width, height);
jpnlMessage.setLayout(new BorderLayout());
jpnlMessage.setBackground(Color.lightGray);
jpnlMessage.setBorder(BorderFactory.createLineBorder(Color.gray, 2));//給模態框加上邊界線
add(jpnlMessage);
JLabel jlblMessage = new JLabel(context, JLabel.CENTER);
jlblMessage.setFont(font);
jlblMessage.setSize(width, height);
jlblMessage.setForeground(Color.blue);
jlblMessage.setHorizontalTextPosition(JLabel.CENTER);
jpnlMessage.add(jlblMessage, BorderLayout.CENTER);
dealAction();
return this;
}
//關閉模態框的操作
public void closeDialog() {
dispose();
}
//顯示模態框
public void showDialog() {
setVisible(true);
}
//給出一個處理事件的方法,可以在之後進行覆蓋(重寫)
public void dealAction() {
}
}
說明:第二行的代碼意思是序列號,也就是版本號;
簡單覆蓋了JDialog類中的幾個方法,其中有個boolean類型的參數model,取值true和false,代表的就是前面說過的模態和非模態;
以上是對模態框的簡單說明,下面接着來介紹有關模態框的應用
2)有關模態框的應用和存在的問題
基於上面的類來完成模態框的簡單顯示,先給出一個swing普通窗口,
public class MainView implements INormalView,Runnable{
private JFrame mainView;
private JButton jbtnAction;
private JButton jbtnExit;
public MainView() {
initView();
}
@Override
public void init() {
mainView = new JFrame("關於對話框的學習");
mainView.setSize(new Dimension(500, 350));
mainView.setLayout(new BorderLayout());
mainView.setLocationRelativeTo(null);
mainView.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
JLabel jlblTopic = new JLabel("關於對話框的學習",JLabel.CENTER);
Font font = new Font("微軟雅黑", Font.BOLD, 30);
jlblTopic.setFont(font);
mainView.add(jlblTopic,BorderLayout.NORTH);
JPanel jpnlBody = new JPanel();
mainView.add(jpnlBody,BorderLayout.CENTER);
jbtnAction = new JButton("開始");
jbtnAction.setFont(normalFont);
jpnlBody.add(jbtnAction);
JPanel jpnlFooter = new JPanel();
mainView.add(jpnlFooter,BorderLayout.SOUTH);
jbtnExit = new JButton("退出");
jbtnExit.setFont(normalFont);
jpnlFooter.add(jbtnExit);
}
@Override
public void reinit() {
}
@Override
public void dealEvent() {
mainView.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
closeView();
}
});
jbtnExit.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
closeView();
}
});
}
private void closeView() {
try {
exitWindow();
} catch (FrameIsNullException e) {
e.printStackTrace();
}
}
@Override
public JFrame getFrame() {
return mainView;
}
顯示結果如下:
要達到的目的是,點擊“開始”按鈕,會彈出一個模態框,顯示正在獲取信息,請稍後······,需要給開始按鈕增加事件響應,
jbtnAction.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mDialog = new MDialog(mainView, "模態框", true);
mDialog.setCaption("正在獲取信息,請稍後···");
mDialog.setVisible(true);
System.out.println("模態框自動關閉!");
}
});
點擊開始按鈕之後,顯示模態框:
但是現在存在一個問題,就是模態框顯示之後,沒辦法關閉,即,就是在執行mDialog.setVisible(true)之後的操作會阻塞,模態框沒有關閉之前,根本無法繼續進行後面的操作,不會輸出模態框自動關閉的語句。
爲了看得更清楚,在模態框顯示之前給出一個線程,在模態框顯示5秒之後,完成模態框的關閉操作,
jbtnAction.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new Thread(MainView.this,"模態框控制線程").start();
mDialog = new MDialog(mainView, "模態框", true);
mDialog.setCaption("正在獲取信息,請稍後···");
mDialog.setVisible(true);
System.out.println("模態框自動關閉!");
}
});
@Override
public void run() {
System.out.println("開始顯示模態框:");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mDialog.closeDialog();
}
這樣雖然完成了模態框的關閉,可以正常執行setVisible(true)之後的操作,但這樣做絕對存在很大的弊端,比如,如果某次事件響應隨之顯示的是兩個或多個不同的模態框,但是關閉操作又寫在一個線程中,這是很不合理的。於是給出一種更爲巧妙的處理方法,繼續向下來看:
3)巧妙處理setVisible(true)的阻塞問題
經過上面的說明,瞭解到,在某個事件中增加模態框的相關內容,會在遇到setVisible(true)之後造成線程阻塞,無法繼續進行後續的操作,那換個角度來看,可以把原本事件響應要做的事放到模態框的事件處理當中,這樣做就不需要在主線程中顯示完模態框再去做其他的操作。
簡單來說,在原本要執行事件響應的地方,先不做具體的操作,只是完成模態框的顯示,而在模態框獲取焦點之後執行原本事件響應需要做的事,執行完該操作之後關閉模態框就好。
下面給出模態框的一個應用場景,用戶登錄界面:
用戶點擊登錄按鈕之後,觸發某個事件響應操作
jbtnLogin.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
dealUserLogin();
}
});
private void dealUserLogin() {
String id = jtxtUserName.getText();
String password = new String(jpswPassword.getPassword());
password = String.valueOf(password.hashCode());
IUserAction userAction = rmiClient.getProxy(IUserAction.class,
jfrmLogin, "正在登錄,請稍後……");
UserInfo user = userAction.userLogin(id, password);
if (user.getId().equalsIgnoreCase("ERROR")) {
ViewTool.showError(jfrmLogin, user.getNick());
jpswPassword.setText("");
jtxtUserName.selectAll();
jtxtUserName.requestFocus();
return;
}
jbtnLogin.setEnabled(true);
}
有關 rmiClient.getProxy(IUserAction.class, jfrmLogin, “正在登錄,請稍後……”)方法如下,這裏用代理機制執行相關方法(有關代理機制可以查看java的兩種動態代理機制
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<?> interfacer, JFrame frame, String caption) {
ClassLoader classLoader = interfacer.getClassLoader();
Class<?>[] interfaces = new Class<?>[] { interfacer };
return (T) Proxy.newProxyInstance(classLoader, interfaces,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RMIModelDialog dialog = new RMIModelDialog(frame, true)
.setMethodInvoker(methodInvoker)
.setMethod(method)
.setArgs(args);
dialog.setCaption(caption);
dialog.showDialog();
return dialog.getResult();
}
});
}
像上面所說的,在代理執行相關方法的地方不做相關操作,只完成模態框的顯示,在這裏對於代理機制的應用也有了很大的突破,在代理中並沒有做什麼實質性的操作,而把真正的執行方法的具體操作放在了處理模態框的地方:
@Override
public <T> T methodInvoke(Object object, Method method, Object[] args) {
Socket socket = null;
DataOutputStream dos = null;
DataInputStream dis = null;
try {
socket = new Socket(rmiServerIp, rmiServerPort);
dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(method.toString());
dos.writeUTF(getArgs(args));
dis = new DataInputStream(socket.getInputStream());
String resultString = dis.readUTF();
Type returnType = method.getGenericReturnType();
@SuppressWarnings("unchecked")
T result = (T) ArgumentMaker.gson.fromJson(resultString, returnType);
return result;
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null && !socket.isClosed()) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
該操作真正執行的地方在模態框處理事件的方法中,執行完之後關閉模態框;
@Override
public void dealAction() {
addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
result = methodInvoker.methodInvoke(null, method, args);
closeDialog();
}
});
}
以上操作就基本處理了有關模態框的阻塞問題,總之,在使用模態框的時候,要特別注意模態框的關閉操作到底應該放在哪裏,纔會避開setVisible(true)之後的線程阻塞問題,這裏我們用了一種可以說很取巧的方法來處理。