John Jiang

a cup of Java, cheers!
https://github.com/johnshajiang/blog

   :: 首页 ::  :: 联系 :: 聚合  :: 管理 ::
  131 随笔 :: 1 文章 :: 530 评论 :: 0 Trackbacks
数据加载模糊进度指示面板的实现与应用
当在加载数据(或其它耗时工作)时,需要显示一个进度指示面板,本文介绍了一种简易的实现方式。(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中的两个组件:JXPanelStackLayout。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(400300));
        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。

posted on 2009-11-29 20:33 John Jiang 阅读(2177) 评论(5)  编辑  收藏 所属分类: JavaSEJavaSwing原创

评论

# re: 数据加载模糊进度指示面板的实现与应用(原) 2009-12-01 12:18 创意礼品
非常好的文章,谢谢楼主分享!非常好的文章,谢谢楼主分享!  回复  更多评论
  

# re: 数据加载模糊进度指示面板的实现与应用(原) 2009-12-01 20:12 BeanSoft
兄弟 这种情况我个人觉得用 JRootPane 会更好一些.

不过我的代码没考虑键盘的事件 呵呵 很早以前自己搞的玩意了

package beansoft.swing;

import javax.swing.*;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.FlowLayout;
import java.awt.event.*;

/**
* We have to provide our own glass pane so that it can paint a loading
* dialog and then the user can see the progress but can't operation
* the GUI, it's a transparent pane so the below contents is visible.
*
* Also please refer to articles by Sun - How to Use Root Panes:
* {@link http://java.sun.com/docs/books/tutorial/uiswing/components/rootpane.html}
* @author Jacky Liu
* @version 1.0 2006-08
*/
public class LoadingGlassPane extends JPanel {
private static final long serialVersionUID = 1L;
/**
* A label displays status text and loading icon.
*/
private JLabel statusLabel = new JLabel("Reading data, please wait...");

public LoadingGlassPane() {
try {
statusLabel.setIcon(new ImageIcon(getClass().getResource(
"loading.gif")));
} catch (RuntimeException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

statusLabel.setHorizontalAlignment(JLabel.CENTER);

// Must add a mouse listener, otherwise the event will not be
// captured
this.addMouseListener(new java.awt.event.MouseAdapter() {
public void mousePressed(MouseEvent e) {
}
});

this.setLayout(new BorderLayout());

this.add(statusLabel);
// Transparent
setOpaque(false);
}

public JLabel getStatusLabel() {
return statusLabel;
}

/**
* Set the text to be displayed on the glass pane.
*
* @param text
*/
public void setStatusText(final String text) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
statusLabel.setText(text);
}
});
}

/**
* Install this to the jframe as glass pane.
* @param frame
*/
public void installAsGlassPane(JFrame frame) {
frame.setGlassPane(this);
}

/**
* A small demo code of how to use this glasspane.
* @param args
*/
public static void main(String[] args) {
JFrame frame = new JFrame("Test GlassPane");
final LoadingGlassPane glassPane = new LoadingGlassPane();
glassPane.installAsGlassPane(frame);

JButton button = new JButton("Test Query");

button.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
// Call in new thread to allow the UI to update
Thread th = new Thread() {
public void run() {
glassPane.setVisible(true);
glassPane.setCursor(new Cursor(Cursor.WAIT_CURSOR));
// TODO Long time operation here
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
glassPane.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
glassPane.setVisible(false);
}
};

th.start();
}

});

frame.getContentPane().setLayout(new FlowLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);

frame.setSize(200, 200);
frame.setVisible(true);

}

}  回复  更多评论
  

# re: 数据加载模糊进度指示面板的实现与应用(原) 2009-12-02 08:50 Sha Jiang
@BeanSoft
我浏览了一下你的代码,你使用的方法就是我在文章末尾提到的"将LoadingPanel设置为GlassPane..."这种方法。
一般地,若要使用GlassPane时,最好优先考虑使用LayeredPane。
但我必须要说,使用StackLayout最灵活性,也最简单。  回复  更多评论
  

# re: 数据加载模糊进度指示面板的实现与应用(原) 2009-12-20 01:25 Krista31
Click <a href="http://www.4writers.net">freelance writing </a> when you want get some knowledge just about this topic.   回复  更多评论
  

# re: 数据加载模糊进度指示面板的实现与应用(原) 2011-02-11 21:14 外贸女装批发
正好有一朋友问到这个问题,就借大作献佛了。呵呵。多谢。~~  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: