Search code examples
javaswingjtablejbuttonflatlaf

JButtons in JTable cell change shape on row selection


I have a single cell (column) per row based table and the cell is a panel with 2 buttons on it. TableCellRenderer and TableCellEditor are implemented correctly to allow render and edit the cell. But when i click the row or even the button, the shape of the buttons change. It looks like the size of the button shrinks to fit the button text. Please see the attached images.

On load View of table

On Selection View of table

If I set a preferred size of the buttons then the size doesn't seem to change but the button text shift towards right. Below is the code of the table cell panel. Running this panel from a main in the same class like shown below dosn't show this behavior. This behavior is only visible when using this panel as a cell in the single column table row. Don't know which property of what to set/change.

public class PartitionsTableRowPanel extends javax.swing.JPanel {

    /**
     * Creates new form PartitionsTableRowPanel
     */
    public PartitionsTableRowPanel() {
        initComponents();
        postInitAdjustments();
    }

    private void postInitAdjustments() {
//        infoButton.putClientProperty("JButton.buttonType", "roundRect");
//        scanButton.putClientProperty("JButton.buttonType", "roundRect");
//        
//        infoButton.addActionListener((e) -> {
//            log.info("info clicked");
//        });
        
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jButton1 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();

        setPreferredSize(new java.awt.Dimension(1000, 59));
        setLayout(new org.netbeans.lib.awtextra.AbsoluteLayout());

        jButton1.setText("Info");
        add(jButton1, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 20, -1, -1));

        jButton2.setText("jButton2");
        add(jButton2, new org.netbeans.lib.awtextra.AbsoluteConstraints(90, 20, -1, -1));
    }// </editor-fold>                        
    
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            FlatDarkLaf.install();
            JFrame f = new JFrame("TestProgressBar");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setContentPane(new PartitionsTableRowPanel());
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        });

    }

    // Variables declaration - do not modify                     
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    // End of variables declaration                   
}

Below is what my main JFrame looks like:

public class MainAppWindow extends javax.swing.JFrame {

    /**
     * Creates new form MainWindow1
     */
    public MainAppWindow() {
        initComponents();
        postInitAdjustments();
    }

    private void postInitAdjustments() {
        
    }
    
    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        northPanel = new javax.swing.JPanel();
        jScrollPane1 = new javax.swing.JScrollPane();
        partitionDetailsTable = new javax.swing.JTable();
        centerPanel = new javax.swing.JPanel();
        messageDetailsTextPaneScrollPane = new javax.swing.JScrollPane();
        messageDetailsTextPane = new javax.swing.JTextPane();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Sentry Disk Helper");
        setName("mainFrame"); // NOI18N
        setPreferredSize(new java.awt.Dimension(984, 606));

        northPanel.setName("northPanel"); // NOI18N

        partitionDetailsTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        partitionDetailsTable.setFocusable(false);
        partitionDetailsTable.setName("partitionsTable"); // NOI18N
        jScrollPane1.setViewportView(partitionDetailsTable);

        javax.swing.GroupLayout northPanelLayout = new javax.swing.GroupLayout(northPanel);
        northPanel.setLayout(northPanelLayout);
        northPanelLayout.setHorizontalGroup(
            northPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(northPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 1048, Short.MAX_VALUE)
                .addContainerGap())
        );
        northPanelLayout.setVerticalGroup(
            northPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(northPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 238, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        getContentPane().add(northPanel, java.awt.BorderLayout.NORTH);

        centerPanel.setBounds(new java.awt.Rectangle(2, 4, 0, 0));
        centerPanel.setName("centerPanel"); // NOI18N

        messageDetailsTextPane.setFocusable(false);
        messageDetailsTextPaneScrollPane.setViewportView(messageDetailsTextPane);

        javax.swing.GroupLayout centerPanelLayout = new javax.swing.GroupLayout(centerPanel);
        centerPanel.setLayout(centerPanelLayout);
        centerPanelLayout.setHorizontalGroup(
            centerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(centerPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(messageDetailsTextPaneScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1048, Short.MAX_VALUE)
                .addContainerGap())
        );
        centerPanelLayout.setVerticalGroup(
            centerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, centerPanelLayout.createSequentialGroup()
                .addComponent(messageDetailsTextPaneScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE)
                .addContainerGap())
        );

        getContentPane().add(centerPanel, java.awt.BorderLayout.CENTER);

        getAccessibleContext().setAccessibleName("Sentery Disk Helper");

        setSize(new java.awt.Dimension(984, 628));
        setLocationRelativeTo(null);
    }// </editor-fold>                        

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(MainAppWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(MainAppWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(MainAppWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(MainAppWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                 //FlatIntelliJLaf.install();
                new MainAppWindow().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JPanel centerPanel;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextPane messageDetailsTextPane;
    private javax.swing.JScrollPane messageDetailsTextPaneScrollPane;
    private javax.swing.JPanel northPanel;
    private javax.swing.JTable partitionDetailsTable;
    // End of variables declaration                   
}

Adding a sample code to help reproduce the problem. Run the main in the NewJFrame1 class to see the issue. Uncomment and Run the main in the TableRowPanel1 private class to see the default correct working. If i use the table cell panel component directly in a JFrame it works fine. But same component used as table cell via an editor shows the problem. Really need help with this please.:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.swing.test.demo.ui;

import com.formdev.flatlaf.FlatDarkLaf;
import com.swing.test.demo.ui.components.TableRowPanel;
import java.awt.Component;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

public class NewJFrame1 extends javax.swing.JFrame {

    /**
     * Creates new form NewJFrame1
     */
    public NewJFrame1() {
        initComponents();
        postInit();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jPanel1 = new javax.swing.JPanel();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        jScrollPane1.setViewportView(jTable1);

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 481, Short.MAX_VALUE)
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 337, Short.MAX_VALUE)
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
//        try {
//            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
//                if ("Nimbus".equals(info.getName())) {
//                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
//                    break;
//                }
//            }
//        } catch (ClassNotFoundException ex) {
//            java.util.logging.Logger.getLogger(NewJFrame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
//        } catch (InstantiationException ex) {
//            java.util.logging.Logger.getLogger(NewJFrame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
//        } catch (IllegalAccessException ex) {
//            java.util.logging.Logger.getLogger(NewJFrame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
//        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
//            java.util.logging.Logger.getLogger(NewJFrame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
//        }
        //</editor-fold>
        FlatDarkLaf.setup();
        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                NewJFrame1 newJFrame1 = new NewJFrame1();
                newJFrame1.setVisible(true);

            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JPanel jPanel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable1;
    // End of variables declaration                   

    private void postInit() {
        List<Data> modelData = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            Data data = new Data();
            data.value = "Data_" + i;
            modelData.add(data);
        }
        jTable1.setDefaultRenderer(Data.class, new MyCellRenderer());
        jTable1.setDefaultEditor(Data.class, new MyCellEditor());
        jTable1.setModel(new MyTableModel(modelData));
        jTable1.setRowHeight(52);
        jTable1.revalidate();
        //
    }

    private class Data {

        String value;
    }

    private class MyTableModel extends AbstractTableModel {

        private List<Data> data;

        public MyTableModel(List<Data> data) {
            this.data = data;
        }

        @Override
        public int getRowCount() {
            return data.size();
        }

        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return data.get(rowIndex);
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return Data.class;
        }

    }

    private class MyCellRenderer implements TableCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            System.out.println("fff");
            return new TableRowPanel1();
        }

    }

    private class MyCellEditor extends AbstractCellEditor implements TableCellEditor {

        @Override
        public Object getCellEditorValue() {
            return null;
        }

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
            System.out.println("ddd");
            return new TableRowPanel1();
        }

    }

    private class TableRowPanel1 extends javax.swing.JPanel {

        /**
         * Creates new form TableRowPanel
         */
        public TableRowPanel1() {
            initComponents();
        }

        /**
         * This method is called from within the constructor to initialize the
         * form. WARNING: Do NOT modify this code. The content of this method is
         * always regenerated by the Form Editor.
         */
        @SuppressWarnings("unchecked")
        // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
        private void initComponents() {

            jButton1 = new javax.swing.JButton();
            jButton2 = new javax.swing.JButton();

            jButton1.setText("jButton1");

            jButton2.setText("jButton2");

            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
            this.setLayout(layout);
            layout.setHorizontalGroup(
                    layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                    .addContainerGap()
                                    .addComponent(jButton1)
                                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                    .addComponent(jButton2)
                                    .addContainerGap(250, Short.MAX_VALUE))
            );
            layout.setVerticalGroup(
                    layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                    .addGap(14, 14, 14)
                                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                            .addComponent(jButton1)
                                            .addComponent(jButton2))
                                    .addContainerGap(14, Short.MAX_VALUE))
            );
        }// </editor-fold>                        

        // Variables declaration - do not modify                     
        private javax.swing.JButton jButton1;
        private javax.swing.JButton jButton2;
        // End of variables declaration                   

//        public static void main(String[] args) {
//            EventQueue.invokeLater(() -> {
//                FlatDarkLaf.setup();
//                JFrame f = new JFrame("TestProgressBar");
//                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//                f.setContentPane(new TableRowPanel());
//                f.pack();
//                f.setLocationRelativeTo(null);
//                f.setVisible(true);
//            });
//
//        }
    }

}


Solution

  • According to the source code, the border inset of the table cell editor is 0 according to the FlatBorder specification.

    com/formdev/flatlaf/ui/FlatBorder.java

    protected boolean isCellEditor( Component c ) {
        return FlatUIUtils.isCellEditor( c );
    }
    
    @Override
    public Insets getBorderInsets( Component c, Insets insets ) {
        // ...
    
        if( isCellEditor( c ) ) {
            // remove top and bottom insets if used as cell editor
            insets.top = insets.bottom = 0;
            // ...
    

    com/formdev/flatlaf/ui/FlatUIUtils.java

    public static boolean isCellEditor( Component c ) {
        // check whether used in cell editor (check 3 levels up)
    

    Any ideas about how to rectify this?

    Make the cell editor panel hierarchy three or more levels.

    class TableRowPanel1 extends JPanel {
      protected final JButton jButton1 = new JButton("jButton1");
      protected final JButton jButton2 = new JButton("jButton2");
    
      public TableRowPanel1() {
        super(new BorderLayout());
        JPanel p2 = new JPanel(new FlowLayout(FlowLayout.LEADING));
        p2.add(jButton1);
        p2.add(jButton2);
        JPanel p1 = new JPanel(new BorderLayout());
        p1.add(p2);
        add(p1);
      }
    }
    

    or override FlatBorder#isCellEditor(Component c) method to always return false.

    class TableRowPanel1 extends JPanel {
      protected final JButton jButton1 = new JButton("jButton1");
      protected final JButton jButton2 = new JButton("jButton2");
    
      public TableRowPanel1() {
        super(new FlowLayout(FlowLayout.LEADING));
        FlatBorder border = new FlatButtonBorder() {
          @Override protected boolean isCellEditor(Component c) {
            return false;
          }
        };
        jButton1.setBorder(border);
        jButton2.setBorder(border);
        add(jButton1);
        add(jButton2);
      }
    }
    

    NewJFrame2.java

    import com.formdev.flatlaf.*;
    import com.formdev.flatlaf.ui.*;
    import java.awt.*;
    import java.util.ArrayList;
    import java.util.List;
    import javax.swing.*;
    import javax.swing.table.*;
    
    public class NewJFrame2 {
      public Component makeUI() {
        List<Data> modelData = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
          Data data = new Data();
          data.value = "Data_" + i;
          modelData.add(data);
        }
        JTable table = new JTable(new MyTableModel(modelData));
        table.setDefaultRenderer(Data.class, new MyCellRenderer());
        table.setDefaultEditor(Data.class, new MyCellEditor());
        table.setRowHeight(52);
    
        JPanel p = new JPanel(new BorderLayout());
        p.add(new JScrollPane(table));
        p.add(new TableRowPanel1(), BorderLayout.SOUTH);
        return p;
      }
    
      public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
          FlatDarkLaf.setup();
          JFrame f = new JFrame();
          f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
          f.getContentPane().add(new NewJFrame2().makeUI());
          f.pack();
          f.setLocationRelativeTo(null);
          f.setVisible(true);
        });
      }
    }
    
    class Data {
      String value;
    }
    
    class MyTableModel extends AbstractTableModel {
      private List<Data> data;
    
      public MyTableModel(List<Data> data) {
        this.data = data;
      }
    
      @Override
      public int getRowCount() {
        return data.size();
      }
    
      @Override
      public int getColumnCount() {
        return 1;
      }
    
      @Override
      public Object getValueAt(int rowIndex, int columnIndex) {
        return data.get(rowIndex);
      }
    
      @Override
      public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
      }
    
      @Override
      public Class<?> getColumnClass(int columnIndex) {
        return Data.class;
      }
    }
    
    class MyCellRenderer implements TableCellRenderer {
      protected final TableRowPanel1 renderer = new TableRowPanel1();
    
      @Override
      public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        return renderer;
      }
    }
    
    class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
      protected final TableRowPanel1 editor = new TableRowPanel1();
    
      @Override
      public Object getCellEditorValue() {
        return null;
      }
    
      @Override
      public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        return editor;
      }
    }
    
    class TableRowPanel1 extends JPanel {
      protected final JButton jButton1 = new JButton("jButton1");
      protected final JButton jButton2 = new JButton("jButton2");
    
      public TableRowPanel1() {
        super(new BorderLayout());
        JPanel p2 = new JPanel(new FlowLayout(FlowLayout.LEADING));
        p2.add(jButton1);
        p2.add(jButton2);
        JPanel p1 = new JPanel(new BorderLayout());
        p1.add(p2);
        add(p1);
      }
    }
    
    // class TableRowPanel1 extends JPanel {
    //   protected final JButton jButton1 = new JButton("jButton1");
    //   protected final JButton jButton2 = new JButton("jButton2");
    // 
    //   public TableRowPanel1() {
    //     super(new FlowLayout(FlowLayout.LEADING));
    //     FlatBorder border = new FlatButtonBorder() {
    //       @Override protected boolean isCellEditor(Component c) {
    //         return false;
    //       }
    //     };
    //     jButton1.setBorder(border);
    //     jButton2.setBorder(border);
    //     add(jButton1);
    //     add(jButton2);
    //   }
    // }