模態框的學習——巧妙處理setVisible(true)的阻塞問題

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)之後的線程阻塞問題,這裏我們用了一種可以說很取巧的方法來處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章