我对Swing开发很新,希望我的问题不是一个愚蠢的问题。
我遇到了以下问题。我正在使用KeyboardFocusManager
跟踪焦点,监听属性permanentFocusOwner
更改。但是,当焦点从一个控件更改为另一个控件时,我将permanentFocusOwner
属性中间更改为null
。
当焦点位于其中一个面板或其子面板内时,我当前的UI逻辑正在对控件进行一些更改。但是,获得中间null
打破了这种逻辑。
我在Google上搜索了有关此问题的信息,但未发现任何相关信息。
问题是,这种行为是否是设计的,以及是否有某种方法可以解决中间空值。
这是复制上述行为的最小应用程序:
import java.awt.*;
import java.beans.*;
import javax.swing.*;
public class FocusNullTest extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
FocusNullTest self = new FocusNullTest();
self.setVisible(true);
}
});
}
public FocusNullTest() {
setSize(150, 100);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.X_AXIS));
contentPane.add(new JButton("1"));
contentPane.add(new JButton("2"));
KeyboardFocusManager focusManager =
KeyboardFocusManager.getCurrentKeyboardFocusManager();
focusManager.addPropertyChangeListener(
"permanentFocusOwner",
new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
System.out.println("permanentFocusOwner changed from: "
+ e.getOldValue());
System.out.println("permanentFocusOwner changed to : "
+ e.getNewValue());
}
});
}
}
日志输出为:
(程序启动,焦点自动设置为按钮1)
permanentFocusOwner已更改为:null
permanentFocusOwner更改为:javax.swing.JButton [,0,18,41x26,(跳过)]
(点击按钮2)
permanentFocusOwner已更改为:javax.swing.JButton [,0,18,41x26,(跳过)]
permanentFocusOwner更改为:null
permanentFocusOwner已更改为:null
permanentFocusOwner更改为:javax.swing.JButton [,41,18,41x26,(跳过)]
(可选部分,关于代码意图)
我的目标是创建一个看起来像列表视图的东西,其中条目扩展并在它们获得焦点时显示更多信息(并在它们丢失时折叠回来)。展开的视图包含一些额外的按钮。
JList
似乎不是合适的控件,因为(1)它不允许点击按钮,(2)它的条目具有恒定的高度,而我希望条目动态扩展在焦点上。 JTable
及其编辑模式似乎也不是一个合适的解决方案,至少因为常量条目大小。
所以我使用带有垂直框布局的普通JPanel
作为容器,并订阅模型更改并手动更新视觉效果。问题是,当我单击按钮时,包含列表项失去焦点。如果焦点不会暂时变为null
,我可以检测到焦点仍然在列表项中。
答案 0 :(得分:2)
KeyboardFocusManager为大多数属性触发两个事件(从bean规范开始,它不应该 - 从未找到原因,只是猜测焦点的异步性质可能是某种原因)
firePropertyChange(someProperty, oldValue, null)
firePropertyChange(someProperty, null, newValue)
根据newVaue做的事情,等待第二次
答案 1 :(得分:2)
作为解决方法,将最后一个“真正的”前一焦点所有者存储为事件处理程序中的成员。
if ((e.getOldValue() != null) && (e.getNewValue() == null))
prev_owner = e.getOldValue();
然后,当您实际聚焦于目标时,您将拥有该对象的句柄。仅当实际组件实际获得焦点时(即getNewValue()
非空时)处理突出显示更改。
(行为似乎与The AWT Focus Subsystem中所描述的一致,在前一个组件首先失去焦点,然后目标组件得到它的意义上。它不是原子的,所以有一段时间在哪里没有什么实际上有焦点。但我不是专家,所以这可能会有所不同。)
答案 2 :(得分:2)
我的目标是制作一个看起来像列表视图的内容,其中条目会扩展,并在获得焦点时显示更多信息。
另一种选择,我有时使用JSplitPane
:在左侧,我将(可聚焦的)展开按钮放在JTable
,Outline
或垂直Box
的面板;在右边,我把展开的视图。
答案 3 :(得分:2)
My goal is to make something looking like a list view, where the entries
expand and display more information when they get focus (and collapse back
when they lose it). The expanded view contains some additional buttons.
ButtonModel可以通过使用JButton来做到这一点,非常好的输出是使用JToggleButton还是仍然有原创的想法与JPanel + MouseListener()
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class CollapsablePanelTest {
public static void main(String[] args) {
CollapsablePanel cp = new CollapsablePanel("test", buildPanel());
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(cp));
f.setSize(360, 300);
f.setLocation(200, 100);
f.setVisible(true);
}
public static JPanel buildPanel() {
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(2, 1, 2, 1);
gbc.weightx = 1.0;
gbc.weighty = 1.0;
JPanel p1 = new JPanel(new GridBagLayout());
p1.setBackground(Color.blue);
gbc.gridwidth = GridBagConstraints.RELATIVE;
p1.add(new JButton("button 1"), gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
p1.add(new JButton("button 2"), gbc);
gbc.gridwidth = GridBagConstraints.RELATIVE;
p1.add(new JButton("button 3"), gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
p1.add(new JButton("button 4"), gbc);
return p1;
}
private CollapsablePanelTest() {
}
}
class CollapsablePanel extends JPanel {
private static final long serialVersionUID = 1L;
private boolean selected;
private JPanel contentPanel_;
private HeaderPanel headerPanel_;
private class HeaderPanel extends JButton /*JToggleButton //implements MouseListener*/ {
private static final long serialVersionUID = 1L;
private String __text;
private Font __font;
private BufferedImage open, closed;
private final int OFFSET = 30, PAD = 5;
public HeaderPanel(String text) {
//addMouseListener(this);
__text = text;
setText(__text);
__font = new Font("sans-serif", Font.PLAIN, 12);
// setRequestFocusEnabled(true);
setPreferredSize(new Dimension(200, 30));
int w = getWidth();
int h = getHeight();
/*try {
open = ImageIO.read(new File("images/arrow_down_mini.png"));
closed = ImageIO.read(new File("images/arrow_right_mini.png"));
} catch (IOException e) {
e.printStackTrace();
}*/
getModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
ButtonModel model = (ButtonModel) e.getSource();
if (model.isRollover()) {
toggleSelection();
} else if (model.isPressed()) {
toggleSelection();//for JToggleButton
}
}
});
}
/*@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int h = getHeight();
///if (selected)
//g2.drawImage(open, PAD, 0, h, h, this);
//else
//g2.drawImage(closed, PAD, 0, h, h, this);
// Uncomment once you have your own images
g2.setFont(font);
FontRenderContext frc = g2.getFontRenderContext();
LineMetrics lm = font.getLineMetrics(__text, frc);
float height = lm.getAscent() + lm.getDescent();
float x = OFFSET;
float y = (h + height) / 2 - lm.getDescent();
g2.drawString(__text, x, y);
}
@Override
public void mouseClicked(MouseEvent e) {
toggleSelection();
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}*/
}
public CollapsablePanel(String text, JPanel panel) {
super(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(1, 3, 0, 3);
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
selected = false;
headerPanel_ = new HeaderPanel(text);
setBackground(Color.orange);
contentPanel_ = panel;
add(headerPanel_, gbc);
add(contentPanel_, gbc);
contentPanel_.setVisible(false);
JLabel padding = new JLabel();
gbc.weighty = 1.0;
add(padding, gbc);
}
public void toggleSelection() {
selected = !selected;
if (contentPanel_.isShowing()) {
contentPanel_.setVisible(false);
} else {
contentPanel_.setVisible(true);
}
validate();
headerPanel_.repaint();
}
}