In a JTable, how can I make some rows automatically increase height to show the complete multiline text inside? This is how it is displayed at the moment:
I do not want to set the height for all rows, but only for the ones which have multiline text.
The only way to know the row height for sure is to render each cell to determine the rendered height. After your table is populated with data you can do:
private void updateRowHeights()
{
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);
}
}
If only the first column can contain multiple line you can optimize the above code for that column only.
Camickr's solution did not work for me at all. My data model was dynamic though - it changed all the time. I guess the mentioned solution works for static data, like coming from an array.
I had JPanel for cell renderer component and it's preferred size wasn't set correctly after using prepareRenderer(...). The size was set correctly after the containing window was already visible and did repaint (2 times in fact after some unspecified, though short time). How could I call updateRowHeights() method shown above then and where would I do this? If I called it in (overriden) Table.paint() it obviously caused infinite repaints. It took me 2 days. Literally. The solution that works for me is this one (this is the cell renderer I used for my column):
public class GlasscubesMessagesTableCellRenderer extends MyJPanelComponent implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
//this method updates GUI components of my JPanel based on the model data
updateData(value);
//this sets the component's width to the column width (therwise my JPanel would not properly fill the width - I am not sure if you want this)
setSize(table.getColumnModel().getColumn(column).getWidth(), (int) getPreferredSize().getHeight());
//I used to have revalidate() call here, but it has proven redundant
int height = getHeight();
// the only thing that prevents infinite cell repaints is this
// condition
if (table.getRowHeight(row) != height){
table.setRowHeight(row, height);
}
return this;
}
}
You must iterate over each row, get the bounding box for each element and adjust the height accordingly. There is no code support for this in the standard JTable (see this article for a solution for Java ... 1.3.1 =8*O).
Related
for my Java program basically when the value in column 4 of my JTable is greater than column 3, I want those specific rows to be colored in red and not the other rows.
I have implemented the following code, but for some reason all my rows are getting colored in red rather than just the ones matching the criteria.
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer(){
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row, int col) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
int Value1= Integer.parseInt(table.getModel().getValueAt(row, 3).toString());
int Value2= Integer.parseInt(table.getModel().getValueAt(row, 4).toString());
if (Value2>=Value1) {
setBackground(Color.red);
}
return this;
}
});
Any suggestions/tips on how to fix this?
A DefaultTableCellRenderer instance uses a template component to render all cells (namely itself, see documentation). Once you set its color, the template will have that color and will be applied to all subsequent cells.
What you need to do is in your logic, set the color to red in the cases you need, and set it to the default background color in all other cases.
if(!isSelected) {
if (Value2>=Value1) {
setBackground(Color.red);
} else {
setBackground(table.getBackground()); // or use another color for another background
}
}
Looking at your code again, I'm noticing you are making an error with regards to model versus view indices. The getTableCellRendererComponent method is called with view indices, yet you are using these to index the model (eg in table.getModel().getValueAt(row, 3)). When your table is sorted, results will be incorrect as model indices and view indices will differ.
If you need to get values from the model, you first need to convert the view indices to model indices. Use JTable.convertRowIndexToModel and JTable.convertColumnIndexToModel to do that. Eg:
int modelRowId = table.convertRowIndexToModel(row);
int Value1= Integer.parseInt(table.getModel().getValueAt(modelRowId, 3).toString());
int Value2= Integer.parseInt(table.getModel().getValueAt(modelRowId, 4).toString());
Take a look at Table Row Rendering which shows how to do this by overriding the prepareRenderer(...) method of the JTable.
Using this approach you don't need a custom renderer for each data type in the table.
I need my JTable to automatically re-size its column widths to fit the content. I found the TableColumnAdjuster class very useful. But there's a small problem. Say i have 5 columns, and their content is very short. In that case, if i use the auto adjuster, it sets the first four columns widths according to their content and gives all the rest of space to the last column. Please see the example.
Here the last column, Balance is given all the excess space. But what if i need to give that space to one of the middle columns. In the above case, i need to assign that space to the third column, name. I tried modifying the TableColumnAdjuster class's adjustColumns() method. But I couldn't get it working.
I tried both column.setPreferredWidth() and column.setWidth() for changing column sizes. But seems it doesn't change anything. How can I effectively change the column sizes of a JTable. If there's some other alternative or a direct answer to my main problem, that's better. Thanks!
You can try the next:
public void resizeColumnWidth(JTable table) {
final TableColumnModel columnModel = table.getColumnModel();
for (int column = 0; column < table.getColumnCount(); column++) {
int width = 15; // Min width
for (int row = 0; row < table.getRowCount(); row++) {
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component comp = table.prepareRenderer(renderer, row, column);
width = Math.max(comp.getPreferredSize().width +1 , width);
}
if(width > 300)
width=300;
columnModel.getColumn(column).setPreferredWidth(width);
}
}
This needs to be executed before the resize method.
If you have:
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
There is no option to automatically resize one column larger than the other.
Maybe you can to something like:
tca = new TableColumnAdjuster( table, 0 );
tca.adjustColumns();
TableColumnModel tcm = table.getColumnModel();
TableColumn tc = tcm.getColumn(1);
tc.setWidth(tc.getWidth() + 25);
This would allow you to add extra space to column 1. This extra space would only be added the first time the table is displayed.
Another option is to use:
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
This would allocate extra space proportionally to each column.
setAutoResizeMode() will tell your table how to resize you should give it a try will all different options available to see the differences, in My case I wanted to specifically resize two columns and let it decide how to adjust all the other ones.
jTable1.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
TableColumnModel colModel=jTable1.getColumnModel();
colModel.getColumn(1).setPreferredWidth(25);
colModel.getColumn(2).setPreferredWidth(400);
You can do this:
JPanel jp = new JPanel();
jp.add(table);
jp.setLayout(new GridLayout(1,1)); /* little trick ;) and believe me that this step is important to the automatic all columns resize! A import is also needed for using GridLayout*/
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); // this is obvius part
With a slight modification to Paul Vargas's answer, you can also take into account the column header size:
public static void resizeColumnWidth(JTable table) {
final TableColumnModel columnModel = table.getColumnModel();
for (int column = 0; column < table.getColumnCount(); column++) {
// Account for header size
double width = table.getTableHeader().getHeaderRect(column).getWidth();
for (int row = 0; row < table.getRowCount(); row++) {
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component comp = table.prepareRenderer(renderer, row, column);
width = Math.max(comp.getPreferredSize().width + 1, width);
}
if (width > 300)
width = 300;
columnModel.getColumn(column).setPreferredWidth((int) width);
}
}
OK, I must be completely brainless but I can't seem to implement the code needed to set (permanently) the background color of the selected (clicked) cell in my JTable. I've read through most of the answers on this site but I'm still not getting it.
I'm using the preparedRenderer() method but I don't understand why it isn't working?
table.addMouseListener(
new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent m) {
row = table.getSelectedRow();
column = table.getSelectedColumn();
}
}
);
table = new JTable(data, columnNames) {
public Component prepareRenderer(TableCellRenderer rend, int r, int k) {
Component g = super.prepareRenderer(rend, row, column);
g.setBackground(Color.BLUE);
return g;
}
};
The way I'm understanding it is that prepareRenderer is taking a specific cell from the table as a Component and then allowing me to change the properties of that Component. But even if I write:
Component g = super.prepareRenderer(rend, 1, 1);
g.setBackground(Color.BLUE);
return g;
it just paints the whole table and not the cell at row=1, column=1???
I'm just not getting it...
it just paints the whole table and not the cell at row=1, column=1???
The prepareRenderer() method is called for every cell that gets repainted. This is done dynamically as the user selects a row or tabs to a new cell or clicks on a cell.
set (permanently) the background color of the selected (clicked) cell in my JTable.
Maybe you can create a Set of Point objects to represent the cells that you want to paint a different color. So when you click on the cell you create a Point object for the row/column and then add the Point to the set.
Then in the prepareRenderer(...) method you create a new Point representing the row/column you are about to renderer. If this Point is found in your Set then you change the background color.
I have a sortable JTable set up to use a custom extension of the AbstractTableModel. However, some behavior of this table is what I expected, and I would love some advice on how to figure this out.
I have the JTable set up to be sortable using:
thisJTable.setAutoCreateRowSorter(true);
This allows me to sort the table by clicking on the column headers as expected.
However, I find that when I sort the table by clicking on the column headers, the formatting (background and foreground color) of my rows are not sorted as well.
I had set up those rows to be color-coded based on the values they contain. When I sort by column header the formatting at a given row NUMBER stays the same (although the content that was previously in that row moved).
The color of the row is set by overriding the default prepareRenderer call for the JTable:
thisTable = new JTable(thisModel){
//Set up custom rendering - Sets background color of row to correct value
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
CustTableModel thisModel = (CustTableModel) getModel();
c.setBackground(thisModel.getRowBackgroundColor(row));
c.setForeground(thisModel.getRowForeColor(row));
return c;
}
};
Is there a better/different way to approach this?
Should I be using a different method to do my rendering, a method which would update the rendering of the JTable on a sort?
Or do I want to look into writing my own sorting method?
Solution (Thanks mKorbel!)
I thought I would post my solution, since I had to play with this a bit since I wasn't sure if the new index would be passed to the prepareRenderer as well.
thisTable = new JTable(thisModel){
//Set up custom rendering - Sets background color of row to correct value
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
int viewIdx = row;
int modelIdx = convertRowIndexToModel(viewIdx);
Component c = super.prepareRenderer(renderer, row, column);
CustTableModel thisModel = (CustTableModel) getModel();
c.setBackground(thisModel.getRowBackgroundColor(modelIdx));
c.setForeground(thisModel.getRowForeColor(modelIdx));
return c;
}
};
you have to convert row index from View to the Model
int modelRow = convertRowIndexToModel(row);
You can enable table sorting by clicking on header using this
table.setAutoCreateRowSorter(true);
for more information visit this site http://www.codejava.net/java-se/swing/6-techniques-for-sorting-jtable-you-should-know
I have a JTable with a custom Cell Renderer for multi-line cells. Everything is ok, the JTable is painted ok in the screen and I am very happy with it, but ast night when I tried to simply print it, I came up with a very strange issue. Using:
table.print(PrintMode.FIT_WIDTH, new MessageFormat("..."), new MessageFormat("..."));
I saw that the table did not print entirely. Then using another class made from a colleague for printing JTables I had the same result:
The table (with multi-line cells) needed 22 pages to print. The printed document (which I only viewed in xps format since I do not own a printer) had also 22 pages. But up to page 16 everything was printed as expected and after that only the borders and the column headers of the table were printed.
Strangely (to me) enough, when I tried to print the table using another cell renderer that does not allow for multi line cells, the table needed exactly 16 pages and was printed entirely, albeit the cropping in the lengthy cell values.
I searched all over the net but I had no luck. Does anybody know why could this be happening? Is there a solution?
Update:
My cell renderer is the following:
public class MultiLineTableCellRenderer extends JTextPane implements TableCellRenderer {
private List<List<Integer>> rowColHeight = new ArrayList<List<Integer>>();
public MultiLineTableCellRenderer() {
setOpaque(true);
}
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
String s = (String)value;
if (s.equals("<περιοδάριθμος>")) {
setForeground(Color.blue);
}
else if(s.equals("<παραγραφάριθμος>")) {
setForeground(Color.red);
}
else {
setForeground(Color.black);
}
setBackground(new Color(224, 255, 255));
if (isSelected) {
setBackground(Color.GREEN);
}
setFont(table.getFont());
setFont(new Font("Tahoma", Font.PLAIN, 10));
if (hasFocus) {
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
if (table.isCellEditable(row, column)) {
setForeground(UIManager.getColor("Table.focusCellForeground"));
setBackground(UIManager.getColor("Table.focusCellBackground"));
}
} else {
setBorder(new EmptyBorder(1, 2, 1, 2));
}
if (value != null) {
setText(value.toString());
} else {
setText("");
}
adjustRowHeight(table, row, column);
SimpleAttributeSet bSet = new SimpleAttributeSet();
StyleConstants.setAlignment(bSet, StyleConstants.ALIGN_CENTER);
StyleConstants.setFontFamily(bSet, "Tahoma");
StyleConstants.setFontSize(bSet, 11);
StyledDocument doc = getStyledDocument();
doc.setParagraphAttributes(0, 100, bSet, true);
return this;
}
private void adjustRowHeight(JTable table, int row, int column) {
int cWidth = table.getTableHeader().getColumnModel().getColumn(column).getWidth();
setSize(new Dimension(cWidth, 1000));
int prefH = getPreferredSize().height;
while (rowColHeight.size() <= row) {
rowColHeight.add(new ArrayList<Integer>(column));
}
List<Integer> colHeights = rowColHeight.get(row);
while (colHeights.size() <= column) {
colHeights.add(0);
}
colHeights.set(column, prefH);
int maxH = prefH;
for (Integer colHeight : colHeights) {
if (colHeight > maxH) {
maxH = colHeight;
}
}
if (table.getRowHeight(row) != maxH) {
table.setRowHeight(row, maxH);
}
}
}
Furthermore, if you test the following very simple example you will notice that something is terribly wrong with the printing, but I really can't find what!
public static void main(String[] args) throws PrinterException {
DefaultTableModel model = new DefaultTableModel();
model.addColumn("col1");
model.addColumn("col2");
model.addColumn("col3");
int i = 0;
for (i = 1; i <= 400; i++) {
String a = "" + i;
model.addRow(new Object[]{a, "2", "3"});
}
JTable tab = new JTable(model);
tab.print();
}
I believe you are having the same problem that I had when I asked this question:
Truncated JTable print output
I found a solution to my problem, and I believe it may help you as well.
The answer is here:
Truncated JTable print output
To summarize my answer:
If your TableCellRenderer is the only place in your code where you are setting rows to their correct height, then you are going to run into trouble caused by an optimization inside JTable: JTable only invokes TableCellRenderers for cells that have been (or are about to be) displayed.
If not all of your cells have been displayed on-screen, then not all of your renderers have been invoked, and so not all of your rows have been set to the desired height. With your rows not being their correct height, your JTable overall height is incorrect. After all, part of determining the overall JTable height is accounting for the height of each of that table's rows. If the JTable overall height isn't correct, this causes the print to truncate, since the JTable overall height is a parameter that is considered in the print layout logic.
An easy (but perhaps not squeaky clean) way to fix this is to visit all of your cell renderers manually before printing. See my linked answer for an example of doing this. I actually chose to do the renderer visitation immediately after populating my table with data, because this fixes some buggy behavior with the JTable's scrollbar extents (in addition to fixing the printing.)
The reason the table looks and works OK on-screen even when printing is broken, is because as you scroll around in the table, the various renderers are invoked as new cells come on screen, and the renderers set the appropriate row height for the newly visible rows, and various dimensions are then are recalculated on the fly, and everything works out OK in the end as you interact with the table. (Although you may notice that the scrollbar "extent" changes as you scroll around, which it really shouldn't normally do.)
Strange thing is that behavior is not deterministic.
Such behavior always makes me suspect incorrect synchronization.
It's not clear how your TableCellRenderer works, but you might try HTML, which is supported in many Swing components.
Another useful exercise is to prepare an sscce that reproduces the problem in minature. A small, complete example might expose the problem. It would also allow others to test your approach on different platforms.
This answer is probably too late for the one who asked this question, but for everybody with a similar problem, here is my solution;
I had exactly the same problem, I have my own TableCellRenderer to handle multi-line Strings which works flawless for showing the table but makes the printing of the table unreliable.
My solutions consists of 2 parts;
Part 1: I have created my own TableModel, in the getValueAt() I 'copied' a part of the StringCellRenderer logic, I make it recalculate and set the height of the table row in case af a multi-line String AND return the String as HTML with 'breaks' instead of line-separators.
Part 2: Before invoking the table.print() I call the getValueAt() for all cells (a for-loop over the columns with an inner for loop over the rows invoking the getValueAt()), this has to be done 'manually' because the print functionality doesn't invoke all getValueAt's (I have found reasons on different fora regarding this issue regarding the execution of the TableCellRenderers).
This way the clipping of the table is done like it is supposed to, only complete rows are printed per page and it devides the rows over severall pages if required with a table header at each page.