焦点所有者临时更改为null

我对Swing开发很新,希望我的问题不是一个愚蠢的问题。

我有一个以下问题。 我正在使用KeyboardFocusManager跟踪焦点,监听属性permanentFocusOwner变化。 但是当焦点从一个控件变为另一个控件时,我将permanentFocusOwner属性的中间更改为null

当焦点位于其中一个面板或其子面板内时,我当前的UI逻辑正在对控件进行一些更改。 但是,获得中间null会打破这种逻辑。

我在谷歌搜索有关此问题的信息,没有发现任何相关信息。

问题是,这种行为是否是设计的,以及是否有某种方法可以解决中间空值。

这是复制上述行为的最小应用程序:

 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,41×26,(跳过)]
(点击按钮2)
permanentFocusOwner已更改为:javax.swing.JButton [,0,18,41×26,(跳过)]
permanentFocusOwner更改为:null
permanentFocusOwner从:null变为
permanentFocusOwner更改为:javax.swing.JButton [,41,18,41×26,(跳过)]


(可选部分,关于代码意图)
我的目标是创建一个看起来像列表视图的东西,其中条目扩展并在它们获得焦点时显示更多信息(并在它们丢失时折叠回来)。 展开的视图包含一些其他按钮。

JList似乎不是合适的控件,因为(1)它不允许点击按钮,(2)它的条目具有恒定的高度,而我希望条目在焦点上动态扩展。 具有编辑模式的JTable似乎也不是一个合适的解决方案,至少因为常量输入大小。

所以我使用带有垂直框布局的普通JPanel作为容器,并订阅模型更改并手动更新视觉效果。 问题是,当我单击按钮时,包含列表项失去焦点。 如果焦点暂时不会变为null ,我可以检测到焦点仍然在列表项内。

KeyboardFocusManager为大多数属性触发两个事件(从bean规范开始,它不应该 – 从未找到原因,只是猜测焦点的异步性质可能是某种原因)

  firePropertyChange(someProperty, oldValue, null) firePropertyChange(someProperty, null, newValue) 

根据newVaue做的事情,等待第二个

解决方法是,将最后一个“真正的”前一焦点所有者存储为事件处理程序中的成员。

 if ((e.getOldValue() != null) && (e.getNewValue() == null)) prev_owner = e.getOldValue(); 

然后,当您实际聚焦在目标上时,您将拥有该对象的句柄。 仅当实际组件实际获得焦点时(即,当getNewValue()为非null时)才处理突出显示更改。

(行为似乎与AWT焦点子系统中描述的一致,在前一个组件首先失去焦点,然后目标组件获得它的意义上。它不是primefaces的,所以有一段时间没有任何实际的焦点但我不是专家,所以这可能会有所不同。)

我的目标是制作看起来像列表视图的内容,其中条目扩展并在获得焦点时显示更多信息。

另一种选择,我有时会使用JSplitPane :在左侧,我将(可聚焦的)展开按钮放在JTableOutline或垂直面板中; 在右边,我把展开的视图。

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