I am trying to build jTable in which I have a heavy (not very heavy) task of painting each cell of jTable. But I'm not painting very frequently (painting/updating each cell very rarely). After implementing jTable I encountered java.lang.OutOfMemoryError: Java heap space. I figured out that it is due to the call to paint(Graphics g, JComponent c) each microsecond. I don't wish to call this method all the time only in the case when table gets updated/modified. Is there any way to solve this problem?
Edit:
I have not called paint manually. The table has a manually created UI which is set using the setUI method. I have used this UI to create cells that can span multiple rows or columns (i.e. to merge few cells together).
setUI(new MultiSpanCellTableUI());
The class MultiSpanCellTableUI implements a paint() method which is getting called every second.
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);
int firstIndex = table.rowAtPoint(new Point(0, clipBounds.y));
int lastIndex = table.getRowCount() - 1;
Rectangle rowRect = new Rectangle(0, 0, tableWidth,
table.getRowHeight() + table.getRowMargin());
rowRect.y = firstIndex * rowRect.height;
for (int index = firstIndex; index <= lastIndex; index++) {
if (rowRect.intersects(clipBounds)) {
paintRow(g, index);
}
rowRect.y += rowRect.height;
}
g.setClip(oldClipBounds);
}
private void paintRow(Graphics g, int row) {
System.out.println("paintRow called");
Rectangle rect = g.getClipBounds();
boolean drawn = false;
AttributiveCellTableModel tableModel = (AttributiveCellTableModel) table
.getModel();
CellSpan cellAtt = (CellSpan) tableModel.getCellAttribute();
int numColumns = table.getColumnCount();
for (int column = 0; column < numColumns; column++) {
Rectangle cellRect = table.getCellRect(row, column, true);
int cellRow, cellColumn;
if (cellAtt.isVisible(row, column)) {
cellRow = row;
cellColumn = column;
} else {
cellRow = row + cellAtt.getSpan(row, column)[CellSpan.ROW];
cellColumn = column
+ cellAtt.getSpan(row, column)[CellSpan.COLUMN];
}
if (cellRect.intersects(rect)) {
drawn = true;
System.out.println("paintCell called!");
paintCell(g, cellRect, cellRow, cellColumn);
} else {
if (drawn)
break;
}
}
}
private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
int spacingHeight = table.getRowMargin();
int spacingWidth = table.getColumnModel().getColumnMargin();
Color c = g.getColor();
g.setColor(table.getGridColor());
g.drawRect(cellRect.x, cellRect.y, cellRect.width - 1,
cellRect.height - 1);
g.setColor(c);
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);
}
}
Since it is called every second, after a while the OutOfMemoryError occurs. I only need to repaint the cells when I update something in the cell and that information I can obtain easily. But how can I limit the calls to paint() based on that information?
not sure why do you needed
1) painting by paint(), please for which type of Components in the JTable's Cell, if is there some JComponent the you have to override paintComponent() instead of paint()
2) not good design to perform custom painting in the larger JTable
3) you have to look at Renderer, better would be prepareRenderer if you painting by using some Color
4) JTable by default returns JLabel, there you can set Icon instaead of prerform some custom painting
5) maybe help you with JTable's performance Christmas Tree Applications
With no code posted above, I assume you are forcing a paint() on the JTable component.
There should not be a need to call paint() on the JTable object. To customize cell painting, use a TableCellRenderer instead.
public class MyCellRenderer extends JLabel implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) {
if (isSelected) {
// Is cell selected?
}
if (hasFocus) {
// Does this cell have the focus
}
// Configure the component with the specified value, here we are using JLabel
setText(value.toString());
// Set tool tip if desired
setToolTipText((String)value);
// Since the renderer is a component, return itself
return this;
}
}
Related
I have a JTable with a few rows of data but the rows don't stretch across the full width of the table. I'd like to be able to see all of the data without it being cut off and have the zebra stripes stretch the whole width, right now this is how my table looks, so far I have been successful in having my data being shown without being cut off:
I have already attempted to achieve what I want with the following code:
orderTable = new JTable(orderTableModel ) {
public boolean isCellEditable(int row, int col) {
return false;
}
public Component prepareRenderer(TableCellRenderer r, int row, int col) {
Component c = super.prepareRenderer(r, row, col);
// Next 3 lines adapted from https://stackoverflow.com/questions/17858132/automatically-adjust-jtable-column-to-fit-content/25570812
int rendererWidth = c.getPreferredSize().width;
TableColumn tableColumn = getColumnModel().getColumn(col);
tableColumn.setPreferredWidth(Math.max(rendererWidth + getIntercellSpacing().width, tableColumn.getPreferredWidth())); // Sets width of columns to fill content.
// Rows alternate in colour for readability.
if (row % 2 == 0) {
c.setBackground(Color.WHITE);
} else {
c.setBackground(new Color(234, 234, 234));
}
if (isRowSelected(row)) {
c.setBackground(new Color(24, 134, 254));
}
return c;
}
};
orderTable.setFont(new Font("", 0, 14));
orderTable.setRowHeight(orderTable.getRowHeight() + 10);
orderTable.setAutoCreateRowSorter(true);
orderTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
I have a JTable and I want when selecting a row in the table it will be colored blue and have a green border marked around the row.
I was able to make the color blue but can not make a green border marked around the row .
I have attached a picture -This is what it should be.
enter image description here
Thanks in advance
After editing:
I add to my code :
JTable jTable1 = new JTable( model )
{
// Returning the Class of each column will allow different
// renderers to be used based on Class
public Class getColumnClass(int column)
{
return columns[column];
}
#Override
public boolean isCellEditable(int row, int column) {
//all cells false
return false;
}
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
Component c = super.prepareRenderer(renderer, row, col);
JComponent jc = (JComponent)c;
if (isRowSelected(row)){
int top = (row > 0 && isRowSelected(row-1))?1:2;
int left = col == 0?2:0;
int bottom = (row < getRowCount()-1 && isRowSelected(row + 1))?1:2;
int right = col == getColumnCount()-1?2:0;
jc.setBorder(BorderFactory.createMatteBorder(top, left, bottom, right, new Color(210,236,204)));
jc.setForeground(new Color(164, 164, 172));
}
else{
jc.setBorder(null);
}
int selCol = -2;
int selRow = jTable1.getSelectedRow();
if ( selCol != -1 && selRow != -1 ){
if ( row == selRow || col == selCol){
c.setBackground(new Color(249,250,254));
}else
c.setBackground(Color.WHITE);
}
return c;
}
};
And I almost got What I wanted, but if you look closely the color missing between each cell.
What should I do?
This is what I have now
enter image description here
I think I have a weird problem here. Here is my code:
private class BorderCellRenderer extends DefaultTableCellRenderer {
private Border extraBorder;
/**
* A cell render is based on labels which are changed to display the correct appearance
* This cell renderer adds extra border to every render action
*/
BorderCellRenderer(Border extraBorder) {
this.extraBorder = extraBorder;
}
/**
* The setBorder() is used to change the cell appearance.
* The trick is to override the call (of JComponent) and to add extra space
* to it by making it an compound border with.
*
* Remember that getBorder() now returns our compound border
*/
public void setBorder(Border border) {
super.setBorder(new CompoundBorder(border, extraBorder));
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
setFont(table.getFont());
setText(value.toString());
/* set color depending on the state of the row */
TableRowItem rowItem = ((FooModel) table.getModel()).getRow(row);
String state = rowItem.getState();
/* set tool tip on EMPLOYEE column */
if(column == FooModel.COL_EMPLOYEE) {
setToolTipText("blalba");
}
/* Paint text according to state*/
if(state.equals(RowItemState.ENTERED)) {
setForeground(Color.black);
//TODO setSelectionForeground(Color.black);
}
if(state.equals(RowItemState.SUBMITTED)) {
setForeground(Color.blue);
//TODO setSelectionForeground(Color.blue);
}
if(state.equals(RowItemState.APPROVED)) {
Color green = new Color(0, 128, 0); // greenish
setForeground(green);
//TODO setSelectionForeground(green);
}
if(state.equals(RowItemState.BOOKED)) {
setForeground(Color.red);
//TODO setSelectionForeground(Color.red);
}
switch (column) {
case FooModel.COL_EMPLOYEE:
case FooModel.COL_SAT:
case FooModel.COL_SUN:
if(state.equals(RowItemState.CHECKED)) {
setBackground(new Color(183, 244,176)); //light green
} else {
setBackground(java.awt.Color.lightGray);
}
break;
default:
if(state.equals(RowItemState.CHECKED)) {
setBackground(new Color(183, 244,176)); //light green
} else {
setBackground(java.awt.Color.white);
}
break;
}
//}
if (column == FooModel.COL_TOTAL){
if (table.getSelectedRowCount() > 0){
int rowsSelected[] = table.getSelectedRows();
double total = 0;
for (int j = 0; j < table.getSelectedRowCount(); j++){
Double dTemp = (Double) table.getValueAt(rowsSelected[j], FooModel.COL_TOTAL);
total += dTemp.doubleValue();
}
setToolTipText("Total Selected = " + String.valueOf(total));
}
}
// return super method for selections
return super.getTableCellRendererComponent(table, value,
isSelected, hasFocus,
row, column);
}
}
Now we get to the fun part. This code example runs fine, however when I uncomment the TODO lines so that the setSelectionForeground() methods are called, my CPU gets to like 20% on a Quad-Core. This happens on all my colleagues PC's as well (also Quad-Cores). When the lines are commented, CPU is around 0-1%. I find this very strange and cannot figure out what is going wrong here.
I hope you can help me.
Invoking setSelectionForeground() schedules a call to repaint() each of the four times time it is called for every cell. Instead,
Do not call setSelectionForeground() in the renderer.
Condition the color once based on the value of isSelected.
use
and to test coordinates boolean isSelected, boolean hasFocus, int row, int column (isSelected/hasFocus)
getValue from XxxTableModel by strictly convertViewToModel (e.g. JTables view can be sorted or filtered)
job for prepareRenderer
I have a jTable as following :
I want when the value in the Quantité is less than the value in the Min seuil de suantité, to change the color of the row to pink.
In the load of the program all works fine, but when I do some event like click on the table, the color of all the rows is changed even if the the value of the Quantité is not less than the value of the Min seuil de quantité :
this is my cell rendering :
public class CustomTableCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table,
Object obj, boolean isSelected, boolean hasFocus, int row, int column) {
Component cell = super.getTableCellRendererComponent(
table, obj, isSelected, hasFocus, row, column);
setHorizontalAlignment(SwingConstants.LEFT);
int selectedRow = table.convertRowIndexToModel(row);
if (table.getModel().getValueAt(selectedRow, 3) != null && table.getModel().getValueAt(selectedRow, 4) != null) {
int quantite = Integer.parseInt(table.getModel().getValueAt(selectedRow, 3).toString());
int minQuantite = Integer.parseInt(table.getModel().getValueAt(selectedRow, 4).toString());
if (quantite < minQuantite) {
if (isSelected) {
cell.setBackground(new Color(255, 138, 239));
} else {
cell.setBackground(new Color(252, 189, 252));
}
}
}
return cell;
}
}
and this is the code which allows me to affect the cell rendering to my table :
private void cellRendering(){
for (int i = 0; i < masterTable.getColumnCount(); i++) {
tcol = masterTable.getColumnModel().getColumn(i);
tcol.setCellRenderer(new CustomTableCellRenderer());
}
}
The renderer is a rubber stamp that remembers what ink was applied last. Be sure to set the desired color each time the renderer is invoked. More details can be found here.
I have a table , where some objects are rendered and are of non-fixed size (list of attributes). I want each row to be as high as it's tallest object , and I was wondering of to do it. I thought about doing something like this (see below) , but I'm sure there's something better..
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected,
boolean hasFocus, int row,
int column)
{
/*....*/
this.setListData((Object[])value);
int height = new Double(getPreferredSize().getHeight()).intValue();
if (table.getRowHeight(row) < height)
table.setRowHeight(row, height);
/*....*/
return this;
}
You should not have code like that in a renderer. Instead, when you load the data into the model, do something like:
private void updateRowHeights()
{
try
{
for (int row = 0; row < table.getRowCount(); row++)
{
int rowHeight = table.getRowHeight();
for (int column = 0; column < table.getColumnCount(); column++)
{
Component comp = table.prepareRenderer(table.getCellRenderer(row, column), row, column);
rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
}
table.setRowHeight(row, rowHeight);
}
}
catch(ClassCastException e) {}
}
I would stick with the solution you have. I don't think there is a simpler way to do this. No matter what you are going to have the check the height of each cell, so you might as well do that as you render each one.