2011-09-10 17 views
5

Soy bastante nuevo en el desarrollo de Swing, espero que mi pregunta no sea estúpida.Cambios temporales del propietario de foco a cero

Tengo un problema siguiente. Estoy siguiendo el enfoque usando KeyboardFocusManager, escuchando los cambios de la propiedad permanentFocusOwner. Sin embargo, cuando el foco cambia de un control a otro, obtengo el cambio intermedio de la propiedad permanentFocusOwner a null.

Mi lógica de IU actual está realizando algunos cambios en los controles cuando el foco está dentro de uno de los paneles o sus paneles secundarios. Sin embargo, obtener el intermedio null rompe esta lógica.

He buscado en Google para obtener información sobre este problema, no he encontrado nada relevante.

La pregunta es si este comportamiento es por diseño y si hay alguna manera de solucionar nulos intermedios.

Aquí está la aplicación mínima reproducir dicho comportamiento:

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

La salida del registro es:

(inicio del programa, se centran conjuntos de botón 1 de forma automática)
permanentFocusOwner cambió de : null
permanentFocusOwner cambiado a: javax.swing.JButton [, 0,18,41x26, (omitido)]
(hace clic en el botón 2)
permanentFocusOwner cambiado de: javax.swing.JButton [, 0,18,41x26, (omitidos)]
permanentFocusOwner cambió a: null
permanentFocusOwner cambió de: null
permanentFocusOwner cambiado a: javax.swing.JButton [, 41,18,41x26, (omitido)]


(parte opcional, en la intención de código)
Mi objetivo es hacer que algo parezca una vista de lista, donde las entradas se expanden y muestran más información cuando obtienen el foco (y colapsan cuando lo pierden). La vista expandida contiene algunos botones adicionales.

JList no parece ser el control apropiado, porque (1) no permite clics en los botones, y (2) sus entradas tienen altura constante, mientras que quiero que las entradas se expandan dinámicamente en el enfoque. JTable con su modo de edición tampoco parece ser una solución adecuada, al menos debido al tamaño constante de las entradas.

Así que estoy usando plain JPanel con un diseño de caja vertical como contenedor, y suscribo a los cambios del modelo y actualizo las imágenes manualmente. El problema es que cuando hago clic en un botón, el elemento de la lista que lo contiene pierde foco. Pude detectar que el foco aún permanece dentro del elemento de la lista si el foco no cambia a null temporalmente.

+0

+ 1 para [sscce] (http://sscce.org/), pero más detalles sobre el objetivo pueden informar una mejor solución. – trashgod

+0

@trashgod: agregué algunos detalles sobre la intención del código – Vlad

Respuesta

2

KeyboardFocusManager está disparando dos eventos para la mayoría de las propiedades (a partir de especificaciones granos no, se debe - nunca encontró la razón, sólo una suposición de que la naturaleza asynchrous de enfoque de alguna manera podría ser la razón)

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

para hacer cosas dependiendo de newVaue, espere el segundo

+1

No puedo evitar especular que este es el resultado de las variaciones multiplataforma en cómo una ventana gana enfoque. – trashgod

2

Como solución temporal, almacene el último propietario de foco anterior "real" como miembro en su controlador de eventos.

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

Entonces tendrás un identificador para ese objeto cuando uno se concentra en realidad aterriza en el objetivo. Maneje los cambios de resaltado solo cuando un componente real realmente recibe el foco (es decir, cuando getNewValue() no es nulo).

(El comportamiento parece consistente con lo que se describe en The AWT Focus Subsystem, en los sens que el componente anterior pierde su foco en primer lugar, a continuación, el componente de destino se pone. No es atómica, por lo que es un período de tiempo en el que nada tiene de hecho Focus. Pero no soy un experto, por lo que este puede variar.)

2

Mi objetivo es hacer algo parecido a una vista de lista, donde las entradas se expanden y se muestran más información cuando reciben el foco.

Una alternativa, a veces uso un JSplitPane: a la izquierda, puse el (enfocable) botón de expansión en un JTable, Outline o vertical Box de paneles; a la derecha, puse la vista expandida.

+0

¡Gracias por tu sugerencia! Si bien esto llevaría a una interfaz de usuario diferente, definitivamente es un diseño agradable. (Y no me enteré de 'Esquema' antes). – Vlad

+0

Por cierto, ¿hay una manera fácil de enlazar los hijos de JPanel/Box a un modelo, de la misma manera que los elementos de JTable pueden vincularse a un modelo de tabla? – Vlad

+0

Normalmente le doy al botón un ['Action'] (http://download.oracle.com/javase/tutorial/uiswing/misc/action.html) en el constructor' JPanel'. El método 'actionPerformed()' consulta el modelo de datos de la aplicación y actualiza la vista expandida. La 'Acción' se puede invocar desde un botón, menú, barra de herramientas o un oyente de enfoque. – 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 puede hacer que mediante el uso JButton, muy agradable de salida es mediante el uso de JToggleButton o todavía está allí idea original con la mantuvo allí 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

¡Muchas gracias por su código! Sin embargo, veo algunos problemas con respecto a lo que me gustaría implementar. Primero, la expansión se alterna mediante la activación del botón. Esto es lo mismo que obtener el foco si el usuario lo hace con el mouse, pero si el foco se establece enfocado por teclado (por ejemplo, mediante tabulación), el botón no se activará. En segundo lugar, el panel debería colapsar tan pronto como el foco lo abandone. – Vlad

+0

si no hay MouseListener entonces ButtonModel funciona para mí con javax.swing.Timer (para mostrar con retraso, o si hay más de una Acción, o posible concurencia de JComponents más cercanos) y KeyBinding (no TAB, pero F1, F2 o F5) puede sobrar eso, pero sigo pensando que sería mejor implementar JWindow como Container en lugar de saltar JPanel – mKorbel

+0

No estoy seguro de entender qué MouseListener mencionaste exactamente. Para que ButtonModel funcione, debemos asegurarnos de que el botón esté presionado siempre que el foco esté en el botón o en cualquier elemento del panel expandido. ¿Hay alguna manera fácil de hacerlo? Por ahora, estoy siguiendo el enfoque como en el ejemplo, ignorando los cambios a null. La interfaz de usuario no está definida por mí, por lo que los paneles de salto parecen ser inevitables. – Vlad

Cuestiones relacionadas