我正在编写一个带有工具栏颜色的下拉组件。所以我从"Swing hacks"一书中汲取了一些想法,稍微改变了概念,并添加了Swing的标准JColorChooser。行为应如下:我单击一个按钮,出现一个带有颜色选择器的窗口;我选择一种颜色,下拉窗口关闭,按钮的文本将颜色变为所选颜色。总的来说,一切正常,但有一个令人不快的错误。在这些操作之后,UI 冻结,按钮甚至不接受"鼠标悬停"等鼠标事件。这种情况一直发生,直到我点击。然后,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上尝试了您的代码。但是,我认为您的代码需要改进。首先,它试图做的事情似乎很冗长。其次,你正在使用一些最好避免的方法。
在你的第一段中,你说你的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 真的冻结了吗?您有响应能力问题吗?或者它只是一个焦点问题,需要您点击比您想要的更多。