數據加載模糊進度指示面板的實現與應用
當在加載數據(或其它耗時工作)時,需要顯示一個進度指示面板,本文介紹了一種簡易的實現方式。(2009.11.30最后更新)
對于許多Swing應用,在與用戶的交互過程中可能需要與數據庫進行通信(如,加載數據)。而這個過程往往比較耗時,為了不造成"假死"現象,一般都會顯示一個模糊進度指示器(不一定使用JProgressBar,簡單地用一個圖片代替即可),當數據加載完畢后,該進度指示器自動消失。
一般地,該模糊進度指示器不會展示在一個彈出的對話框中(因為這樣不美觀),而是直接顯示在需要展示被加載數據的面板中,并且對該面板進行模糊處理。實現這一功能的關鍵就在于,在屏幕的同一區域內展示兩層面板:一層是展示數據的面板;另一層是展示進度指示器的面板。當加載數據時,顯示進度指示器面板,并模糊數據面板;當數據加載完畢后,隱藏進度指示器面板,并使數據面板清晰顯示。下面將使用org.jdesktop.swingx.StackLayout方式來實現上述功能。
1. LoadingPanel--加載指示器面板
首先創建一個加載指示器面板。如前所述,我們不必使用真正的進度條作為進度指示器,僅需要使用一張動態圖片來代替即可。LoadingPanel的完整代碼如下所示,
public class LoadingPanel extends JPanel {
private static final long serialVersionUID = 1962748329465603630L;
private String mesg = null;
public LoadingPanel(String mesg) {
this.mesg = mesg;
initUI();
interceptInput();
setOpaque(false);
setVisible(false);
}
private void initUI() {
JLabel label = new JLabel(mesg);
label.setHorizontalAlignment(JLabel.CENTER);
label.setIcon(new ImageIcon(getClass().getResource("/path/to/spinner.gif")));
setLayout(new BorderLayout());
add(label, BorderLayout.CENTER);
}
private void interceptInput() {
addMouseListener(new MouseAdapter() {});
addMouseMotionListener(new MouseMotionAdapter() {});
addKeyListener(new KeyAdapter() {});
addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
requestFocusInWindow();
}
});
setFocusTraversalKeysEnabled(false);
}
}
上述代碼很容易理解,LoadingPanel中僅有一個JLabel,它會展示一張圖片(spinner.gif)及一段信息。但有兩段代碼需要特別說明:
[1]構造器中的兩行代碼
setVisible(false);
setOpaque(false);
LoadingPanel只在加載數據時才顯示,其它時候是不顯示的,所以它默認不可見。另外,在顯示LoadingPanel的同時,我們仍然希望能看到數據面板,所以LoadingPanel應該是透明的。
[2]interceptInput方法
當LoadingPanel顯示之后,我們不希望用戶還能夠操作數據面板,那么就需要屏蔽掉用戶(鼠標,鍵盤)輸入。
addMouseListener(new MouseAdapter() {});
addMouseMotionListener(new MouseMotionAdapter() {});
addKeyListener(new KeyAdapter() {});
上述三行代碼就使得LoadingPanel能捕獲所有的鼠標與鍵盤事件,并忽略掉它們。但僅僅如此還不夠,在展示LoadingPanel時,數據面板中的某個UI組件很可能已經獲得焦點了,那么用戶仍然可以通過鍵盤操控數據面板中的組件(因為系統會把鍵盤事件發送給當前獲取焦點的組件)。而且,即使數據面板中沒有任何組件獲得焦點,用戶仍然可以通過Tab鍵把焦點轉移到數據面板中的組件上。為了阻止這一操作,還需要加上如下幾行代碼,
addComponentListener(new ComponentAdapter() { // 一旦LoadingPanel可見,即獲取焦點
@Override
public void componentShown(ComponentEvent e) {
requestFocusInWindow();
}
});
setFocusTraversalKeysEnabled(false); // 阻止用戶轉移焦點
2. 示例程序
在此處的示例程序中,數據面板(dataPanel)中僅有一個按鈕,當點擊該按鈕時會顯示loadingPanel,且模糊掉dataPanel,并會啟動一個新的線程,該線程會在睡眠大約3秒(模擬耗時的數據加載工作)之后隱藏loadingPanel,且使dataPanel重新清晰可見。
值得注意的是,該示例程序使用了SwingX中的兩個組件:JXPanel和StackLayout。JXPanel提供了一個方法(setAlpha)以方便地設置Panel的透明度(Alpha值);而StackLayout允許在同一塊區域內添加多層組件,并能同時展示所有層的組件(而,CardLayout一次只能顯示某一層的組件)。完整的示例程序如下所示,
public class LoadDataDemo extends JFrame {
private static final long serialVersionUID = 5927602404779391420L;
private JXPanel dataPanel = null; // 使用org.jdesktop.swingx.JXPanel,以方便設置清晰度
private LoadingPanel loadingPanel = null;
public LoadDataDemo() {
super("LoadData Demo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initUI();
}
private void initUI() {
JButton button = new JButton("Load Data");
button.addActionListener(handler);
dataPanel = new JXPanel(new FlowLayout(FlowLayout.CENTER));
dataPanel.add(button);
loadingPanel = new LoadingPanel("Loading
");
// 使用org.jdesktop.swingx.StackLayout,將loadingPanel置于dataPanel的上方
JPanel centerPanel = new JXPanel(new StackLayout());
centerPanel.add(dataPanel, StackLayout.TOP);
centerPanel.add(loadingPanel, StackLayout.TOP);
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(centerPanel, BorderLayout.CENTER);
}
transient private ActionListener handler = new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 將dataPanel及其子組件的清晰度設置為50%;并顯示loadingPanel
dataPanel.setAlpha(0.5F);
loadingPanel.setVisible(true);
Thread thread = new Thread() {
public void run() {
try {
Thread.sleep(3000L); // 睡眠約3秒鐘,以模擬加載數據的過程
} catch (InterruptedException e) {
e.printStackTrace();
}
// 數據加載完畢后,重新隱藏loadingPanel;并使dataPanel及其子組件重新清晰可見
loadingPanel.setVisible(false);
dataPanel.setAlpha(1F);
};
};
thread.start();
}
};
public static void main(String[] args) {
LoadDataDemo demo = new LoadDataDemo();
demo.setSize(new Dimension(400, 300));
demo.setVisible(true);
}
}
3. 不使用SwingX
SwingX為我們提供了一系列功能強大,使用簡易的Swing擴展組件,我強烈建議你去使用它。但若因故,你不準備使用它時,我們仍然有替代的解決方案,但此處僅簡述一二。
[1]對于設置Alpha值,需要創建一個繼承自JPanel的DataPanel類,覆寫paintComponent方法,在其中使用Alpha合成,
Graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
[2]對于StackLayout,我們可以使用GlassPane(玻璃窗格)或LayeredPane(分層窗格)進行替換,將LoadingPanel設置為GlassPane或LayeredPanel中的一層。由于一個JFrame只有一個GlassPane,為了程序的靈活性,一般首選使用LayeredPane。