java - 子窗口关闭后 UI 卡住,直到第一次单击

标签 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() 中。但没有成功。

我错过了什么?

最佳答案

与您问题的其他评论一样,我无法重现 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 事件线程应该只用于出于分析、测试和调试等目的观察事件。永远不要使用它来改变 UI 的状态或向其推送昂贵的代码。对于 UI 更改,您始终在 UI 组件上使用特定的事件监听器,或者使用 SwingWorkers 进行更昂贵的计算。

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

此外,您使用 invokeLater 创建下拉窗口不会改变任何内容,因为这只是将代码放置在 AWT 事件线程中,无论如何它都会在那里结束。方法 invokeLater 及其 friend invokeAndWait 用于同步 Java 所谓的初始线程(其中一个执行 main 方法,因此是称为“主”线程)与事件分派(dispatch)线程。您不使用它们从 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);
    });
}

因此您的 main 方法将该代码封装在另一个 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();
    }
}

请尝试此代码并告诉我您的问题是否消失。如果没有,请进一步详细说明您体验到的效果。用户界面真的会卡住吗?你有反应问题吗?或者这只是一个焦点问题,需要您点击的次数超出您的预期。

关于java - 子窗口关闭后 UI 卡住,直到第一次单击,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14576533/

相关文章:

java - JTextArea 的开始和结束索引检索

java - 将 Matlab 组件添加到 Java JWindow

Java内联方法/截断字符串的策略?

java - Android 通知在现有 Activity 上打开

java - 使用 Wildfly 的 JAX-RS ChunkedOutput 和 ChunkedInput

java - 如何在另一个测试类中重用现有的 JUnit 测试?

java - 如何将容器内的内部框架图标化到特定位置

java - 如何在 Matlab 中自定义 JIDE 网格

java - 从另一个线程更新 JLabel

java - 如何在没有任何确定或取消选项的情况下在 Java 中仅显示纯消息?