7

我正在编写一个带有工具栏颜色的下拉组件。所以我从“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() 中。并且没有成功。

我错过了什么?

4

1 回答 1

1

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

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

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 事件线程应该仅用于观察事件以用于分析、测试和调试等目的。你永远不应该用它来改变你的 UI 状态或向它推送昂贵的代码。对于 UI 更改,您总是在 UI 组件上使用特定的事件侦听器,或者SwingWorkers进行更昂贵的计算。

根据您使用的平台或 Java 运行时实现,您对 AWT 事件线程的使用可能会导致 UI 变得有些迟钝,因为您正在从中更改窗口的可见性状态。

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

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

您始终可以将其视为 Java 运行时环境将其推迟到事件线程。所以基本上发生的事情是这样的:

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();
    }
}

请尝试此代码并告诉我您的问题是否消失。如果没有,请进一步详细说明您所经历的影响。UI真的冻结了吗?你有反应问题吗?或者它只是一个焦点问题,需要你点击比你想要的更多。

于 2013-11-16T15:07:53.373 回答