EarthWeb
Developer.com
Site
windows 2000
visual c++
java
visual basic
javascripts
recommend it
 
Book
thinking in java
 
Interact
forum
guest book
jobs
jokes
what's new

share code
 
Resource
add resource
modify resource
new resource
 

[Internet Jobs]
-----
Java by E-mail:

Get the weekly e-mail highlights on Java!
-----

-

Changing height of individual row


Author: Zafir Anjum


By default the row height in a JTable is 16 pixels high. You can change it any amount but the new height applies to all the rows. Sometimes, it is useful to set the height of an individual row without changing the others.

To achieve this, we override two classes. The JTable class itself, and the class responsible for rendering the table onto the screen, the TableUI class. Actually the TableUI is an abstract class and we don't want to implement all of its functionality, so our new class is actually derived from BasicTableUI.

The two new classes are JTableEx and TableUIEx. We have hard coded the use of TableUIEx within JTableEx. JTableEx adds a data member, rowHeights, to track the height of the non-uniform rows. The type of rowHeights is HashMap and one of the reason for selecting HashMap rather than Hashtable was that HashMap is not synchronized and therefore faster than Hashtable. There are a few new methods to deal with the rowHeights variable. All the other methods in JTableEx are overrides of JTable methods. These methods are essentially the same as the those in JTable but do not assume that all rows are the same height.

The TableUIEx class is what actually renders the table onto the screen. Normally the UI classes are customized for each platform to give the component a platform specific look and feel (or the java look and feel). In this case, the rendering of the table is practically the same on different platforms and we can get by with subclassing BasicTableUI. Again, only those functions have been overridden that assumed that all rows were the same height.

The new functions are


	int getRowHeight( int row )
	{
		Object o = rowHeights.get( new Integer(row) );
		if( o == null ) return getRowHeight();
		return ((Integer)o).intValue();
	}
	
	void setRowHeight( int row, int height )
	{
		rowHeights.put( new Integer( row ), new Integer( height ) );
		revalidate();
	}
	
	void resetRowHeight( int row )
	{
		rowHeights.remove( new Integer( row ) );
		revalidate();
	}
	
	void resetRowHeight()
	{
		rowHeights.clear();
		revalidate();
	}

Note that the last three functions may affect a row height and call revalidate() at the end. It took me a while to figure out the difference between validate(), invalidate() and revalidate(). Here's my understanding of them so far. invalidate() marks a component and it's parent (and parent's parent) as dirty. It doesn't actually update the screen. revalidate() simply queues an invalidate() for a later time and get activated once all the events in the queue have been handled. Good thing about revalidate() is that you can call it multiple times and the invalidate() call placed in the queue causes any prior invalidate() in the queue to be removed so the component gets invalidated only once for a series of changes. validate() is really what updates everything and makes them valid. It is most often called for a container object which in turn validates all the contained components.


The two files ( JTableEx.java & TableUIEx.java ) follow:


// JTableEx.java
import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.plaf.*;

/**
 * @author Zafir Anjum
 */

public class JTableEx extends JTable
{
	public JTableEx() {
		this(null, null, null);
	}

	public JTableEx(TableModel dm) {
		this(dm, null, null);
	}

	public JTableEx(TableModel dm, TableColumnModel cm) {
		this(dm, cm, null);
	}

	public JTableEx(TableModel dm, TableColumnModel cm, ListSelectionModel sm) {
		super(dm,cm,sm);
		setUI( new TableUIEx() );
	}

	public JTableEx(int numRows, int numColumns) {
		this(new DefaultTableModel(numRows, numColumns));
	}

	public JTableEx(final Vector rowData, final Vector columnNames) {
		super( rowData, columnNames );
		setUI( new TableUIEx() );
	}

	public JTableEx(final Object[][] rowData, final Object[] columnNames) {
		super( rowData, columnNames );
		setUI( new TableUIEx() );
	}

	public int rowAtPoint(Point point) {
		int y = point.y;
		if( y < 0 ) return -1;

		int rowSpacing = getIntercellSpacing().height;
		int rowCount = getRowCount();
		int rowHeight = 0;
		for( int i = 0; i < rowCount; i++ )
		{
			rowHeight += getRowHeight(i) + rowSpacing;
			if( y < rowHeight )
				return i;
		}
		return -1;
	}

	public Rectangle getCellRect(int row, int column, boolean includeSpacing) {
		int index = 0;
		Rectangle cellFrame;
		int columnMargin = getColumnModel().getColumnMargin();
		Enumeration enumeration = getColumnModel().getColumns();
		TableColumn aColumn;

		cellFrame = new Rectangle();
		cellFrame.height = getRowHeight(row) + rowMargin;
		int rowSpacing = getIntercellSpacing().height;
		int y = 0;
		for( int i = 0; i < row; i++ )
		{
			y += getRowHeight(i) + rowSpacing;
		}
		cellFrame.y = y;

		while (enumeration.hasMoreElements()) {
			aColumn = (TableColumn)enumeration.nextElement();
			cellFrame.width = aColumn.getWidth() + columnMargin;

			if (index == column)
				break;

			cellFrame.x += cellFrame.width;
			index++;
		}

		if (!includeSpacing) {
			Dimension spacing = getIntercellSpacing();
			// This is not the same as grow(), it rounds differently.
			cellFrame.setBounds(cellFrame.x +	  spacing.width/2,
								cellFrame.y +	  spacing.height/2,
								cellFrame.width -  spacing.width,
								cellFrame.height - spacing.height);
		}
		return cellFrame;
	}

	public void tableChanged(TableModelEvent e) {
		if (e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW) {
			// The whole thing changed
			clearSelection();

			if (getAutoCreateColumnsFromModel())
				createDefaultColumnsFromModel();

			resizeAndRepaint();
			if (tableHeader != null) {
				tableHeader.resizeAndRepaint();
			}
			return;
		}

		if (e.getType() == TableModelEvent.INSERT) {
			tableRowsInserted(e);
			return;
		}

		if (e.getType() == TableModelEvent.DELETE) {
			tableRowsDeleted(e);
			return;
		}

		int modelColumn = e.getColumn();
		int start = e.getFirstRow();
		int end = e.getLastRow();

		if (start == TableModelEvent.HEADER_ROW) {
			start = 0;
			end = Integer.MAX_VALUE;
		}

//		int rowHeight = getRowHeight() + rowMargin;
		Rectangle dirtyRegion;
		if (modelColumn == TableModelEvent.ALL_COLUMNS) {
			// 1 or more rows changed
//			dirtyRegion = new Rectangle(0, start * rowHeight,
			dirtyRegion = new Rectangle(0, getCellRect(start,0,false).y ,
									       
getColumnModel().getTotalColumnWidth(), 0);
		}
		else {
			// A cell or column of cells has changed.
			// Unlike the rest of the methods in the JTable, the TableModelEvent
			// uses the co-ordinate system of the model instead of the view.
			// This is the only place in the JTable where this "reverse mapping"
			// is used.
			int column = convertColumnIndexToView(modelColumn);
			dirtyRegion = getCellRect(start, column, false);
		}

		// Now adjust the height of the dirty region according to the value of "end".
		// Check for Integer.MAX_VALUE as this will cause an overflow.
		if (end != Integer.MAX_VALUE) {
			//dirtyRegion.height = (end-start+1)*rowHeight;
			dirtyRegion.height = getCellRect(end+1,0,false).y - dirtyRegion.y;
			repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
		}
		// In fact, if the end is Integer.MAX_VALUE we need to revalidate anyway
		// because the scrollbar may need repainting.
		else {
			resizeAndRepaint();
		}
	}

	private void tableRowsInserted(TableModelEvent e) {
		int start = e.getFirstRow();
		int end = e.getLastRow();
		if (start < 0)
			start = 0;

		// Move down row height info - for rows below the first inserted row
		int rowCount = getRowCount();
		int rowsInserted = end - start + 1;
		for( int r = start; r < rowCount; r++ )
		{
			Integer height = (Integer)rowHeights.get( new Integer(r) );
			if( height == null ) continue;
			rowHeights.put( new Integer( r+rowsInserted ), height  );
		}
			
		// 1 or more rows added, so we have to repaint from the first
		// new row to the end of the table.  (Everything shifts down)
//		int rowHeight = getRowHeight() + rowMargin;
		Rectangle drawRect = new Rectangle(0, getCellRect(start,0,false).y ,
									       
getColumnModel().getTotalColumnWidth(), 0);
//										   (getRowCount()-start) *
rowHeight);
		drawRect.height = getCellRect(rowCount,0,false).y - drawRect.y;

		// Adjust the selection to account for the new rows
		if (selectionModel != null) {
			if (end < 0)
				end = getRowCount()-1;
			int length = end - start + 1;

			selectionModel.insertIndexInterval(start, length, true);
		}
		revalidate();
		// PENDING(philip) Find a way to stop revalidate calling repaint
		// repaint(drawRect);
	}

	/*
	 * Invoked when rows have been removed from the table.
	 *
	 * @param e the TableModelEvent encapsulating the deletion
	 */
	private void tableRowsDeleted(TableModelEvent e) {
		int start = e.getFirstRow();
		int end = e.getLastRow();
		if (start < 0)
			start = 0;

		int deletedCount = end - start + 1;
		int previousRowCount = getRowCount() + deletedCount;
		
		// Remove any height information for deleted rows
		for( int i = start; i <= end; i++ )
			resetRowHeight(i);
		// Move up row height info - for rows below the last deleted row
		for( int r = end + 1; r < previousRowCount; r++ )
		{
			Integer height = (Integer)rowHeights.get( new Integer(r) );
			if( height == null ) continue;
			rowHeights.put( new Integer( r-deletedCount ), height  );
		}
			
		// 1 or more rows added, so we have to repaint from the first
		// new row to the end of the table.  (Everything shifts up)
//		int rowHeight = getRowHeight() + rowMargin;
		Rectangle drawRect = new Rectangle(0, getCellRect(start,0,false).y ,
									       
getColumnModel().getTotalColumnWidth(),0);
//										(previousRowCount - start) *
rowHeight);
		drawRect.height = getCellRect(previousRowCount,0,false).y - drawRect.y;

		// Adjust the selection to account for the new rows
		if (selectionModel != null) {
			if (end < 0)
				end = getRowCount()-1;

			selectionModel.removeIndexInterval(start, end);
		}
		revalidate();
		// PENDING(philip) Find a way to stop revalidate calling repaint
		// repaint(drawRect);
	}

	int getRowHeight( int row )
	{
		Object o = rowHeights.get( new Integer(row) );
		if( o == null ) return getRowHeight();
		return ((Integer)o).intValue();
	}
	
	void setRowHeight( int row, int height )
	{
		rowHeights.put( new Integer( row ), new Integer( height ) );
		revalidate();
	}
	
	void resetRowHeight( int row )
	{
		rowHeights.remove( new Integer( row ) );
		revalidate();
	}
	
	void resetRowHeight()
	{
		rowHeights.clear();
		revalidate();
	}
	
	protected HashMap rowHeights = new HashMap();
}  // End of Class JTableEx




//********************************************************************
//TableUIEx
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.util.Enumeration;
//import java.util.Hashtable;
import java.awt.event.*;
import java.awt.*;
//import javax.swing.plaf.*;
//import java.util.EventObject;

import javax.swing.text.*;

import javax.swing.plaf.basic.*;

public class TableUIEx extends BasicTableUI
{
	private Dimension createTableSize(long width) {
		int height = ((JTableEx)table).getCellRect(table.getRowCount(), 0, false).y;
		int totalMarginWidth =	table.getColumnModel().getColumnMargin() * 
												       
table.getColumnCount(); 
		long widthWithMargin = Math.abs(width) + totalMarginWidth;
		if (widthWithMargin > Integer.MAX_VALUE) {
			widthWithMargin = Integer.MAX_VALUE;
		}
		return new Dimension((int)widthWithMargin, height);
	}

	/**
	 * Return the minimum size of the table. The minimum height is the 
	 * row height (plus inter-cell spacing) times the number of rows. 
	 * The minimum width is the sum of the minimum widths of each column 
	 * (plus inter-cell spacing).
	 */
	public Dimension getMinimumSize(JComponent c) {
		long width = 0;
		Enumeration enumeration = table.getColumnModel().getColumns();
		while (enumeration.hasMoreElements()) {
			TableColumn aColumn = (TableColumn)enumeration.nextElement();
			width = width + aColumn.getMinWidth();
		}
		return createTableSize(width);
	}

	/**
	 * Return the preferred size of the table. The preferred height is the 
	 * row height (plus inter-cell spacing) times the number of rows. 
	 * The preferred width is the sum of the preferred widths of each column 
	 * (plus inter-cell spacing).
	 */
	public Dimension getPreferredSize(JComponent c) {
		long width = 0;
		Enumeration enumeration = table.getColumnModel().getColumns();
		while (enumeration.hasMoreElements()) {
			TableColumn aColumn = (TableColumn)enumeration.nextElement();
			width = width + aColumn.getPreferredWidth();
		}
		return createTableSize(width);
	}

	/**
	 * Return the maximum size of the table. The maximum height is the 
	 * row height (plus inter-cell spacing) times the number of rows. 
	 * The maximum width is the sum of the maximum widths of each column 
	 * (plus inter-cell spacing).
	 */
	public Dimension getMaximumSize(JComponent c) {
		long width = 0;
		Enumeration enumeration = table.getColumnModel().getColumns();
		while (enumeration.hasMoreElements()) {
			TableColumn aColumn = (TableColumn)enumeration.nextElement();
			width = width + aColumn.getMaxWidth();
		}
		return createTableSize(width);
	}

	public void paint(Graphics g, JComponent c) {
		Rectangle oldClipBounds = g.getClipBounds();
		Rectangle clipBounds = new Rectangle(oldClipBounds);
		int tableWidth = table.getColumnModel().getTotalColumnWidth();
		clipBounds.width = Math.min(clipBounds.width, tableWidth);
		g.setClip(clipBounds);

		// Paint the grid
		paintGrid(g);

		// Paint the rows
		int firstIndex = table.rowAtPoint(new Point(0, clipBounds.y));
		int  lastIndex = lastVisibleRow(clipBounds);

		Rectangle rowRect = new Rectangle(0, 0,
				   tableWidth,
				   ((JTableEx)table).getRowHeight(firstIndex) + table.getRowMargin());
		rowRect.y = table.getCellRect(firstIndex, 0, false).y;

		for (int index = firstIndex; index <= lastIndex; index++) {
			// Paint any rows that need to be painted
			if (rowRect.intersects(clipBounds)) {
				paintRow(g, index);
			}
			rowRect.y += rowRect.height;
			rowRect.height = ((JTableEx)table).getRowHeight(index+1);
		}
		g.setClip(oldClipBounds);
	}

	private void paintGrid(Graphics g) {
		g.setColor(table.getGridColor());

		if (table.getShowHorizontalLines()) {
			paintHorizontalLines(g);
		}
		if (table.getShowVerticalLines()) {
			paintVerticalLines(g);
		}
	}

	/*
	 * This method paints horizontal lines regardless of whether the
	 * table is set to paint one automatically.
	 */
	private void paintHorizontalLines(Graphics g) {
		Rectangle r = g.getClipBounds();
		Rectangle rect = r;
//		int delta = table.getRowHeight() + table.getRowMargin();
		int rowMargin = table.getRowMargin();
		int firstIndex = table.rowAtPoint(new Point(0, r.y));
		int  lastIndex = lastVisibleRow(r);
//		int y = delta*firstIndex+(delta-1);
		int y = table.getCellRect(firstIndex+1, 0, false).y -1;

		for (int index = firstIndex; index <= lastIndex; index ++) {
			if ((y >= rect.y) && (y <= (rect.y + rect.height))) {
				g.drawLine(rect.x, y, rect.x + rect.width - 1, y);
			}
			y += ((JTableEx)table).getRowHeight(index+1) + rowMargin;
		}
	}

	/*
	 * This method paints vertical lines regardless of whether the
	 * table is set to paint one automatically.
	 */
	private void paintVerticalLines(Graphics g) {
		Rectangle rect = g.getClipBounds();
		int x = 0;
		int count = table.getColumnCount();
		int horizontalSpacing = table.getIntercellSpacing().width;
		for (int index = 0; index <= count; index ++) {
			if ((x > 0) && (((x-1) >= rect.x) && ((x-1) <= (rect.x +
rect.width)))){
				g.drawLine(x - 1, rect.y, x - 1, rect.y + rect.height - 1);
			}

			if (index < count)
				x += ((TableColumn)table.getColumnModel().getColumn(index)).
					getWidth() + horizontalSpacing;
		}
	}

	private void paintRow(Graphics g, int row) {
		Rectangle rect = g.getClipBounds();
		int column = 0;
		boolean drawn = false;
		int draggedColumnIndex = -1;
		Rectangle draggedCellRect = null;
		Dimension spacing = table.getIntercellSpacing();
		JTableHeader header = table.getTableHeader();

		// Set up the cellRect
		Rectangle cellRect = new Rectangle();
		cellRect.height = ((JTableEx)table).getRowHeight(row) + spacing.height;
		cellRect.y = table.getCellRect(row,0,false).y;			//row * cellRect.height;

		Enumeration enumeration = table.getColumnModel().getColumns();

		// Paint the non-dragged table cells first
		while (enumeration.hasMoreElements()) {
			TableColumn aColumn = (TableColumn)enumeration.nextElement();

			cellRect.width = aColumn.getWidth() + spacing.width;
			if (cellRect.intersects(rect)) {
				drawn = true;
				if ((header == null) || (aColumn != header.getDraggedColumn())) {
					paintCell(g, cellRect, row, column);
				}
				else {
					// Paint a gray well in place of the moving column
					// This would be unnecessary if we drew the grid more cleverly
					g.setColor(table.getParent().getBackground());
					g.fillRect(cellRect.x, cellRect.y, cellRect.width, cellRect.height);
					draggedCellRect = new Rectangle(cellRect);
					draggedColumnIndex = column;
				}
			}
			else {
				if (drawn)
					// Don't need to iterate through the rest
					break;
			}

			cellRect.x += cellRect.width;
			column++;
		}

		// paint the dragged cell if we are dragging
		if (draggedColumnIndex != -1 && draggedCellRect != null) {
			draggedCellRect.x += header.getDraggedDistance();

			// Fill the background
			g.setColor(table.getBackground());
			g.fillRect(draggedCellRect.x, draggedCellRect.y,
					   draggedCellRect.width, draggedCellRect.height);

			// paint grid if necessary.
			g.setColor(table.getGridColor());
			int x1 = draggedCellRect.x;
			int y1 = draggedCellRect.y;
			int x2 = x1 + draggedCellRect.width - 1;
			int y2 = y1 + draggedCellRect.height - 1;
			if (table.getShowVerticalLines()) {
			// Left
				// g.drawLine(x1-1, y1, x1-1, y2);
			// Right
				g.drawLine(x2, y1, x2, y2);
			}
			// Bottom
			if (table.getShowHorizontalLines()) {
				g.drawLine(x1, y2, x2, y2);
			}

			// Render the cell value
			paintCell(g, draggedCellRect, row, draggedColumnIndex);
		}
	}

	private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
		// The cellRect is inset by half the intercellSpacing before painted
		int spacingHeight = table.getRowMargin();
		int spacingWidth = table.getColumnModel().getColumnMargin();

		// Round so that when the spacing is 1 the cell does not paint obscure lines.
		cellRect.setBounds(cellRect.x + spacingWidth/2, cellRect.y + spacingHeight/2,
						   cellRect.width - spacingWidth, cellRect.height -
spacingHeight);

		if (table.isEditing() && table.getEditingRow()==row &&
								 table.getEditingColumn()==column) {
			Component component = table.getEditorComponent();
		component.setBounds(cellRect);
			component.validate();
		}
		else {
			TableCellRenderer renderer = table.getCellRenderer(row, column);
			Component component = table.prepareRenderer(renderer, row, column);

			if (component.getParent() == null) {
				rendererPane.add(component);
			}
			rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
										cellRect.width,
cellRect.height, true);
		}
		// Have to restore the cellRect back to it's orginial size
		cellRect.setBounds(cellRect.x - spacingWidth/2, cellRect.y - spacingHeight/2,
						   cellRect.width + spacingWidth, cellRect.height +
spacingHeight);

	}

	private int lastVisibleRow(Rectangle clip) {
		int lastIndex = table.rowAtPoint(new Point(0, clip.y + clip.height - 1));
		// If the table does not have enough rows to fill the view we'll get -1.
		// Replace this with the index of the last row.
		if (lastIndex == -1) {
				lastIndex = table.getRowCount() -1;
		}
		return lastIndex;
	}
}


Posted On: 26-Dec-1998

internet.commerce



Acceptable Use Policy

internet.comMediabistrojusttechjobs.comGraphics.com

WebMediaBrands Corporate Info


Advertise | Newsletters | Feedback | Submit News

Legal Notices | Licensing | Permissions | Privacy Policy