Reputation: 32720
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.
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 TableListModel
class :
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;
}
}
Upvotes: 1
Views: 890
Reputation: 33
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
Upvotes: 1
Reputation: 324207
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();
Upvotes: 2