CellContext.isExpanded() returns always true in JXTreeTable

123 views Asked by At

I am using a JXTreeTable. I want to show a text in a cell only if the row is collapsed. If the row is expanded the detailed values are shown in the childs so the shortened text in the parent should no longer be visible.

As far as I know this need to be implemented by providing a special ComponentProvider used by the DefaultTableRenderer. Anyhow the CellContext used by the ComponentProvider always indicates that the node is expanded. context.isExpanded() always returns true.

StringValue valueProvider = new StringValue() 
{
    @Override
    public String getString(Object value) 
    {
        return String.valueOf(value);
    }
};
ComponentProvider<?> textProvider = new LabelProvider(valueProvider, JLabel.TRAILING)
{
    @Override
    protected String getValueAsString(CellContext context)
    {
        System.out.println("Context expanded " + context.isExpanded());
        if (context.isExpanded())
        {
            return "";
        }
        return super.getValueAsString(context);
    }
};
DefaultTableRenderer renderer = new DefaultTableRenderer(textProvider);

What do I have to change in the ComponentProvider to detect if the row of the cell is expanded or not?

2

There are 2 answers

0
Uli On BEST ANSWER

The solution can be implemented based on the answer of dic19.

The solution is to implement a special renderer with a getTableCellRendererComponent method that checks for a JXTreeTable. In such a case it is evaluated if the row is expanded. The check for the leaf flag may be added also.

Unfortunately it is not possible to modify the DefaultTableRenderer because the CellContext is not visible in overriding classes.

public class DefaultTreeTableRenderer
    extends AbstractRenderer
    implements TableCellRenderer
{

    private TableCellContext cellContext;

    /**
     * Instantiates a default table renderer with the default component provider.
     * 
     * @see #DefaultTableRenderer(ComponentProvider)
     */
    public DefaultTreeTableRenderer()
    {
        this((ComponentProvider<?>)null);
    }

    /**
     * Instantiates a default table renderer with the given component provider. If the controller is null, creates
     * and uses a default. The default provider is of type <code>LabelProvider</code>.
     * 
     * @param componentProvider the provider of the configured component to use for cell rendering
     */
    public DefaultTreeTableRenderer(ComponentProvider<?> componentProvider)
    {
        super(componentProvider);
        this.cellContext = new TableCellContext();
    }

    /**
     * Instantiates a default table renderer with a default component provider using the given converter.
     * 
     * @param converter the converter to use for mapping the content value to a String representation.
     * @see #DefaultTableRenderer(ComponentProvider)
     */
    public DefaultTreeTableRenderer(StringValue converter)
    {
        this(new LabelProvider(converter));
    }

    /**
     * Instantiates a default table renderer with a default component provider using the given converter and
     * horizontal alignment.
     * 
     * @param converter the converter to use for mapping the content value to a String representation.
     * @see #DefaultTableRenderer(ComponentProvider)
     */
    public DefaultTreeTableRenderer(StringValue converter, int alignment)
    {
        this(new LabelProvider(converter, alignment));
    }

    /**
     * Intantiates a default table renderer with default component provider using both converters.
     * 
     * @param stringValue the converter to use for the string representation
     * @param iconValue the converter to use for the icon representation
     */
    public DefaultTreeTableRenderer(StringValue stringValue, IconValue iconValue)
    {
        this(new MappedValue(stringValue, iconValue));
    }

    /**
     * Intantiates a default table renderer with default component provider using both converters and the given
     * alignment.
     * 
     * @param stringValue the converter to use for the string representation
     * @param iconValue the converter to use for the icon representation
     * @param alignment the rendering component's horizontal alignment
     */
    public DefaultTreeTableRenderer(StringValue stringValue, IconValue iconValue, int alignment)
    {
        this(new MappedValue(stringValue, iconValue), alignment);
    }

    // -------------- implements javax.swing.table.TableCellRenderer
    /**
     * Returns a configured component, appropriate to render the given list cell.
     * <p>
     * Note: The component's name is set to "Table.cellRenderer" for the sake of Synth-based LAFs.
     * 
     * @param table the <code>JTable</code>
     * @param value the value to assign to the cell at <code>[row, column]</code>
     * @param isSelected true if cell is selected
     * @param hasFocus true if cell has focus
     * @param row the row of the cell to render
     * @param column the column of the cell to render
     * @return the default table cell renderer
     */
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
        boolean hasFocus, int row, int column)
    {
        boolean expanded = true;
        boolean leaf = true;
        if (table instanceof JXTreeTable)
        {
            JXTreeTable treeTable = (JXTreeTable)table;
            expanded = treeTable.isExpanded(row);
        }
        this.cellContext.installContext(table, value, row, column, isSelected, hasFocus, expanded, leaf);
        Component comp = this.componentController.getRendererComponent(this.cellContext);
        // fix issue #1040-swingx: memory leak if value not released
        this.cellContext.replaceValue(null);
        return comp;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected ComponentProvider<?> createDefaultComponentProvider()
    {
        return new LabelProvider();
    }

}
2
dic19 On

Anyhow the CellContext used by the ComponentProvider always indicates that the node is expanded.

This behavior goes back to the implementation of getTableCellRenderer(...) provided by DefaultTableRenderer: both expanded and leaf properties are set to true:

public class DefaultTableRenderer extends AbstractRenderer implements TableCellRenderer {

    private TableCellContext cellContext;

    ...

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

         // both 'expanded' and 'leaf' properties are set to 'true' here:
        cellContext.installContext(table, value, row, column, isSelected, hasFocus, true, true);
        // 'componentController' is an instance of 'ComponentProvider<?>'
        Component comp = componentController.getRendererComponent(cellContext);
        cellContext.replaceValue(null);
        return comp;
    }

    ...
}

It makes sense given those properties are significant in a tree cell context, not a table cell context. If you set a TreeCellRenderer to the JXTreeTable using the very same ComponentProvider then it should work as expected in the first column (tree column):

ComponentProvider<?> textProvider = new LabelProvider(valueProvider, JLabel.TRAILING) {...};
...
DefaultTreeRenderer treeRenderer = new DefaultTreeRenderer(textProvider);
...
treeTable.setTreeCellRenderer(treeRenderer);

Because the CellContext won't be a TableCellContext but a TreeCellContext and it will contain the correct expanded and leaf values:

public class DefaultTreeRenderer extends AbstractRenderer implements TreeCellRenderer {

    private TreeCellContext cellContext;

    ...

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {

        // both 'expanded' and 'leaf' properties are correctly set here:
        cellContext.installContext(tree, value, row, 0, selected, hasFocus, expanded, leaf);
        // 'componentController' is an instance of 'ComponentProvider<?>'
        Component comp = componentController.getRendererComponent(cellContext);
        cellContext.replaceValue(null);
        return comp;
    }

    ...
}