Question

I have a grid (JTable) that looks like MS Excel's grid. I want to allow the user to resize rows and columns. For the columns I simply used this :

grid.getTableHeader().setResizingAllowed(true);

And for the rows I took the TableRowResizer class from here and which I'm using like this :

new TableRowResizer(grid);

This works fine. However, I've one problem : when resizing a row the row header is not resized too.

enter image description here

Here's how I made the row headers :

    AbstractListModel lm = null;
    lm = new TableListModel(grid);
    final JList list = new JList(lm);
    list.setFixedCellWidth(60);         
    list.setFixedCellHeight(grid.getRowHeight());
    list.setCellRenderer(new TableRowHeaderRenderer(grid));
    list.setBackground(grid.getTableHeader().getBackground());
    scrollPane.setRowHeaderView(list);

Here's the TableRowHeaderRenderer class :

    class TableRowHeaderRenderer extends JLabel implements ListCellRenderer {   

        private JTable table;

        public TableRowHeaderRenderer(JTable table)
        {
            this.table = table;
            JTableHeader header = table.getTableHeader();
            setOpaque(true);
            setBorder(BorderFactory.createEtchedBorder());
            setHorizontalAlignment(CENTER);
            setForeground(header.getForeground());
            setBackground(header.getBackground());
            setFont(header.getFont());
        }

        public Component getListCellRendererComponent(JList list, 
        Object value, int index, boolean isSelected, boolean cellHasFocus) 
        {
            Color bg = UIManager.getColor("TableHeader.background");
            int selectedrow =  table.getSelectedRow();
            if (selectedrow==index) bg = new Color(107, 142, 35);
            setBackground(bg);
            setText("" + Grid.getRowName(index));
            return this;
        }
        }

And this is the TableListModelclass :

    class TableListModel extends AbstractListModel{
        private JTable mytable;

        public TableListModel(JTable table) {
            super();
            mytable = table;
            }

        public int getSize() {
            return mytable.getRowCount();
            }

        public Object getElementAt(int index) {
            return "" + index;
            }
        }
Was it helpful?

Solution

Check out the Row Number Table. It uses a JTable (instead of a JList) to render the row numbers. Therefore you can keep the row heights in sync with the main table.

However, I can't get the row header to repaint automatically when the row height of the main table is changed since no event is fired when an individual row height is changed. So you will also need to modify the resizing code to look something like:

table.setRowHeight(resizingRow, newHeight);
JScrollPane scrollPane = (JScrollPane)SwingUtilities.getAncestorOfClass(JScrollPane.class, table);
scrollPane.getRowHeader().repaint();

OTHER TIPS

I know this is an old question and have already accepted answer but I find the accepted answer have a performance issue when you implement a resizable row header in which a user can resize the row by just dragging the line in between rows of the header, just like in excel. When dragged outside the header's bound, it begins lagging too much and I can't find any alternative good performance implementation of this so I made my own header and for those who want to implement a resizable row header, you might wanna check this out.

Create an interface that will be use to listen for rows selection in the table.

public interface TableSelectionListener {
    
    public void onRowSelected(int selectedRow);
    public void onMultipleRowsSelected(Map<Integer, Integer> rows);
}

make a RowTableHeader class and extend the JComponent, implement the interface you made and some other listeners for the table.

/**
 * Initialize this class after setting the table's model
 * @author Rene Tajos Jr.
 */
public class TableRowHeader extends JComponent implements ChangeListener, TableModelListener, TableSelectionListener {

    private JTable mTable = new JTable();
    private final int mRows;
    private int mViewportPos = 0;
    private final Color mGridColor;
    private Color mFontColor = GradeUtils.Colors.darkBlueColor;
    private final Color mSelectedRowColor = GradeUtils.Colors.semiCreamWhiteBlueColor;
    private final Color mBgColor = new Color(247,245,251);
    private int mSelectedRow = -1;
    private Map<Integer, Integer> mRowsSelected = new HashMap<>();
    
    private Font mFont = GradeUtils.getDefaultFont(12);
    
    private Map<Integer> resizePoints = new HashMap<>();
    
    private final MouseInputAdapter inputAdapter = new MouseInputAdapter() {
        private int mMouseY = -1;
        private final int MIN_ROW_HEIGHT = 23;
        private int mRowToResize;
        private boolean isOnResizingPoint = false;

        @Override
        public void mouseMoved(MouseEvent e) {
            Point p = e.getPoint();
            
            if (resizePoints.containsKey((int)p.getY())) {
                isOnResizingPoint = true;
                GradeUtils.setCustomCursor(TableRowHeader.this, GradeUtils.resizeVerticalCursorStr);
                return;
            }
            
            isOnResizingPoint = false;
            setCursor(Cursor.getDefaultCursor());
        }
        
        @Override
        public void mouseDragged(MouseEvent e) {
            if (!isOnResizingPoint)
               return;

            int y = e.getY();
            if (mMouseY != -1) {
                int elapse = y - mMouseY;
                int oldRowHeight = mTable.getRowHeight(mRowToResize);
                int rowHeight = Math.max(MIN_ROW_HEIGHT, oldRowHeight + elapse);
                mTable.setRowHeight(mRowToResize, rowHeight);
            }
            mMouseY = y;
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            mMouseY = -1;
            isOnResizingPoint = false;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (isOnResizingPoint) {
                mMouseY = e.getY();
                // region: get the row point to resize
                final Point p = e.getPoint();
                p.y += mViewportPos;
                mRowToResize = mTable.rowAtPoint(_getRowPoint(p, 5));
                
                // region end
            }
        }
        /**
         * Locate the row point to resize
         * @param p The event Point
         * @param i The number difference of where to locate the row
         */
        private Point _getRowPoint(Point p, int i) {
            if (!resizePoints.containsKey(p.y -= i)) {
                p.y -= i;
                return p;
            }
            return _getRowPoint(p, i-1);
        }
    };
    
    public TableRowHeader(JTable table) {
        mTable = table;
        mRows = table.getRowCount();
        mGridColor = mTable.getGridColor();
        ((TajosTable)mTable).addTableSelectionListener(this);
        
        JViewport v = (JViewport) mTable.getParent();
        v.addChangeListener(this);
        
        mTable.getModel().addTableModelListener( this );
        
        setPreferredSize(new Dimension(30, HEIGHT));
        setOpaque(true);
        setBackground(Color.WHITE);
        
        addMouseListener(inputAdapter);
        addMouseMotionListener(inputAdapter);
    }
    
    /**
     * Update the row resize points
     */
    private void _updateResizePoints(int y) {
        if (!resizePoints.isEmpty())
            resizePoints.clear();
            
        int nexPoint = y;
        for (int i=0; i<mTable.getRowCount(); i++) {
            int resizePointMinThreshold = nexPoint + mTable.getRowHeight(i) - 2;
            int resizePointMaxThreshold = nexPoint + mTable.getRowHeight(i) + 2;
            
            for (int j=resizePointMinThreshold; j<=resizePointMaxThreshold; j++)
                resizePoints.put(j, j);
            
            nexPoint += mTable.getRowHeight(i);
        }
    }

    @Override
    public void setForeground(Color fg) {
        mFontColor = fg;
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        
        int y = -1 - mViewportPos;
        _updateResizePoints(y);
        // loop in every rows to draw row header
        for (int row=0; row<mRows; row++) {
            int rowHeight = mTable.getRowHeight(row);
            
            g2d.setColor(mGridColor);
            if (row != mRows-1 && row == 0) { // draw row at index 0
                // region: draw background
                if (row == mSelectedRow || mRowsSelected.containsKey(row))
                    g2d.setColor(mSelectedRowColor);
                else
                    g2d.setColor(mBgColor);
                
                g2d.fillRect(0, y+1, getPreferredSize().width-1, rowHeight-1);
                // region end
                // region: draw borders
                g2d.setColor(mGridColor);
                g2d.drawRect(0, y+1, getPreferredSize().width-1, rowHeight-1);
                // region end
            } else { // draw the rest of the rows
                // region: draw background
                if (row == mSelectedRow || mRowsSelected.contains(row))
                    g2d.setColor(mSelectedRowColor);
                else
                    g2d.setColor(mBgColor);
                
                g2d.fillRect(0, y, getPreferredSize().width-1, rowHeight);
                // region end
                // region: draw borders
                g2d.setColor(mGridColor);
                g2d.drawRect(0, y, getPreferredSize().width-1, rowHeight);
                // region end
            }
            // region: draw text with CENTER ALIGNMENT
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g2d.setColor(mFontColor);
            g2d.setFont(mFont);
            String str = String.valueOf(row+1);
            
            FontMetrics fm = getFontMetrics(mFont);
            Rectangle2D textRect = fm.getStringBounds(str, g2d);
            
            int textX = ((getPreferredSize().width-1) - (int)textRect.getWidth()) / 2;
            int diffY = (rowHeight - (int)textRect.getHeight()) / 2;
            int textY = y + (rowHeight - diffY);
            
            g2d.drawString(str, textX, textY - 1);
            // region end
            
            y += rowHeight;
        }
        
        g2d.dispose();
    }

    /**
     * Implement the ChangeListener
     */
    @Override
    public void stateChanged(ChangeEvent e)
    {
        //  Keep the view of this row header in sync with the table
        JViewport viewport = (JViewport) e.getSource();
        mViewportPos = viewport.getViewPosition().y;
    }

    /**
    *  Listens for changes in the table
    */
    @Override
    public void tableChanged(TableModelEvent e)
    {
        revalidate();
    }

    /**
     * Listens for single row selection
     * @param selectedRow The selected row
     */
    @Override
    public void onRowSelected(int selectedRow) {
        mSelectedRow = selectedRow;
        if (!mRowsSelected.isEmpty())
            mRowsSelected.clear();
        
        revalidate();
        repaint();
    }

    /**
     * Listens for multiple row selection
     * @param rows The selected rows
     */
    @Override
    public void onMultipleRowsSelected(Map<Integer, Integer> rows) {
        mSelectedRow = -1;
        mRowsSelected = rows;
        
        revalidate();
        repaint();
    }
}

After that, you need to create a class that extends Jtable where you can add a mouselisteners that will listen every time the user selects/multiple selects row/s

public class TajosTable extends JTable {
   ............
   private TableSelectionListener mTableSelectionListener;
   
   private final MouseInputAdapter mouseListener = new MouseInputAdapter() {
        private final int TRUE = 1, FALSE = 0;
        private int isDraggingMouse;
        @Override
        public void mousePressed(MouseEvent e) {
            final int row = rowAtPoint(e.getPoint());
            final int col = columnAtPoint(e.getPoint());
            
            mTableSelectionListener.onRowSelected(row);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            isDraggingMouse = TRUE;
            final int[] rowsSelected = getSelectedRows();
            final int[] colsSelected = getSelectedColumns();

            Map<Integer, Integer> map = new HashMap<>();
            for (int row : rowsSelected)
               map.put(row, row);

            mTableSelectionListener.onMultipleRowsSelected(map);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            isDraggingMouse = FALSE;
        }
    };

    public TajosTable() {
        super();
        setTableHeader(null);
        setColumnSelectionAllowed(true);
        // add the mouse listener
        addMouseListener(mouseListener);
        addMouseMotionListener(mouseListener);
    }
    ............
    // and of course create a method that will attach the selection listener
    public void addTableSelectionListener(TableSelectionListener lstener) {
        mTableSelectionListener = lstener;
    }
}

And in your main() {}, always attach this Row Header to the scrollpane AFTER setting the table's model

table.setModel(new YourTableModel(rows, cols));
tableScrollPane.setRowHeaderView(new TableRowHeader(table));

That's it. You can further modify it to suit your needs. Here is the result, and now when I exited my cursor while resizing a row outside the bounds of my custom made row header. I can't see any lags now.

Here's the result in GIF image

table

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top