Quantcast
Channel: BlogJava-Jiangshachina-随笔分类-Swing
Viewing all articles
Browse latest Browse all 7

数据加载模糊进度指示面板的实现与应用(原)

$
0
0
数据加载模糊进度指示面板的实现与应用
当在加载数据(或其它耗时工作)时,需要显示一个进度指示面板,本文介绍了一种简易的实现方式。(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。



Sha Jiang 2009-11-29 20:33 发表评论

Viewing all articles
Browse latest Browse all 7