关闭子窗口后,UI会冻结,直到第一次单击

时间:2013-01-29 05:55:52

标签: java swing jwindow jcolorchooser

我正在编写一个带有工具栏颜色的下拉组件。所以我从'Swing hacks'一书中提出了想法,稍微改变了构思并添加了Swing的标准JColorChooser。行为应该遵循:我点击一个按钮,出现一个带有颜色选择器的窗口;我选择一种颜色,然后关闭下拉窗口,按钮的文字将颜色变为拾取的颜色。 总的来说一切正常,但有一个令人不快的错误。在这些操作之后,UI冻结,按钮甚至不接受鼠标事件,如“鼠标悬停”。这发生在我点击之前。用户界面的行为符合要求。

这是带概念的代码。

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.colorchooser.AbstractColorChooserPanel;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.metal.MetalComboBoxIcon;

class DropDownComponent2 {
    private JWindow _window;
    private boolean _windowShouldBeShown = false;
    private JComponent _component;
    private AbstractButton _button;
    private JFrame _ownerFrame;

    public DropDownComponent2(JFrame ownerFrame, JComponent component, AbstractButton button) {
        _ownerFrame = ownerFrame;
        _component = component;
        _button = button;
        _button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _window.setVisible(false);
                Point pt = _button.getLocationOnScreen();
                pt.translate(0, _button.getHeight());
                _window.setLocation(pt);
                showWindow();
                _windowShouldBeShown = true;
            }
        });

        _button.addAncestorListener(new AncestorListener() {
            public void ancestorAdded(AncestorEvent event){
                _window.setVisible(false);
            }
            public void ancestorRemoved(AncestorEvent event){
                _window.setVisible(false);
            }
            public void ancestorMoved(AncestorEvent event){
                if (event.getSource() != _window) {
                    System.out.println("Ansestor moved");
                    _window.setVisible(false);
                }
            }
        });

        Toolkit.getDefaultToolkit().addAWTEventListener(
                new AWTEventListener() { 
                    public void eventDispatched(AWTEvent event) {
                        if (event.getID() == MouseEvent.MOUSE_CLICKED) {
                            if ( !_window.getBounds().contains( MouseInfo.getPointerInfo().getLocation() )) {
                                if (_windowShouldBeShown)
                                    _windowShouldBeShown = false;
                                else {
                                    _window.setVisible(false);
                                }
                            }
                        }
                    }            
                }, AWTEvent.MOUSE_EVENT_MASK);

        _window = new JWindow(_ownerFrame);
        _window.getContentPane().add(component);
        _window.addWindowFocusListener(new WindowAdapter() {
            public void windowLostFocus(WindowEvent evt) {
                System.out.println("window lost focus");
                _window.setVisible(false);
            }
        });
        _window.pack();        
    }

    private Rectangle getScreenRect() {
        return new Rectangle(java.awt.Toolkit.getDefaultToolkit().getScreenSize());
    }

    public void showWindow() {
        Rectangle screenRect = getScreenRect();
        Rectangle windowRect = _window.getBounds();

        int sx1 = screenRect.x;
        int sx2 = screenRect.x + screenRect.width;
        int sy1 = screenRect.y;
        int sy2 = screenRect.y + screenRect.height;

        int wx1 = windowRect.x;
        int wx2 = windowRect.x + windowRect.width;
        int wy1 = windowRect.y;
        int wy2 = windowRect.y + windowRect.height;

        if (wx2 > sx2) {
            _window.setLocation(wx1-(wx2-sx2), _window.getY());
        }
        if (wx1 < sx1) {
            _window.setLocation(0, _window.getY());
        }
        if (wy2 > sy2) {
            _window.setLocation(_window.getX(), wy1-(wy2-wy1));
        }
        if (wy2 < sy1) {
            _window.setLocation(_window.getX(), 0);
        }

        _window.setVisible(true);
    }

    public void hideWindow() {
        _window.setVisible(false);
    }  
}

public class DropDownFrame extends JFrame {
    JButton _button;
    JColorChooser _colorChooser;
    DropDownComponent2 _dropDown;
    JWindow _window;

    public DropDownFrame() {
        _colorChooser = new JColorChooser();
        _colorChooser.setPreviewPanel(new JPanel());
        _colorChooser.setColor(Color.RED);

        // Remove panels other than Swatches
        AbstractColorChooserPanel[] panels = _colorChooser.getChooserPanels();
        for (int i=0; i<panels.length; i++) {
            if (!panels[i].getDisplayName().equals("Swatches"))
                _colorChooser.removeChooserPanel(panels[i]);
        }
        _colorChooser.getSelectionModel().addChangeListener(new ChangeListener() {
            // ### I think the key point is there
            @Override
            public void stateChanged(ChangeEvent e) {
                _dropDown.hideWindow();
                _button.setForeground(_colorChooser.getColor());
            }

        });            

        _button = new JButton("Show JWindow");
        _button.setIcon(new MetalComboBoxIcon());
        _button.setHorizontalTextPosition(SwingConstants.LEFT);
        this.getContentPane().add(_button);

        _dropDown = new DropDownComponent2(DropDownFrame.this, _colorChooser, _button);

        pack();
        setVisible(true);        
    }

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new DropDownFrame();
            }
        });
    }
}

我确信JColorChooser和选择模型有一些东西。但我无法理解这个想法。 我尝试了requestFocus()和requestFocusInWindow()。没有成功。 我试图使用JDialog而不是JWindow。当我在对话框上按[x]时,一切都按照需要,但当我选择颜色时,UI也会冻结!

另一点!如果我在下拉窗口中使用标签而不是颜色选择器并处理标签上的点击,一切正常:窗口关闭,没有冻结!

我将_dropDown.hideWindow()放在SwingUtilities.invokeLater()中。但没有成功。

我错过了什么?

1 个答案:

答案 0 :(得分:1)

与您的问题的其他评论一样,我无法重现UI的冻结。我在Windows 7,Sun JDK 7和Linux Mint,OpenJDK 7上尝试了您的代码。但是,我认为您的代码需要改进。首先,它试图做的事情似乎相当冗长。第二,你正在使用一些最好避免使用的方法。

在你的第一段中,你说你的用户界面会冻结,直到你点击某处。这听起来很矛盾。如果它冻结了你应该无法点击让它再次工作。所以我假设你只是一个焦点问题?如我错了请纠正我。实际上,在选择颜色后按钮会失去焦点,因此您必须再次单击两次以再次打开颜色选择器。因此,您的更改侦听器应如下所示:

public void stateChanged(ChangeEvent ce) {
    button.setForeground(colorChooser.getColor());
    DropDownWindow.this.setVisible(false);
    // the drop down window had the focus while being displayed
    // so after it closes, return focus to the button
    button.requestFocus();
}

其次,您直接在AWT事件调度线程上注册侦听器以捕获一些鼠标事件。我不明白为什么你这样做而不是使用UI组件的普通鼠标监听器。 AWT事件线程仅应用于oborserve事件,以用于分析,测试和调试等目的。永远不应该使用它来改变UI的状态或将昂贵的代码推送到它。对于UI更改,您始终在UI组件上使用特定事件侦听器,或SwingWorkers进行更昂贵的计算。

根据您使用的平台或Java Runtime实现,您使用AWT事件线程可能会导致UI变得有些反应,因为您正在更改窗口的可见性状态。

此外,使用invokeLater创建下拉窗口并不会改变任何内容,因为这只会将代码放在AWT事件线程中,无论如何它都会在此处结束。方法invokeLater及其朋友invokeAndWait用于同步Java的所谓初始线程(其中一个执行main方法,因此被称为'主'线程)事件派遣线程。您不要使用它们从UI线程异步运行代码。例如,如果你运行一个main方法来创建一个像这样的窗口或框架

public static void main (String[] args) {
    new JFrame().setVisible(true);
}

您始终可以将其视为Java Runtime Environment延迟到事件线程。所以基本上会发生这样的事情:

public static void main (String[] args) {
    // JRE 'starts' Swing by creating an event thread and then
    SwingUtilities.invokeLater(new Runnable() {
    public void run (Runnable r) {
        new JFrame().setVisible(true);
    });
}

因此,您的主要方法将该代码封装在另一个Runnable中,这基本上具有相同的效果并且无法解决您的问题。

我重写了你的程序并将其缩短了一点。我不确定它是否完全符合您的要求。我在Windows和Linux上试过这个代码没有任何问题。无论您是否打开颜色选择器,您都可以看到按钮上的鼠标事件仍在处理中。

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JWindow;
import javax.swing.colorchooser.AbstractColorChooserPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

class DropDownWindow extends JWindow {
    static void setColorChooserPanels (JColorChooser jcc, String name) {
        for (AbstractColorChooserPanel p : jcc.getChooserPanels()) {
            if (!p.getDisplayName().equals(name)) {
                jcc.removeChooserPanel(p);
            }
        }
    }

    final JColorChooser colorChooser;

    DropDownWindow (JFrame ownerFrame, final JButton button) {
        super(ownerFrame);

        colorChooser = new JColorChooser();
        setColorChooserPanels(colorChooser, "Swatches");
        colorChooser.setVisible(true);
        colorChooser.getSelectionModel().addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent ce) {
                button.setForeground(colorChooser.getColor());
                DropDownWindow.this.setVisible(false);
                button.requestFocus();
            }
        });

        add(colorChooser);
        setSize(colorChooser.getPreferredSize());
        pack();

        button.addActionListener(new ActionListener() {
            public void actionPerformed (ActionEvent e) {
                Point pt = button.getLocationOnScreen();
                pt.translate(0, button.getHeight());
                DropDownWindow.this.setLocation(pt);
                DropDownWindow.this.setVisible(true);
            }
        });
    }
}

class MyFrame extends JFrame {
    final JButton button;

    MyFrame () {
        super();

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(300, 100);
        setLocation(500, 300);

        button = new JButton("Choose Color");

        button.addMouseListener(new MouseAdapter() {
            public void mouseEntered (MouseEvent event) {
                System.out.println("mouse entered at: (" + event.getXOnScreen() +
                        ", " + event.getYOnScreen() + ")");
            }
        });

        button.addMouseListener(new MouseAdapter() {
            public void mouseExited (MouseEvent event) {
                System.out.println("mouse exited at: (" + event.getXOnScreen() +
                        ", " + event.getYOnScreen() + ")");
            }
        });

        add(button);

        DropDownWindow ddw = new DropDownWindow(this, button);

        setVisible(true);
    }
}

public class Test {
    public static void main(String[] args) {
        new MyFrame();
    }
}

请尝试使用此代码并告诉我您的问题是否消失。如果没有,请详细说明您体验的效果。用户界面真的冻结了吗?你有响应问题吗?或者它只是一个焦点问题,需要您点击超过您想要的。