I have a JTable and, inside its TableModel, I saved an int; this is the row index that I want my view to "keep in the middle" of the Viewport.
What I mean is that, for example, if I have 1000 rows, 10 are shown in the View at any time and my index is 500, I want my vieport to automatically show rows 495-504.
These rows are not necessarily selected.
I hope I was able to explain my problem properly.
Thank you
Assuming your row heights are the same,
int rowPosition = index - 10 / 2;
int pixelPosition = rowPosition * table.getRowHeight(rowPosition);
scrollPane.getViewPort().setViewPosition(new Point(0, pixelPosition));
If the row heights aren't the same, you would have to sum all of the row heights from row 1 to row index - 5.
1) JTable (notice can by caused by Filtering) returns number of rows
2) each of row can returns Rectangle
3) put this Rectangle to the JScrollPane#scrollRectToVisible(Rectangle aRect) wrapped into invokeLater(), simple example about moving JViewport to the decision row here
Related
I need to show multi-line content in a JTable. The actual content is a collection of objects maintained in a custom model, which extends DefaultTableModel and generates cell content on the fly by overriding getValueAt().
In order to have multi-line content, I have implemented a custom TableCellRenderer:
private class MultiLineCellRenderer extends JTextArea implements TableCellRenderer {
public MultiLineCellRenderer() {
setLineWrap(true);
setWrapStyleWord(true);
setOpaque(true);
setBorder(new EmptyBorder(-1, 2, -1, 2));
setRows(1);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
String text = value == null ? "" : value.toString();
if (!getText().equals(text)) {
setText(text);
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) != newHeight)
table.setRowHeight(row, newHeight);
}
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
return this;
}
}
Now if I populate the table with a few hundred rows (column count is 2), I see the AWT worker thread starting to max out one CPU core. At the same time, memory consuption goes up from ~100 MB to ten times that amount and further. That happens even if the application is not actually doing anything (no data loaded in the background, no user interaction) and stops only when I clear the collection from which the table gets its content.
By commenting out select sections of code, I have identified these lines as the culprit:
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) != newHeight)
table.setRowHeight(row, newHeight);
If I comment out this section, all table rows have the same height (1 row of text), but memory consumption stays around ~100 MB.
If I replace these lines with a single call to table.setRowHeight(row, 32), i.e. with a fixed value, memory consumption starts going up again indefinitely.
The following modification works, at the expense of all rows having the same height:
int newHeight = getRowHeight() * getLineCount();
if (table.getRowHeight() < newHeight)
table.setRowHeight(newHeight);
Bottom line: it seems setting individual row heights in a JTable creates a massive memory leak. Am I doing something wrong, or have I encountered an actual bug? In the latter case, are there any known fixes/workarounds?
Setting the row height triggers a redraw, which in turn triggers another call to the renderer. Therefore, it is important to set the row height only if it is different from the current one, in order to avoid an endles loop. This is what happens when you call setRowHeight() unconditionally, even with a fixed value.
A second issue is that each row comprises two cells, which may have different heights. The code above will set the row height to match the cell being rendered right now. When the other cell of that row gets rendered and has a different height, the row height gets changed again. That will trigger a redraw, also of the first column in the row. Since that will result in another height change, there’s the infine loop again.
Proof: the following code fixes this:
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) < newHeight)
table.setRowHeight(row, newHeight);
Now the row height will only increase, but never decrease, thus breaking the infinite loop. Side effect: if the cell contents change and now occupy fewer rows than before, the row height will not change to reflect this.
Bottom line: rendering a JTable with multiline cells is non-trivial, and SO has quite a few buggy examples. The only working example I found (thanks to another SO post) is at https://www.javaspecialists.eu/archive/Issue106.html.
Their solution is to store cell heights internally in the renderer (though this could also be done in the table model, whichever works best for your implementation). When calculating the height of a cell, store it, then get the maximum height of any cell in the row and use that. Also be sure to set the row height only if it differs from the current one.
That has fixed the memory leak/processor consumption issue, in addition to finally giving me a working example of how to calculate cell height properly.
I have a JTable with an AbstractTabelModel displayed. I tried to create a void method in my project that sets the width of each column to the length of the longest value in the column. Here is the method that I am using right now, where "accountWindow" is the JTable:
public void setColumnWidths(){
accountWindow.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
for (int i = 0; i < accountWindow.getColumnCount(); i++){
int greatestStringLength = 2;
for (int z = 0; z < accountWindow.getRowCount(); z++){
if (accountWindow.getValueAt(z, i).toString().length() > greatestStringLength){
System.out.println("Width SET");
greatestStringLength = accountWindow.getValueAt(z, i).toString().length();
accountWindow.getColumnModel().getColumn(i).setPreferredWidth(greatestStringLength);
}
//System.out.println(accountWindow.getValueAt(z, i).toString());
//System.out.println("Greatest Value: " + greatestValueWidth);
}
}
}
The method is called correctly in my Controller class (MVC), but it is setting the width of each column to essentially 0. Method is called after the table is updated and the fireTableData() method is called and account information is displayed. I have added the JTable to a scroll pane. Any comments or suggestions would be appreciated.
The width needs to be specified in pixels not characters in the String.
if (accountWindow.getValueAt(z, i).toString().length() > greatestStringLength){
Looks to me like you are just getting the number of characters in the String, not the width it takes to render the String. That is 20 characters will NOT render in 20 pixels. The width of the String will vary for different Fonts.
Check out Table Column Adjuster for the solution on how to determine the rendered width. It gives a simple usage and a custom class that you can use which has more features.
Both solutions will actually invoke the renderer used by the table to determine the maximum width.
I'm having some issues with autosizing cells. After my program populates and formats each accordingly, it autosizes the cells to make it look visually acceptable. The issue is, when the cell is formatted as percentage, it auto-sizes the cell without taking in consideration the fact that there's a '%' character, so we end up getting #### on the cells until you expand the cell. Is there a way to autosize it WHILE taking in consideration that extra '%' character?
EDIT:
So this is what happens, the left VAR has been autosized correctly for whatever reasons, but the VAR on the right hasn't.
EDIT2:
I noticed that this ONLY happens when the cell value is 0.00% So all the values in the column for VAR that has #### were 0s and some of the values in the left column for VAR were non-zeros.
While this may not be an ideal solution, one possibility is to grab the size of the string you are inserting into the cell (ie. "50.00%" = size of 6), and set the cell width based manually based on that.
I'm afraid this is not supported in Apache POI, as it disregards the custom cell formatting when trying to calculate column width (it only takes font characteristics and rotation into consideration, but no DataFormat).
As a workaround you can try to write your custom autoSizeColumn method as a modification of the one currently implemented, i.e.:
public void autoSizeColumn(Sheet sheet, int column, int plusMinusChars) {
double width = SheetUtil.getColumnWidth(sheet, column, false);
if (width != -1) {
width += plusMinusChars;
width *= 256;
int maxColumnWidth = 255*256; // The maximum column width for an individual cell is 255 characters
if (width > maxColumnWidth) {
width = maxColumnWidth;
}
sheet.setColumnWidth(column, (int)(width));
}
}
Then you can call it with an extra '%' character to be printed out:
//4th column autosized + 1 character in width to accomodate for '%'
autoSizeColumn(sheet, 3, 1);
If I have a jTable ( inside a jScrollPane ) with 1000+ columns. Is it possible when creating this jtable that the first column to be displayed is the 100th column ? all the previous columns could be seen by scrolling backwards. Thanks for any help.
Try
int columnToScrollTo = 100;
table.scrollRectToVisible(table.getCellRect(0, columnToScrollTo, true));
I have a JTable in which I set the column size as follows:
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getColumnModel().getColumn(0).setPreferredWidth(27);
table.getColumnModel().getColumn(1).setPreferredWidth(120);
table.getColumnModel().getColumn(2).setPreferredWidth(100);
table.getColumnModel().getColumn(3).setPreferredWidth(90);
table.getColumnModel().getColumn(4).setPreferredWidth(90);
table.getColumnModel().getColumn(6).setPreferredWidth(120);
table.getColumnModel().getColumn(7).setPreferredWidth(100);
table.getColumnModel().getColumn(8).setPreferredWidth(95);
table.getColumnModel().getColumn(9).setPreferredWidth(40);
table.getColumnModel().getColumn(10).setPreferredWidth(400);
This works fine, but when the table is maximized, I get empty space to the right of the last column. Is it possible to make the last column resize to the end of the window when resized?
I found AUTO_RESIZE_LAST_COLUMN property in docs but it does not work.
Edit: JTable is in a JScrollPane its prefered size is set.
What happens if you call setMinWidth(400) on the last column instead of setPreferredWidth(400)?
In the JavaDoc for JTable, read the docs for doLayout() very carefully. Here are some choice bits:
When the method is called as a result of the resizing of an enclosing window, the
resizingColumn is null. This means that resizing has taken place "outside" the JTable
and the change - or "delta" - should be distributed to all of the columns regardless of
this JTable's automatic resize mode.
This might be why AUTO_RESIZE_LAST_COLUMN didn't help you.
Note: When a JTable makes adjustments to the widths of the columns it respects their
minimum and maximum values absolutely.
This says that you might want to set Min == Max for all but the last columns, then set Min = Preferred on the last column and either not set Max or set a very large value for Max.
With JTable.AUTO_RESIZE_OFF, the table will not change the size of any of the columns for you, so it will take your preferred setting. If it is your goal to have the columns default to your preferred size, except to have the last column fill the rest of the pane, You have the option of using the JTable.AUTO_RESIZE_LAST_COLUMN autoResizeMode, but it might be most effective when used with TableColumn.setMaxWidth() instead of TableColumn.setPreferredWidth() for all but the last column.
Once you are satisfied that AUTO_RESIZE_LAST_COLUMN does in fact work, you can experiment with a combination of TableColumn.setMaxWidth() and TableColumn.setMinWidth()
JTable.AUTO_RESIZE_LAST_COLUMN is defined as "During all resize operations, apply adjustments to the last column only" which means you have to set the autoresizemode at the end of your code, otherwise setPreferredWidth() won't affect anything!
So in your case this would be the correct way:
table.getColumnModel().getColumn(0).setPreferredWidth(27);
table.getColumnModel().getColumn(1).setPreferredWidth(120);
table.getColumnModel().getColumn(2).setPreferredWidth(100);
table.getColumnModel().getColumn(3).setPreferredWidth(90);
table.getColumnModel().getColumn(4).setPreferredWidth(90);
table.getColumnModel().getColumn(6).setPreferredWidth(120);
table.getColumnModel().getColumn(7).setPreferredWidth(100);
table.getColumnModel().getColumn(8).setPreferredWidth(95);
table.getColumnModel().getColumn(9).setPreferredWidth(40);
table.getColumnModel().getColumn(10).setPreferredWidth(400);
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
Use this method
public static void setColumnWidths(JTable table, int... widths) {
TableColumnModel columnModel = table.getColumnModel();
for (int i = 0; i < widths.length; i++) {
if (i < columnModel.getColumnCount()) {
columnModel.getColumn(i).setMaxWidth(widths[i]);
}
else break;
}
}
Or extend the JTable class:
public class Table extends JTable {
public void setColumnWidths(int... widths) {
for (int i = 0; i < widths.length; i++) {
if (i < columnModel.getColumnCount()) {
columnModel.getColumn(i).setMaxWidth(widths[i]);
}
else break;
}
}
}
And then
table.setColumnWidths(30, 150, 100, 100);
Reading the remark of Kleopatra (her 2nd time she suggested to have a look at javax.swing.JXTable, and now I Am sorry I didn't have a look the first time :) )
I suggest you follow the link
I had this solution for the same problem: (but I suggest you follow the link above)
On resize the table, scale the table column widths to the current table total width.
to do this I use a global array of ints for the (relative) column widths):
private int[] columnWidths=null;
I use this function to set the table column widths:
public void setColumnWidths(int[] widths){
int nrCols=table.getModel().getColumnCount();
if(nrCols==0||widths==null){
return;
}
this.columnWidths=widths.clone();
//current width of the table:
int totalWidth=table.getWidth();
int totalWidthRequested=0;
int nrRequestedWidths=columnWidths.length;
int defaultWidth=(int)Math.floor((double)totalWidth/(double)nrCols);
for(int col=0;col<nrCols;col++){
int width = 0;
if(columnWidths.length>col){
width=columnWidths[col];
}
totalWidthRequested+=width;
}
//Note: for the not defined columns: use the defaultWidth
if(nrRequestedWidths<nrCols){
log.fine("Setting column widths: nr of columns do not match column widths requested");
totalWidthRequested+=((nrCols-nrRequestedWidths)*defaultWidth);
}
//calculate the scale for the column width
double factor=(double)totalWidth/(double)totalWidthRequested;
for(int col=0;col<nrCols;col++){
int width = defaultWidth;
if(columnWidths.length>col){
//scale the requested width to the current table width
width=(int)Math.floor(factor*(double)columnWidths[col]);
}
table.getColumnModel().getColumn(col).setPreferredWidth(width);
table.getColumnModel().getColumn(col).setWidth(width);
}
}
When setting the data I call:
setColumnWidths(this.columnWidths);
and on changing I call the ComponentListener set to the parent of the table (in my case the JScrollPane that is the container of my table):
public void componentResized(ComponentEvent componentEvent) {
this.setColumnWidths(this.columnWidths);
}
note that the JTable table is also global:
private JTable table;
And here I set the listener:
scrollPane=new JScrollPane(table);
scrollPane.addComponentListener(this);
This code is worked for me without setAutoResizeModes.
TableColumnModel columnModel = jTable1.getColumnModel();
columnModel.getColumn(1).setPreferredWidth(170);
columnModel.getColumn(1).setMaxWidth(170);
columnModel.getColumn(2).setPreferredWidth(150);
columnModel.getColumn(2).setMaxWidth(150);
columnModel.getColumn(3).setPreferredWidth(40);
columnModel.getColumn(3).setMaxWidth(40);
fireTableStructureChanged();
will default the resize behavior ! If this method is called somewhere in your code AFTER you did set the column resize properties all your settings will be reset. This side effect can happen indirectly. F.e. as a consequence of the linked data model being changed in a way this method is called, after properties are set.
No need for the option, just make the preferred width of the last column the maximum and it will take all the extra space.
table.getColumnModel().getColumn(0).setPreferredWidth(27);
table.getColumnModel().getColumn(1).setPreferredWidth(120);
table.getColumnModel().getColumn(2).setPreferredWidth(100);
table.getColumnModel().getColumn(3).setPreferredWidth(90);
table.getColumnModel().getColumn(4).setPreferredWidth(90);
table.getColumnModel().getColumn(6).setPreferredWidth(120);
table.getColumnModel().getColumn(7).setPreferredWidth(100);
table.getColumnModel().getColumn(8).setPreferredWidth(95);
table.getColumnModel().getColumn(9).setPreferredWidth(40);
table.getColumnModel().getColumn(10).setPreferredWidth(Integer.MAX_INT);
Use this code. It worked for me. I considered for 3 columns. Change the loop value for your code.
TableColumn column = null;
for (int i = 0; i < 3; i++) {
column = table.getColumnModel().getColumn(i);
if (i == 0)
column.setMaxWidth(10);
if (i == 2)
column.setMaxWidth(50);
}