2011-02-10 13 views
8

Estoy tratando de crear una tabla con encabezados de columna personalizados. Quiero que los encabezados de columna incluyan un botón en el que los usuarios puedan hacer clic. La función del botón será eliminar la columna de la tabla. Esencialmente, estoy tratando de construir algo como this.JTable con un botón "Cerrar" en el encabezado de columna

Aquí está mi código:

public class CustomColumnHeadersTable { 

    private static String[] columnNames = { 
     "Column 1", "Column 2", "Column 3" 
    }; 
    private static String[][] data = { 
     {"A", "B", "C"}, 
     {"D", "E", "F"}, 
     {"G", "H", "I"} 
    }; 

    public CustomColumnHeadersTable() { 
     DefaultTableModel model = new DefaultTableModel((Object[][]) data, columnNames); 
     JTable table = new JTable(model); 
     JScrollPane scrollPane = new JScrollPane(table); 

     //set Header Renderer of each column to use the Custom renderer 
     Enumeration enumeration = table.getColumnModel().getColumns(); 
     while (enumeration.hasMoreElements()) { 
      TableColumn aColumn = (TableColumn) enumeration.nextElement(); 
      aColumn.setHeaderRenderer(new CustomColumnCellRenderer()); 
     } 

     JFrame frame = new JFrame(); 
     frame.getContentPane().add(scrollPane, BorderLayout.CENTER); 

     frame.setPreferredSize(new Dimension(300, 150)); 
     frame.pack(); 
     frame.setLocationRelativeTo(null); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     CustomColumnHeadersTable ccht = new CustomColumnHeadersTable(); 
    } 
} 

class CustomColumnCellRenderer implements TableCellRenderer { 

    private static String iconURL = "http://www.accessdubuque.com/images/close_icon.gif"; 
     //using a URL for the icon, so I don't have to upload the icon with the question 
    private static Dimension buttonSize = new Dimension(16, 16); 
    private static Dimension buttonBoxSize = new Dimension(16, 16); 
    private static Border panelBorder = BorderFactory.createRaisedBevelBorder(); 

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 
     JPanel panel = new JPanel(); 
     JLabel label = new JLabel(); 
     JButton button = new JButton(); 
     Box buttonBox = Box.createHorizontalBox(); 
     BorderLayout layout = new BorderLayout(); 

     label.setText(table.getColumnName(column)); 

     try { button.setIcon(new ImageIcon(new URL(iconURL))); } 
     catch (MalformedURLException ex) { 
      Logger.getLogger(CustomColumnCellRenderer.class.getName()).log(Level.SEVERE, null, ex); 
     } 

     //set size of the button and it's box 
     button.setMaximumSize(buttonSize); 
     button.setSize(buttonSize); 
     button.setPreferredSize(buttonSize); 
     buttonBox.setMaximumSize(buttonBoxSize); 
     buttonBox.setSize(buttonBoxSize); 
     buttonBox.setPreferredSize(buttonBoxSize); 

     button.addMouseListener(new CustomMouseListener()); //doesn't work... 

     buttonBox.add(button); 
     panel.add(label, BorderLayout.CENTER); 
     panel.add(buttonBox, BorderLayout.EAST); 

     panel.setBorder(panelBorder); 

     return panel; 
    } 
} 

class CustomMouseListener implements MouseListener 
{ 
    public void mouseClicked(MouseEvent e) { System.out.println("Mouse Clicked."); } 
    public void mousePressed(MouseEvent e) { System.out.println("Mouse Pressed."); } 
    public void mouseReleased(MouseEvent e) { System.out.println("Mouse Released."); } 
    public void mouseEntered(MouseEvent e) { System.out.println("Mouse Entered."); } 
    public void mouseExited(MouseEvent e) { System.out.println("Mouse Exited."); } 
} 

Por defecto, si he entendido bien, JTable utiliza un JLabel para rendir encabezados de columna. Mi idea es usar una implementación personalizada de TableCellRenderer y construir mi propio encabezado de columna a partir de varios componentes, a saber, un JPanel que contiene un JLabel y un JButton. Construyo y devuelvo eso en la función getTableCellRendererComponent (...).

Visualmente, esto funciona. El problema es que no puedo detectar clics del mouse en el botón (o, para el caso, en el panel que lo contiene). Simplemente agregar un MouseListener al botón no funciona. El evento nunca lo alcanza.

Encontré varias cosas similares en la web, pero no alcanzan la funcionalidad que necesito.

En primer lugar, hay un ejemplo de cómo poner un JCheckBox en la cabecera, aquí:

http://java-swing-tips.blogspot.com/2009/02/jtableheader-checkbox.html 

El problema con esto es que toda la cabecera es la casilla de verificación. Al hacer clic en la casilla de verificación o en la etiqueta asociada produce el mismo efecto. Por lo tanto, no es posible ordenar la columna. Me gustaría hacerlo de modo que al hacer clic en la etiqueta se ordene la columna, y al hacer clic en el botón de cerrar se elimine la columna de la tabla. En otras palabras, el encabezado debe tener dos áreas separadas con manejadores de eventos de mouse separados.

encontré otro ejemplo aquí:

http://www.devx.com/getHelpOn/10MinuteSolution/20425/1954?pf=true 

Este consiste en colocar JButtons en las células de la tabla, a continuación, la detección de clics del ratón en la mesa de sí mismo, el cálculo de la columna y fila en la que se produjo el clic, y el envío de la evento al botón apropiado.

Hay varios problemas con esto, también. Primero, los botones están en las celdas, no en los encabezados. Y segundo, esto es nuevamente solo un componente, no varios componentes dentro de un JPanel. Aunque tuve la idea de despachar eventos de este ejemplo, no puedo hacer que funcione para un componente compuesto.

Probé con otro enfoque. Pensé que si podía obtener las coordenadas de los botones de cierre, entonces al conocer las coordenadas del clic del mouse, puedo calcular en qué botón se hizo clic y enviar el evento de manera apropiada. Ejecuté varias pruebas y descubrí que los componentes dentro del encabezado de la tabla no se encuentran realmente en la pantalla.

Agregué una variable JButton estática a mi clase principal (pública) e hice que la clase que implementa TableCellRenderer fuera una clase interna de la clase principal. En getTableCellRendererComponent (...), antes de devolver, asigno el JButton que acabo de crear a esa variable estática. De esta forma, puedo manejarlo, por así decirlo. Luego, en main, intenté obtenerX(), getY(), getWidth(), getHeight() y getLocationOnScreen() usando esa variable estática. X, Y, Ancho y Alto devuelven 0. GetLocationOnScreen() bloquea el programa, diciendo que el componente debe estar presente en la pantalla para que funcione esta función.

El código de este se ve algo como esto:

public class CustomColumnHeadersTable { 

     private static JButton static_button; 
     ///the rest as before.... 

"CustomColumnCellRenderer clase implementa TableCellRenderer" se convierte en una clase interna de CustomColumnHeadersTable. Para eso, tengo que renunciar a las variables estáticas en CustomColumnCellRenderer, así que no me molesté con los iconos o las URL's o algo así. En lugar de un botón con un icono, acabo de utilizar un simple botón que decía "botón" ...

A continuación, en el interior getTableCellRendererComponent (...), justo antes de la instrucción de retorno, lo hice

static_button = button; 

Y, por último, dentro de main(), he intentado hacer esto:

System.out.println("X: " + static_button.getX()); 
    System.out.println("Y: " + static_button.getY()); 
    System.out.println("W: " + static_button.getWidth()); 
    System.out.println("H: " + static_button.getHeight()); 
    System.out.println("LOC: " + static_button.getLocation()); 
    System.out.println("LOC on screen: " + static_button.getLocationOnScreen()); 

La salida tiene el siguiente aspecto:

X: 0 
Y: 0 
W: 0 
H: 0 
LOC: java.awt.Point[x=0,y=0] 
Exception in thread "main" java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location 
at java.awt.Component.getLocationOnScreen_NoTreeLock(Component.java:1943) 
at java.awt.Component.getLocationOnScreen(Component.java:1917) 
... 

En otras palabras, el botón tiene todas las dimensiones de 0, y de acuerdo con Java, en realidad no se encuentra en la pantalla (aunque puedo verlo ...). Llamar a getLocationOnScreen() bloquea el programa.

Así que ayuda por favor si puedes. Quizás alguien sepa cómo hacer esto. Tal vez podrías sugerir algún otro enfoque para probar. O, tal vez usted sepa que no es posible en absoluto ...

Gracias por su ayuda.

Respuesta

2

Sí, me sorprendió la primera vez que traté de hacer algo similar, como poner JButton s en un JList - no funciona por las razones que usted indicó. La forma correcta de hacer lo que quería es utilizar una lista como LayoutManager para colocar el JButton s en una lista.

En su caso, trataría de usar una combinación de JLabel sy MouseListeners, que creo que funcionará correctamente. Usando un JLabel como encabezado le permitirá proporcionar una imagen y texto, y puede registrar un MouseListener en el JLabel. De acuerdo, no obtendrá los mismos efectos visuales que cuando hace clic en JButton (es decir, el botón presiona), pero permitirá la misma funcionalidad.

Otra cosa a tener en cuenta es usar estática JButton s (o cualquier JComponent). El problema al hacer esto es que las coordenadas del JButton se almacenan en el JButton, lo que significa que no se puede usar el mismo JButton en diferentes lugares. En otras palabras, si intenta agregar el mismo static JButton a una lista varias veces, no verá múltiples JButton s, verá el JButton en la ubicación que agregó por última vez. La forma correcta de lograr el efecto deseado es mantener una referencia estática al contenido del JButton, es decir, la imagen y el texto, en lugar del JButton. Luego, solo instale un nuevo JButton usando la imagen y el texto y colóquelo donde desee. En general, es mejor evitar las referencias estáticas a los componentes de Sentido, ya que no es posible tener varias instancias del mismo componente.

Swing no es tan oscilante a veces. Con suerte, he proporcionado información útil: ¡sigue luchando la buena batalla!

+0

Gracias! Intenté algo similar: decidí simplemente calcular si los clics caían sobre el botón. Para eso, necesito mantener el botón en un solo lugar. Digamos que son 16x16 y necesito mantenerlo siempre a 8 píxeles a la izquierda del borde de la columna. Aunque pude hacer eso usando Cajas y pegamento, no pude hacer que funcionara bien con JLabel. Diga, mi columna se ve así: | Nombre de la columna [x] | Si la columna se vuelve a clasificar según el tamaño, me gustaría que se vea como | Colum ... [x] | IOW, las elipses deberían aparecer automáticamente. En cambio, el botón solo pinta sobre la etiqueta. ¿Algunas ideas? – Aleksey

1

Puede usar JXTable (desde la paleta de swingx) que tiene la opción de 'desactivar' una columna en particular de su tabla.

Andrei Ionut Apopei

0

me dio la solución:

public class CustomColumnHeadersTable { 

    public static Vector<JButton> buttons = new Vector<JButton>(); 

    private static String[] columnNames = { 
      "Column 1", "Column 2", "Column 3" 
    }; 
    private static String[][] data = { 
      {"A", "B", "C"}, 
      {"D", "E", "F"}, 
      {"G", "H", "I"} 
    }; 

    class ColumnHeaderListener extends MouseAdapter { 
     public void mouseClicked(MouseEvent evt) { 
      JTable table = ((JTableHeader) evt.getSource()).getTable(); 
      TableColumnModel colModel = table.getColumnModel(); 

      int index = colModel.getColumnIndexAtX(evt.getX()); 
      if (index == -1) { 
       return; 
      } 
      Rectangle headerRect = table.getTableHeader().getHeaderRect(index); 

      if(1== index){ 
       if(headerRect.contains(evt.getX() , evt.getY())){ 
        int xx = evt.getX() - headerRect.x ; 

        for (int i = 0; i < buttons.size(); i++) { 
         JButton button = buttons.get(i); 
         Rectangle re = button.getBounds(); 
         if(re.contains(xx, evt.getY())){ 
          System.out.println("Bingle"); 
         } 

        } 
       } 
      } 
     } 
    } 

    public CustomColumnHeadersTable() { 
     DefaultTableModel model = new DefaultTableModel((Object[][]) data, columnNames); 
     JTable table = new JTable(model); 
     JScrollPane scrollPane = new JScrollPane(table); 

     JTableHeader header = table.getTableHeader(); 
     header.addMouseListener(new ColumnHeaderListener()); 

     //set Header Renderer of each column to use the Custom renderer 
     Enumeration enumeration = table.getColumnModel().getColumns(); 
     while (enumeration.hasMoreElements()) { 
      TableColumn aColumn = (TableColumn) enumeration.nextElement(); 
      aColumn.setHeaderRenderer(new CustomColumnCellRenderer(buttons)); 
     } 

     JFrame frame = new JFrame(); 
     frame.getContentPane().add(scrollPane, BorderLayout.CENTER); 

     frame.setPreferredSize(new Dimension(300, 150)); 
     frame.pack(); 
     frame.setLocationRelativeTo(null); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     CustomColumnHeadersTable ccht = new CustomColumnHeadersTable(); 
    } 
} 

class CustomColumnCellRenderer implements TableCellRenderer { 

    private static String iconURL = "http://www.accessdubuque.com/images/close_icon.gif"; 
    //using a URL for the icon, so I don't have to upload the icon with the question 
    private static Dimension buttonSize = new Dimension(16, 16); 
    private static Dimension buttonBoxSize = new Dimension(16, 16); 
    private static Border panelBorder = BorderFactory.createRaisedBevelBorder(); 

    Vector<JButton> buttons = null; 

    JPanel panel = new JPanel(); 
    JLabel label = new JLabel(); 
    JButton button = new JButton(); 

    CustomColumnCellRenderer(Vector<JButton> buttons) { 
     this.buttons = buttons; 

     try { button.setIcon(new ImageIcon(new URL(iconURL))); } 
     catch (MalformedURLException ex) { 
      Logger.getLogger(CustomColumnCellRenderer.class.getName()).log(Level.SEVERE, null, ex); 
     } 

     //set size of the button and it's box 
     button.setMaximumSize(buttonSize); 
     button.setSize(buttonSize); 
     button.setPreferredSize(buttonSize); 

     BorderLayout layout = new BorderLayout(); 
     panel.setLayout(layout); 
     panel.add(label, BorderLayout.CENTER); 
     panel.add(button, BorderLayout.EAST); 
     panel.setBorder(panelBorder); 

     buttons.add(button); 
    } 

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 

     label.setText(table.getColumnName(column)); 
     return panel; 
    } 
} 
Cuestiones relacionadas