Skip to main content

Adjustable Row Height & GlazedLists clear()

10 replies [Last post]
Anonymous

Hi,

I just stumbled upon a problem concerning the row height and a backing
event list from the glazedlist project. I'll try to elaborate in steps
what happens:

- construct a EventTableModel to use my eventList in the table (all
glazedlists components)
- create the JXTable with that model and set the rowHeightEnabled to
true (there is one renderer which can have a different height)
- add several rows
- clear the list: BasicEventList.clear() - this will fire several events
each deleting one row:

- the tablemodel gets an event and propagates it using
fireTableChanged()
- JXTable handles the event in the method tableChanged()
- in the previous method the super.tableChanged() gets called
- one of the actions includes flushing of the filterPipeLine
- during this flush the sizeSequence is changed and recreated, note that
at this point it requests the row count of the tableModel
- tableModel row count is zero (the eventlist is cleared, now busy
firing events)
- SizeSequence of rowModel is empty

The second time the repeated part is running, the event requests a
delete of a row that isn't in the SizeSequence anymore (size = 0) which
is used in the super.tableChanged() method. It therefor results in a
java.lang.NegativeArraySizeException.

Am I making any sense here? While writing this, it seems a bit of a
cryptic explanation, if it may help, I'll try to create an example. Just
for reference, it does work with a plain JTable.

Kind Regards,
Jan

**** DISCLAIMER ****
http://www.schaubroeck.be/maildisclaimer.htm

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Kleopatra

Jan,

I think it's a glazedLists misbehaviour: as you already mentioned in
your first message of this thread, the model.getRowCount is incorrect
during event notification: which is to fire one delete per row, but the
actual count is already 0 (didn't dig, probably something related to the
event tricksery which is needed in glazedlists to keep the "view"
state). below is a failing test case exposing the problem.

Core JTable doesn't blow because it's not really using the current
rowCount (except for repainting, which simply doesn't update the correct
row ;-) JXTable on the other hand relies on the rowCount to update
internals _each time_ the tableChanged is invoked. This leads to
removing all sizes in the view rowModel if the count is 0.

As the reason is an illegal state during event notification, I'll pass
this back to Jesse - no way I'll adjust SwingX code to cope with
cheating models, we're doing enough tricksery ;-)

Sorry for the bad news
Jeanette

[code]
public class EventListTest extends TestCase {

/**
* NegativeIndexException if removing rows. GlazedLists only.
*/
public void testClearEventListNotificationRowCount() {
final EventList shownList = new BasicEventList();
EventTableModel model = createTableModel(shownList);
final int old = model.getRowCount();
JTable table = new JTable(model) {

@Override
public void tableChanged(TableModelEvent e) {
if (TableModelEvent.DELETE == e.getType()) {
int deletedCount = e.getFirstRow() - e.getLastRow() + 1;
assertEquals(1, deletedCount);
assertEquals("old - 1", old - 1, getRowCount());
}
super.tableChanged(e);
}

};
shownList.clear();
}
// the model creation is copied from your example
private EventTableModel createTableModel(
EventList shownList) {
addItemsToList(shownList);
TableFormat tableFormat = new TableFormat() {

public int getColumnCount() {
return 1;
}

public String getColumnName(int column) {
return "name";
}

public Object getColumnValue(RowItem baseObject, int column) {
return baseObject.getName();
}
};
return new EventTableModel(shownList, tableFormat);
}

private static void addItemsToList(EventList list) {
list.add(new RowItem("default1"));
list.add(new RowItem("default2"));
list.add(new RowItem("default3"));
}

public static class RowItem {

private String name;

public String getName() {
return name;
}

public RowItem(String name) {
this.name = name;
}
}

}
[/code]

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Jan,

sounds tricky (as everything on the borderline of different frameworks
with a design impedance mismatch ;-) I faintly remember having seen a
bit unexpected (by me, not being a glazedlist expert) event sequence
which Jesse never really explained ... so could be similar here:
> - clear the list: BasicEventList.clear() - this will fire several events
> each deleting one row:
>
>

wouldn't expect it to fire one event per row but the complete interval.
At least if you don't use the glazedList sorting/filtering (if you do,
it's a different story altogether because then you'll probably need to
use Jesse's adaption of the JX?Table anyway ... ). That said:
>
> - the tablemodel gets an event and propagates it using
> fireTableChanged()
> - JXTable handles the event in the method tableChanged()
> - in the previous method the super.tableChanged() gets called
> - one of the actions includes flushing of the filterPipeLine
> - during this flush the sizeSequence is changed and recreated, note that
> at this point it requests the row count of the tableModel
> - tableModel row count is zero (the eventlist is cleared, now busy
> firing events)
> - SizeSequence of rowModel is empty
>

>
> The second time the repeated part is running, the event requests a
> delete of a row that isn't in the SizeSequence anymore (size = 0) which
> is used in the super.tableChanged() method. It therefor results in a
> java.lang.NegativeArraySizeException.
>
>

sounds fishy ... how can the tableModel report zero rowcount if it had
fired the delete for one row only? Would expect a oldRowCount - 1 at
that point. So my pointing-fingers-perspective is that the model is
misbehaving (its in an illegal state, firing deletes while advertising
to be empty).

> Am I making any sense here? While writing this, it seems a bit of a
> cryptic explanation, if it may help, I'll try to create an example. Just
> for reference, it does work with a plain JTable.
>
>
No, your explanation is plain-text enough, in fact excellent! And yeah,
the JTable is more robust than its X successor - not surprisingly given
we have to apply a bunch of tricks on top Can't promise to have the
time to really dig, but an example is always helpful.

Cheers
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Jan Hoskens

I created a sample to show this behavior.

The zip contains:
- maven project with source
- eclipse project files
- in target/dependency the needed dependencies
- in target a test jar which can be used to start the sample (manifest
contains classpath with prefix dependency/ & main class)

Sample starts a frame with two tables: on the left a jxtable on the
right a normal jtable. Each has two buttons to fill and clear the table.
When starting the jar with java -jar test...jar an exception will be
thrown when clearing the jxtable. Note that when you've double clicked
the jar, the exception is probably invisible, so start command line to
see the exception in the console.

ps: don't mind the code, it's been put together rather quickly ;-)

(hope the attachment comes through)

Kind Regards,
Jan

On Wed, 2008-03-26 at 13:46 +0100, Kleopatra wrote:
> Hi Jan,
>
> sounds tricky (as everything on the borderline of different frameworks
> with a design impedance mismatch ;-) I faintly remember having seen a
> bit unexpected (by me, not being a glazedlist expert) event sequence
> which Jesse never really explained ... so could be similar here:
> > - clear the list: BasicEventList.clear() - this will fire several events
> > each deleting one row:
> >
> >
>
> wouldn't expect it to fire one event per row but the complete interval.
> At least if you don't use the glazedList sorting/filtering (if you do,
> it's a different story altogether because then you'll probably need to
> use Jesse's adaption of the JX?Table anyway ... ). That said:
> >
> > - the tablemodel gets an event and propagates it using
> > fireTableChanged()
> > - JXTable handles the event in the method tableChanged()
> > - in the previous method the super.tableChanged() gets called
> > - one of the actions includes flushing of the filterPipeLine
> > - during this flush the sizeSequence is changed and recreated, note that
> > at this point it requests the row count of the tableModel
> > - tableModel row count is zero (the eventlist is cleared, now busy
> > firing events)
> > - SizeSequence of rowModel is empty
> >

> >
> > The second time the repeated part is running, the event requests a
> > delete of a row that isn't in the SizeSequence anymore (size = 0) which
> > is used in the super.tableChanged() method. It therefor results in a
> > java.lang.NegativeArraySizeException.
> >
> >
>
> sounds fishy ... how can the tableModel report zero rowcount if it had
> fired the delete for one row only? Would expect a oldRowCount - 1 at
> that point. So my pointing-fingers-perspective is that the model is
> misbehaving (its in an illegal state, firing deletes while advertising
> to be empty).
>
> > Am I making any sense here? While writing this, it seems a bit of a
> > cryptic explanation, if it may help, I'll try to create an example. Just
> > for reference, it does work with a plain JTable.
> >
> >
> No, your explanation is plain-text enough, in fact excellent! And yeah,
> the JTable is more robust than its X successor - not surprisingly given
> we have to apply a bunch of tricks on top Can't promise to have the
> time to really dig, but an example is always helpful.
>
> Cheers
> Jeanette
>
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
> For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

**** DISCLAIMER ****
http://www.schaubroeck.be/maildisclaimer.htm
[test.zip]
---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Attachment not added (too large): "test.zip"

Kleopatra

Jan Hoskens schrieb:
> I created a sample to show this behavior.
>
> The zip contains:
>
>

a zip?? Sorry, Jan (no offence meant!) but if you can't strip it to a
small stand-alone (exept for references to glazedLists which is in one
of my swingx quick-test projects anyway) example of a couple of dozen
lines of code, there's a probability of zero for my looking at it. I
will not spent a single second to install anything. On my part it must
be just plain c&p into a file and run. Your description of the problem
sounded like that should be quite easy.

Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Jan Hoskens

I just zipped the case that I made for myself. It actually contained the
two java classes for the test and the necessary dependencies so that it
could be run out of the box.

I admit that I was a bit lazy to extract the core of the problem, after
debugging the whole thing for some time I took the shortest route for me
and didn't think twice about it, sorry for that.

In any case, here is a simple class demonstrating the same issue (though
not as nice as the one it the zip ;-) ).

package test.swing;

import java.awt.Component;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;

import org.jdesktop.swingx.JXTable;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.EventTableModel;

public class TableRowHeightSimpleTest
{
public static class RowItem
{

private String name;

public String getName()
{
return name;
}

public RowItem(String name)
{
this.name = name;
}
}

private static JPanel createJXTable()
{
JXTable table = new JXTable() ;
table.setRowHeightEnabled(true);

final EventList shownList = new
BasicEventList();
createTableModel(table,shownList);

JPanel panel = new JPanel();
panel.add(new JScrollPane(table));
panel.add(new JButton(new AbstractAction("clear")
{

@Override
public void actionPerformed(ActionEvent e)
{
shownList.clear();
}
}));
panel.add(new JButton(new AbstractAction("fill")
{

@Override
public void actionPerformed(ActionEvent e)
{
addItemsToList(shownList);
}
}));
return panel;
}

private static void createTableModel(JTable table,
EventList shownList)
{
addItemsToList(shownList);
TableFormat tableFormat = new TableFormat(){

@Override
public int getColumnCount()
{
return 1;
}

@Override
public String getColumnName(int column)
{
return "name";
}

@Override
public Object getColumnValue(RowItem baseObject, int column)
{
return baseObject.getName();
}};
table.setModel(new EventTableModel(shownList,
tableFormat));
TableCellRenderer renderer = new DefaultTableCellRenderer()
{

@Override
public Component getTableCellRendererComponent(JTable
table, Object value,
boolean isSelected, boolean hasFocus, int row,
int column)
{
table.setRowHeight(row, 50);
return super.getTableCellRendererComponent(table,
value, isSelected, hasFocus, row, column);
}

};

table.getColumnModel().getColumn(0).setCellRenderer(renderer);
}

private static void addItemsToList(EventList list)
{
list.add(new RowItem("default1"));
list.add(new RowItem("default2"));
list.add(new RowItem("default3"));
}

public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.add(createJXTable());
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}

Kind Regards,
Jan

On Wed, 2008-03-26 at 15:01 +0100, Kleopatra wrote:
> Jan Hoskens schrieb:
> > I created a sample to show this behavior.
> >
> > The zip contains:
> >
> >
>
> a zip?? Sorry, Jan (no offence meant!) but if you can't strip it to a
> small stand-alone (exept for references to glazedLists which is in one
> of my swingx quick-test projects anyway) example of a couple of dozen
> lines of code, there's a probability of zero for my looking at it. I
> will not spent a single second to install anything. On my part it must
> be just plain c&p into a file and run. Your description of the problem
> sounded like that should be quite easy.
>
> Jeanette
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
> For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

**** DISCLAIMER ****
http://www.schaubroeck.be/maildisclaimer.htm

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Jan,

> TableCellRenderer renderer = new DefaultTableCellRenderer()
> {
>
> @Override
> public Component getTableCellRendererComponent(JTable
> table, Object value,
> boolean isSelected, boolean hasFocus, int row,
> int column)
> {
> table.setRowHeight(row, 50);
> return super.getTableCellRendererComponent(table,
> value, isSelected, hasFocus, row, column);
> }
>
> };
>
>

here's the culprit: _never ever_ change the calling renderee's state
during the getXXRendererComponent. It must be regarded as strictly
read-only (even though technically, it's possible) in all if its
properties, especially those which might re-trigger a painting.
Commenting the line is okay. You need to update the size somewhere else,
f.i. when listening to cell content changes which might result in a
different height.

CU
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Jan Hoskens

The problem still persists if the height of a row is set in any other
way. If eg adding a button that sets the height of the selected row like
this:

panel.add(new JButton(new AbstractAction("set height")
{

@Override
public void actionPerformed(ActionEvent e)
{
int i = table.getSelectedRow();
if (i != -1)
table.setRowHeight(i, 50);
}
}));

When clearing the table, the exception still pops up.

I find it odd that there isn't an easy way to set the row height. At
first I thought that by setting the preferred size of the cell renderer
the row would adjust its height. No luck there.

Then I tried setting the row height in the cell renderer which is
actually suggested by a lot of samples on the web. Only thing I didn't
put in the sample that I posted was a check on the current row height to
see if it needed changing, but this doesn't give a different result
(still has the error). This works with the JTable, but each cell may set
a different height and trigger a repaint.

Now I've tried to create a listener on the model to adjust the height
when rows are added, but this does not seem to work. Additionally it
kinda feels like fixing things afterward, but that may be just me.

Why can't the table just respect the preferred sizes of the cell
renderers when needed? In my opinion, that seems to be the natural way
to have different row heights. Now everyone needs to add a custom way to
adjust the row height to the maximum preferred height of all cells in
the row. Isn't this a very common behavior that is lacking in the JTable
today?

Kind Regards,
Jan

> here's the culprit: _never ever_ change the calling renderee's state
> during the getXXRendererComponent. It must be regarded as strictly
> read-only (even though technically, it's possible) in all if its
> properties, especially those which might re-trigger a painting.
> Commenting the line is okay. You need to update the size somewhere else,
> f.i. when listening to cell content changes which might result in a
> different height.
>
> CU
> Jeanette

**** DISCLAIMER ****
http://www.schaubroeck.be/maildisclaimer.htm

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Jan,
> The problem still persists if the height of a row is set in any other
> way.

sigh .. would have been too easy ;-)

Okay, will give it another look asap.

Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Jan Hoskens

Thanks,

I do get the feeling that it is indeed a glazedlists issue as you
mentioned earlier. Firing a sequence of row deleted events while the
tableModel has no rows sounds a bit fishy. My first reaction was to post
it here because the JTable didn't have any problems, but it might be
better to mention this on the glazedlists maillist. I'm going to post a
reference to this thread on their maillist so that they can take a look.

Kind Regards,
Jan

On Thu, 2008-03-27 at 11:46 +0100, Kleopatra wrote:
> Jan,
> > The problem still persists if the height of a row is set in any other
> > way.
>
> sigh .. would have been too easy ;-)
>
> Okay, will give it another look asap.
>
> Jeanette
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
> For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

**** DISCLAIMER ****
http://www.schaubroeck.be/maildisclaimer.htm

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

crossing messages ;-)

yeah, good idea to pass it over to the glazedlists (away from swingx )

CU
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net