2011-09-10 36 views
5

我是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,41x26,(跳過)]
(點擊按鈕2)
permanentFocusOwner由:javax.swing.JButton中[,0,18,41x26,(跳過)]
permanentFocusOwner更改爲:空
permanentFocusOwner由:空
permanentFocusOwner變更爲:javax.swing.JButton中[,41,18,41x26,(跳過)]


(可選部分地基於所述代碼意圖)
我的目標是使東西看起來像一個列表視圖,其中的條目展開並顯示更多的信息,當他們獲得焦點(並在失去時折回)。展開的視圖包含一些額外的按鈕。

JList似乎不是合適的控件,因爲(1)它不允許點擊按鈕,(2)它的條目具有恆定的高度,而我希望條目在焦點上動態擴展。編輯模式JTable似乎也不是一個合適的解決方案,至少因爲不斷的條目大小。

所以我使用普通的JPanel和一個垂直框佈局作爲容器,並訂閱模型更改並手動更新視覺效果。問題是,當我點擊一個按鈕時,包含列表項失去焦點。如果焦點暫時不會變爲null,我可以檢測到焦點仍在列表項內。

+0

+ 1 for [sscce](http://sscce.org/),但有關目標的更多詳細信息可能會提供更好的解決方案。 – trashgod

+0

@trashgod:添加了一些關於代碼意圖的細節 – Vlad

回答

2

的KeyboardFocusManager被燒成大多數屬性兩個事件(如豆類規範的,它不應該 - 從來沒有發現的原因,只是猜測,分衆的asynchrous性質在某種程度上可能是這個原因)

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

根據newVaue做些事情,等待第二個

+1

我不禁猜測這是窗口獲得焦點的跨平臺變化的結果。 – trashgod

2

作爲解決方法,將最後一個「真正的」前一個焦點所有者作爲成員存儲在事件處理程序中。

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

然後,當您將注意力集中在目標上時,您將擁有該對象的句柄。僅當實際組件實際獲得焦點時才處理突出顯示更改(即當getNewValue()非空時)。

(這種行爲看起來與The AWT Focus Subsystem中描述的一致,前面的組件首先失去了它的焦點,然後目標組件得到它。這不是原子的,所以有一段時間沒有任何實際的東西)

2

我的目標是讓東西看起來像列表視圖,其中的條目展開並顯示更多信息,當他們獲得焦點時。

一種替代方案,我有時使用一個JSplitPane:在左邊,我把(可聚焦)在JTableOutline或面板的垂直Box展開按鈕;在右側,我提出了擴展視圖。

+0

謝謝您的建議!雖然這會導致不同的用戶界面,但它絕對是一個不錯的設計。 (我之前沒有聽說過'Outline'。) – Vlad

+0

順便問一下,有沒有簡單的方法將JPanel/Box兒童綁定到模型上,就像JTable的項目可以綁定到TableModel一樣? – Vlad

+0

我通常在'JPanel'構造函數中給這個按鈕一個['Action'](http://download.oracle.com/javase/tutorial/uiswing/misc/action.html)。 'actionPerformed()'方法查詢應用程序的數據模型並更新擴展視圖。可以從按鈕,菜單,工具欄或焦點偵聽器調用「操作」。 – trashgod

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. 

enter image description here enter image description here

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

非常感謝您的代碼!但是,我發現我想實施的一些問題。首先,通過按鈕激活切換擴展。這與使用鼠標時獲得焦點相同,但如果焦點通過鍵盤設置爲焦點(例如,通過標籤),則該按鈕將不會被激活。其次,焦點一離開,專家組就會崩潰。 – Vlad

+0

如果不存在MouseListener,那麼ButtonModel適用於javax.swing.Timer(用於延遲顯示,或者是否存在多個Action或可能來自最近的JComponents的協調)&KeyBinding(不是TAB,但是F1,F2或F5)可以解決這個問題,但我仍然認爲將JWindow實現爲Container而不是跳轉JPanel – mKorbel

+0

我不確定是否瞭解您提及的MouseListener。爲了使ButtonModel正常工作,只要焦點位於按鈕或擴展面板上的任何項目上,我們就需要確保按鈕處於按下狀態 - 有沒有簡單的方法?現在,我只是像在示例中那樣跟蹤焦點,另外忽略對null的更改。用戶界面不是由我定義的,所以跳板似乎是不可避免的。 – Vlad