Remove JTable cell truncation

51 Views Asked by At

By default, JTable truncates strings which are too long for the cells with ellipses like this.

enter image description here

I would like to just clip, as in macOS's Console app.

enter image description here

I've seen a previous answer which allows for replacing the ellipses with something else via overriding LabelUI but I'm not sure (1) how to implement this with the DefaultTableCellRenderer and (2) I intend the app to be cross platform so I don't think I can override a specific class like MetalUI and expect it to work.

1

There are 1 best solutions below

0
gthanop On

One idea could be to put the default installed renderer in a JScrollPane. The reasoning behind this is that you do not need to customize the renderer which comes with the JTable (so this sounds promising for portability). The JScrollPane additionally ensures that the default renderer (which is a JLabel) will never get a size smaller than its preferred (ie no text clipping). You just need to hide the scrollbars of JScrollPane.

Example code:

import java.awt.Component;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;

public class TableCellTrunc {
    
    public static class ScrollDecoratorRenderer implements TableCellRenderer {
        
        private final TableCellRenderer decorated;
        private final JScrollPane pane;

        public ScrollDecoratorRenderer(final TableCellRenderer decorated) {
            this.decorated = Objects.requireNonNull(decorated);
            pane = new JScrollPane();
            pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
            pane.setBorder(BorderFactory.createEmptyBorder());
        }
        
        @Override
        public Component getTableCellRendererComponent(final JTable table,
                                                       final Object value,
                                                       final boolean isSelected,
                                                       final boolean hasFocus,
                                                       final int row,
                                                       final int column) {
            pane.setViewportView(decorated.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column));
            return pane;
        }
    }
    
    private static Font enlargeFont(final Font font) {
        return font.deriveFont(font.getSize2D() * 1.2f);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(() -> {
            
            /*try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            }
            catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException exception) {
                System.err.println("Failed to set system L&F.");
            }*/
            
            // Create data to fill the table:
            final int columnsCount = 3, rowsCount = 5;
            final String textExtension = " - huge but necessary text";
            final DefaultTableModel model = new DefaultTableModel();
            for (int columnIndex = 0; columnIndex < columnsCount; ++columnIndex)
                model.addColumn("Column " + columnIndex + textExtension);
            for (int rowIndex = 0; rowIndex < rowsCount; ++rowIndex) {
                final List<String> row = new ArrayList<>(columnsCount);
                for (int columnIndex = 0; columnIndex < columnsCount; ++columnIndex)
                    row.add("Column " + columnIndex + " - Row " + rowIndex + textExtension);
                model.addRow(row.toArray());
            }
            
            final JTable table = new JTable(model);
            final JTableHeader header = table.getTableHeader();

            // Apply custom renderer to header and/or table:
            header.setDefaultRenderer(new ScrollDecoratorRenderer(header.getDefaultRenderer()));
            table.setDefaultRenderer(Object.class, new ScrollDecoratorRenderer(table.getDefaultRenderer(Object.class)));

            // Enlarge font size for demonstration (optional):
            header.setFont(enlargeFont(header.getFont()));
            table.setFont(enlargeFont(table.getFont()));
            table.setRowHeight(header.getPreferredSize().height); // Simple way to accomodate larger font (only applicable in this case, and not intended as a general purpose solution).
            
            // Truncate bottom empty space of table (optional):
            table.setPreferredScrollableViewportSize(table.getPreferredSize());

            // Show result:
            final JFrame frame = new JFrame("Table cell trunc");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(new JScrollPane(table));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

The result looks like this: Metal L&F view of the proposed method.

You can apply the exact same principle in the header too (as shown in the example code). This is also tested for Windows 10 system L&F, showing similar behaviour.

But it comes with some minor limitations which so far I failed to mitigate:

  1. There is a black vertical border separating column titles. If you shrink the table enough such that column titles need to be truncated, then these borders become white for some reason (which I don't know), as shown below (in red circles): Bug of the proposed method in Metal L&F.
  2. If you remove the following lines from the example code:
    header.setFont(enlargeFont(header.getFont()));
    table.setFont(enlargeFont(table.getFont()));
    
    then you still need the following one:
    table.setRowHeight(header.getPreferredSize().height);
    
    because otherwise a slightly bigger margin can be observed on the top of each cell, with respect to the bottom of the cell (ie each cell's text seems to be slightly more aligned to the bottom of the cell) for the default table sizes and font.

This method then seems to need some adjustments, but they in turn seem to be related with small issues in the borders.