Hi.
I need some help from a real Swing expert. Please!
I'm trying to create a JTable, that saves & restores *precise* column
widths, with dynamic paging (row count fits actual column widths).
Evertyhing works great, expect the column widths.
I've been through this for a few days, and my results are:
First, I realised there's no way to get the precise column width from a
JTable - the sum of columns preferred widths and the width of the table
are completely different values.
I was trying to override JTable.doLayout(), but it looks way too
complicated for me (by the way, the authors of JTable made this really
unfriendly for overriding - Resizeable2, Resizeable3... what is all
that?)
So my idea was to gather information of every column resize event, to
calculate the actual visible column width myself. For simplifying
purposes, let's assume that table consists of 3 columns and
its initial width is 300px. I know that creating a default JTable with
3 columns with unset widths will result in creating 3 columns 75px
wide, adding them to JTable, and the JTable will add 25px to each of
them (but the preferredWidth of the column won't change).
Definitions:
int tableWidth;
HashMap columns {
"name" -> column id
"text" -> visible column name
"width" -> column width
}
So my init method looks like these:
tableWidth = table.getWidth();
int oneCol = (int) (tableWidth/columns.length);
for(int i=0; i<columnModel.getColumnCount(); i++) {
columnModel.getColumn(i).setPreferredWidth(oneCol);
int modelIndex = table.convertColumnIndexToModel(i);
columns[modelIndex].put("width",oneCol);
}
Rounding to integers may cause some problems, so I need also:
int total = oneCol * columns.length;
int diff = width - total;
int lastViewIndex = columns.length -1;
TableColumn col = columnModel.getColumn(lastViewIndex);
col.setPreferredWidth(col.getPreferredWidth() + diff);
int modelIndex = table.convertColumnIndexToModel(lastViewIndex);
columns[modelIndex].put("width",oneCol)
After this, the sum of column widths equals *precisely*
table.getWidth();
Now, if I save the widths to e.g. HashMap, I'll be able to dispose of
the table, construct it again, restore the values, and the visual
result will be equal to the initial one. And this is where the stairs
begin:
The table width can change every time the user resizes its frame.
That's why I store the column widths in columns hashmap. So:
table.addComponentListener(this);
// (.....)
public void componentResized() {
this.widthChanged(table.getWidth());
}
public void widthChanged(int width) {
int percentChange = (int) (width/tableWidth);
tableWidth = width;
for(int modelIndex = 0; modelIndex < columns.length; modelIndex++)
{
int widthBefore = ((Integer)
columns[modelIndex].get("width")).intValue();
int widthAfter = (int) ( (widthBefore * percentChange) /
tableWidth);
columns[modelIndex].put("width",widthAfter);
int viewIndex = table.convertColumnIndexToView(modelIndex);
columnModel.getColumn(viewIndex).setPreferredWidth(widthAfter);
}
// and once again I need to calculate "diff" and add it to the last
visible column
// to exactly match table.getWidth()
}
This was the easy part, and this works fine. Now, I want the user to be
able to resize every column he wants:
table.setAutoResizeMode(AUTO_RESIZE_NEXT_COLUMN);
Turning auto resize off doesn't work, the header simply won't move when
this is set to AUTO_RESIZE_OFF.
My idea was: I catch the column resize event, get the new width of the
column, compare it to
column[resized_column_model_index].get("width") (which still keeps the
previous column width), calculate the difference, and add it to next
column:
public void columnResized(int viewIndex) {
Rectangle resRect = table.getTableHeader().getHeaderRect(viewIndex);
Rectangle nextRect =
table.getTableHeader().getHeaderRect(viewIndex+1);
/**
* The rectangles widths are now *exactly* equal to their visible
widths
* BTW, this is never out of bounds, because you can't resize the last
column.
*/
int modelIndex = table.convertColumnIndexToModel(viewIndex);
columnModel.getColumn(viewIndex).setPreferredWidth(resRect.width);
columns[modelIndex].put("width",resRect.width);
int nextModelIndex = table.convertColumnIndexToModel(viewIndex+1);
columnModel.getColumn(viewIndex+1).setPreferredWidth(resRect.width);
columns[nextModelIndex].put("width",resRect.width);
}
Because autoResizeMode is set to NEXT_COLUMN, only those two widths
were changed in the visible widths model. So the sum of
columns[].get("width") is still *exactly* equal to table.getWidth();
But now I encountered problems with catching the resize events. I
realised that JTable does not provide any listeners for this action.
The only access to that is the field JTableHeader.resizingColumn which
is set during column resizing and goes null when the mouse is released.
So:
table.getTableHeader().addMouseListener(this);
table.getTableHeader().addMouseMotionListener(this);
TableColumn resizingColumn;
public void mouseDragged(MouseEvent e) {
resizingColumn = header.getResizingColumn();
}
public void mouseReleased(MouseEvent e) {
if (resizingColumn != null) {
this.columnResized(columnModel.getColumnIndexAtX(e.getX());
}
}
And that's where I'm stucked. Everything works pretty nice, until
getHeaderRect() returns an idiotic value. The column widths are set to
abstract values(?!), according to calculations (because the input was
wrong) - e.g. resRect.width = 750 while the complete table width is
600.
>From this moment, everything goes down - the sum of column widths is
not equal to tableWidth, so doLayout keeps adding and substracting
width from different columns to match the table width,
TableColumn.getPreferredWidth() still returns the previous values
(doLayout() does not have any effect on prefferedWidth of the columns),
the incorrect widths are saved to the HashMap, because I've got no idea
how to check if headerRect() returned a correct value (sometimes it
lacks just a few pixels).
If anyone can tell me, why getHeaderRect() works incorrect, or maybe
someone has already been through this, or just can tell me how to
create a JTable, that stores *exact* column widths, fits the parent
component and allows column resizing at the same time (table width may
change every moment), please help.
Maybe I'm missing something - maybe there IS a way of getting *exact*
column width from a JTable (not TableColumn.getWidth(), because it is
equal to TableColumn.getPreferredWidth()), but the visible width after
adding or substracting values by JTable). Maybe there is an easier way
to catch column resize events, not reaching to getHeaderRect(). Maybe
I'm getting headerRect wrong, I just have absolutely no idea what's
wrong in my code, especially that it works fine in 99% cases and the
only bug is an incorrect value from the JTableHeader method.
I really need some help here.
Thanks a lot in advance,
Michal K.
Babu Kalakrishnan - 24 Aug 2006 08:48 GMT
> I'm trying to create a JTable, that saves & restores *precise* column
> widths, with dynamic paging (row count fits actual column widths).
[quoted text clipped - 8 lines]
> unfriendly for overriding - Resizeable2, Resizeable3... what is all
> that?)
Have you tried calling getWidth() on the table columns instead of
getPreferredWidth() ? I would expect that getWidth() should give you
the values to which the widths have been currently set to.
e.g.
public void doLayout()
{
super.doLayout();
// Query the widths of columns here and save to your variables
}
Since doLayout() gets called for any change (including sizing of the
container as well as drag-resizing of the column header), an overridden
doLayout() appears to be a good enough way to get notified about
changes in column widths.
Alternately, you could try registering a PropertyChangeListener on each
table column and listen to events with the property
TableColumn.COLUMN_WIDTH_PROPERTY
BK
Babu Kalakrishnan - 24 Aug 2006 08:55 GMT
> Alternately, you could try registering a PropertyChangeListener on each
> table column and listen to events with the property
> TableColumn.COLUMN_WIDTH_PROPERTY
Correction. Looking at the source code of TableColumn.java, it looks
like the property to listen for is "width" - (The public static final
String COLUMN_WIDTH_PROPERTY defined in the class seems to be unused
inside the class)
BK