12

I have a JTable that is using a TableColumnModelListener() to detect when the column has been re-sized and I have some code I want to execute in the columnMarginChanged() method.

How do I determine whether the column was re-sized by the user or as a result of other code?

I am thinking I have to start with ChangeEvent.getSource() but I don't know where to go from there.

Thank you.

Ben
  • 54,723
  • 49
  • 178
  • 224
Dead_Jester
  • 602
  • 6
  • 9
  • 18
  • Unless you change it, the source will always be the table's `DefaultTableColumnModel` via `fireColumnMarginChanged()`. What problem are you trying to solve? – trashgod Jan 06 '12 at 02:54
  • no way. Repeating @trashgod's question: why? – kleopatra Jan 07 '12 at 11:40
  • If its your code which changes the column size, you can always deactivate or remove the listener before the change, make the change and then reactive or add the listener again. – Robin Jan 07 '12 at 15:05

4 Answers4

14

I can give you one possible approach. I was trying to solve the same problem, because I wanted to serialize information about column widths to disk, so that the next time the table opened up in my application, I could restore the column widths appropriately. Here goes:

Step 1 - Override your JTable and add a boolean property to it

class MyTable extends JTable {

    private boolean isColumnWidthChanged;
    public boolean getColumnWidthChanged() {
        return isColumnWidthChanged;
    }

    public void setColumnWidthChanged(boolean widthChanged) {
        isColumnWidthChanged = widthChanged;
    }

}

Step 2 - Add a TableColumnModelListener() to the table

private class TableColumnWidthListener implements TableColumnModelListener
{
    @Override
    public void columnMarginChanged(ChangeEvent e)
    {
        /* columnMarginChanged is called continuously as the column width is changed
           by dragging. Therefore, execute code below ONLY if we are not already
           aware of the column width having changed */
        if(!tableObj.hasColumnWidthChanged())
        {
            /* the condition  below will NOT be true if
               the column width is being changed by code. */
            if(tableObj.getTableHeader.getResizingColumn() != null)
            {
                // User must have dragged column and changed width
                tableObj.setColumnWidthChanged(true);
            }
        }
    }

    @Override
    public void columnMoved(TableColumnModelEvent e) { }

    @Override
    public void columnAdded(TableColumnModelEvent e) { }

    @Override
    public void columnRemoved(TableColumnModelEvent e) { }

    @Override
    public void columnSelectionChanged(ListSelectionEvent e) { }
}

Step 3 - Add a mouse listener to the table header

private class TableHeaderMouseListener extends MouseAdapter
{
    @Override
    public void mouseReleased(MouseEvent e)
    {
        /* On mouse release, check if column width has changed */
        if(tableObj.getColumnWidthChanged())
        {
            // Do whatever you need to do here

            // Reset the flag on the table.
            tableObj.setColumnWidthChanged(false);
        }
    }
}

NOTE: In my application, the TableHeaderMouseListener and TableColumnWidthListener classes were private inner classes of my main application class. My main application class held on to a reference of the table being observed. Therefore, these inner classes had access to the table instance. Obviously, depending on your setup, you need to do the appropriate thing to make the table instance available to these other classes. Hope this helps!

eternaln00b
  • 1,043
  • 9
  • 18
  • (don't quite get your requirement - why don't you simple save the widths always?) - but as to the marker: instead of using the cursor, which is extremely brittle, as you already noted :-) use the header's resizingColumn: it's != null only while a resize by the user is under way. That's how JXTableHeader supports packing and fixes the annoying core bug of triggering a sort even when resizing ... – kleopatra Jan 19 '12 at 13:36
  • 1
    As you pointed out, one approach is to simply serialize all column widths when the application is being shut down (maybe via the windowClosing method of a WindowListener). That may be the correct approach for the person who posted this question. The reason I must serialize column widths incrementally is because my JTable is destroyed and recreated from scratch every time a row/col is created/destroyed. (This may sound bizarre, but there is a reason for this that I can not disclose in a public forum.) So only way to get back the original widths is to serialize them incrementally. – eternaln00b Jan 19 '12 at 14:30
  • Maybe I can give a little more detail. :) You see, my JTable is an entry form for a more sophisticated backend. when the user makes changes to the JTable in my application, certain things are auto-constructed for the user in the background. So my JTable is essentially a wizard. To ensure that the table reflects exactly what has been auto-constructed in the background, I must parse the auto-constructed objects and recreate the table. If I do not do this, there is a danger (due to programming error) that the table and the auto-constructed objects start diverging and there is no longer one truth. – eternaln00b Jan 19 '12 at 14:34
4

Typically you want to be notified of a completed column drag, or a completed column resize. E.g.

interface SGridModel {
    public void columnMoved(int oldLocation, int newLocation);
    public void columnResized(int column, int newWidth);
}        

This isn't pretty but it will do what you want:

class SColumnListener extends MouseAdapter implements TableColumnModelListener {

    private final Logger log = Logger.getLogger(getClass());
    private final SGridModel model;

    private int oldIndex = -1;
    private int newIndex = -1;
    private boolean dragging = false;

    private boolean resizing = false;
    private int resizingColumn = -1;
    private int oldWidth = -1;

    SColumnListener(SGridModel model){
        this.model = model;
    }

    @Override
    public void mousePressed(MouseEvent e) {
        // capture start of resize
        if(e.getSource() instanceof JTableHeader) {
            TableColumn tc = ((JTableHeader)e.getSource()).getResizingColumn();
            if(tc != null) {
                resizing = true;
                resizingColumn = tc.getModelIndex();
                oldWidth = tc.getPreferredWidth();
            } else {
                resizingColumn = -1;
                oldWidth = -1;
            }
        }   
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        // column moved
        if(dragging && oldIndex != newIndex) {
            model.columnMoved(oldIndex, newIndex);
            log.info("column moved: " +oldIndex+" -> "+newIndex);
        }
        dragging = false;
        oldIndex = -1;
        newIndex = -1;


        // column resized
        if(resizing) {
            if(e.getSource() instanceof JTableHeader) {
                TableColumn tc = ((JTableHeader)e.getSource()).getColumnModel().getColumn(resizingColumn);
                if(tc != null) {
                    int newWidth = tc.getPreferredWidth();
                    if(newWidth != oldWidth) {
                        model.columnResized(resizingColumn, newWidth);
                        log.info("column resized: " +resizingColumn+" -> "+newWidth);
                    }
                }
            }   
        }
        resizing = false;
        resizingColumn = -1;
        oldWidth = -1;
    }

    @Override
    public void columnAdded(TableColumnModelEvent e) {      
    }

    @Override
    public void columnRemoved(TableColumnModelEvent e) {        
    }

    @Override
    public void columnMoved(TableColumnModelEvent e) {
        // capture dragging
        dragging = true;
        if(oldIndex == -1){
            oldIndex = e.getFromIndex();
        }

        newIndex = e.getToIndex();  
    }

    @Override
    public void columnMarginChanged(ChangeEvent e) {
    }

    @Override
    public void columnSelectionChanged(ListSelectionEvent e) {
    }
}

add it to the table as follows:

table.getColumnModel().addColumnModelListener(cl);
table.getTableHeader().addMouseListener(cl);
Ronziod
  • 41
  • 2
2

A simpler solution might just be to listen on the mouse release event (which happens only once in this user interaction) and check if the column-sizes changed in the meantime ? I'm using the code below to listen for column reordering and size changes.

getTableHeader().addMouseListener( new MouseAdapter() {
    public void mouseReleased(MouseEvent arg0)
    {
      String colNamesAndSizes = "";
      for( int i=0;i<getColumnModel().getColumnCount();i++ ) {

        if( i>0 ) colNamesAndSizes += ",";
        TableColumn column = getColumnModel().getColumn(i);
        colNamesAndSizes += column.getHeaderValue();
        colNamesAndSizes += ":";
        colNamesAndSizes += column.getWidth();
      }
      // check if changed, if yes, persist...
   }});
Axel Podehl
  • 4,034
  • 29
  • 41
0

You can also add PropertyChangeListener on each TableColumn to capture changes on width and preferredWidth properties.

mvmn
  • 3,717
  • 27
  • 30