Link to home
Start Free TrialLog in
Avatar of jksung
jksung

asked on

Java SWING JTable - is there a way to dynamically freeze a specific number of columns in the table so they cannot be moved?

By default, the columns of a JTable can be rearranged by dragging the column to new locations from the UI.  However, I would like to freeze some of the beginning columns so that cannot be moved.  To do so, I am using the two table method using the code below, basically one table has sorting disabled and will act as the frozen columns, and the second table with sorting enabled will be act as the normal columns, and I will declare the window to add to the display panel with a constructor such as:
frame.getContentPane().add(new FrozenTablePane(table, 2)); //2 is the number of columns to freeze.

This works, however I would like to dynamically change the number of frozen columns from the UI, for example, in the code below, in the test console created from
createTestFrame();
by entering the number of columns to freeze, then clicking on the "Set Frozen Columns" button, this would change the number of columns that are frozen in the table.  Is it possible to do this?  I have not been able to come up with a solution.  The code for the two files FrozenTablePane.java and ExampleTableModel.java (the sample table data) are below:


<<<FrozenTablePane.java>>>
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashSet;

import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

public class FrozenTablePane extends JScrollPane {

      public FrozenTablePane(JTable table, int colsToFreeze) {
            super(table);

            TableModel model = table.getModel();

            // create a frozen model
            TableModel frozenModel = new DefaultTableModel(model.getRowCount(),
                        colsToFreeze);

            // populate the frozen model
            for (int i = 0; i < model.getRowCount(); i++) {
                  for (int j = 0; j < colsToFreeze; j++) {
                        String value = (String) model.getValueAt(i, j);
                        frozenModel.setValueAt(value, i, j);
                  }
            }

            // create frozen table
            JTable frozenTable = new JTable(frozenModel);

            // remove the frozen columns from the original table
            for (int j = 0; j < colsToFreeze; j++) {
                  table.removeColumn(table.getColumnModel().getColumn(0));
            }
            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

            // format the frozen table
            JTableHeader header = table.getTableHeader();
            frozenTable.setBackground(header.getBackground());
            frozenTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            frozenTable.setEnabled(false);

            // set frozen table as row header view
            JViewport viewport = new JViewport();
            viewport.setView(frozenTable);
            viewport.setPreferredSize(frozenTable.getPreferredSize());
            setRowHeaderView(viewport);
            setCorner(JScrollPane.UPPER_LEFT_CORNER, frozenTable.getTableHeader());
      }

      /**
       * The main() is used in application.
       *
       * @param args
       *            command line arguments
       */
      public static void main(final String[] args) {
            // Create parent frame for ReportApplianceUI
            JFrame frame = new JFrame("FrozenTablePane");
            //Create the JTable using the ExampleTableModel implementing
            //the AbstractTableModel abstract class
            JTable table = new JTable(new ExampleTableModel());
            
            //Set the column sorting functionality on
            table.setAutoCreateRowSorter(true);
            
            //Uncomment the next line if you want to turn the grid lines off
          //  table.setShowGrid(false);
            
            //Change the colour of the table - yellow for gridlines
            //blue for background
            table.setGridColor(Color.YELLOW);
            table.setBackground(Color.CYAN);
            
            //String array to populate the combobox options
            String[] countries = {"Australia", "Brazil", "Canada", "China"
                        ,"France", "Japan", "Norway", "Russia", "South Korea"
                        , "Tunisia", "USA"};
            JComboBox countryCombo = new JComboBox(countries);
          
            //Set the default editor for the Country column to be the combobox
            TableColumn countryColumn = table.getColumnModel().getColumn(2);
            countryColumn.setCellEditor(new DefaultCellEditor(countryCombo));
          
            //set the Event column to be larger than the rest and the Place column
            //to be smaller
            TableColumn eventColumn = table.getColumnModel().getColumn(3);
            eventColumn.setPreferredWidth(150);
            
            TableColumn placeColumn = table.getColumnModel().getColumn(4);
            placeColumn.setPreferredWidth(5);
            
            
          //frame.getContentPane().add(table);
          frame.getContentPane().add(new FrozenTablePane(table, 2));
            frame.setPreferredSize(new Dimension(800, 500));
            frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            frame.pack();
            frame.setVisible(true);
            
            //create and display the test console
            createTestFrame();
      }
      
      private static void createTestFrame() {
            JFrame mButtonFrame = new JFrame();
            mButtonFrame.setLayout(new GridBagLayout());

            JButton mButton = new JButton("Set Frozen Columns");
            mButton.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent e) {
                        
                        System.out.println("Change the number of frozen columns here.");
                        
                  }
            });
            mButtonFrame.add(mButton);
            JTextField mTextField = new JTextField();
            mTextField.setPreferredSize(mButton.getPreferredSize());
            mButtonFrame.add(mTextField);
            mButtonFrame.pack();
            mButtonFrame.setVisible(true);
      }
}


<<<ExampleTableModel.java>>>
import javax.swing.table.AbstractTableModel;

class ExampleTableModel extends AbstractTableModel {

      // Two arrays used for the table data
      String[] columnNames = { "First Name", "Surname", "Country", "Event",
                  "Place", "Time", "World Record" };

      Object[][] data = {
                  { "César Cielo", "Filho", "Brazil", "50m freestyle", 1,
                              "21.30", false },
                  { "Amaury", "Leveaux", "France", "50m freestyle", 2, "21.45",
                              false },
                  { "Alain", "Bernard", "France", "50m freestyle", 3, "21.49",
                              false },
                  { "Alain", "Bernard", "France", "100m freestyle", 1, "47.21",
                              false },
                  { "Eamon", "Sullivan", "Australia", "100m freestyle", 2,
                              "47.32", false },
                  { "Jason", "Lezak", "USA", "100m freestyle", 3, "47.67", false },
                  { "César Cielo", "Filho", "Brazil", "100m freestyle", 3,
                              "47.67", false },
                  { "Michael", "Phelps", "USA", "200m freestyle", 1, "1:42.96",
                              true },
                  { "Park", "Tae-Hwan", "South Korea", "200m freestyle", 2,
                              "1:44.85", false },
                  { "Peter", "Vanderkaay", "USA", "200m freestyle", 3, "1:45.14",
                              false },
                  { "Park", "Tae-Hwan", "South Korea", "400m freestyle", 1,
                              "3:41.86", false },
                  { "Zhang", "Lin", "China", "400m freestyle", 2, "3:42.44",
                              false },
                  { "Larsen", "Jensen", "USA", "400m freestyle", 3, "3:42.78",
                              false },
                  { "Oussama", "Mellouli", "Tunisia", "1500m freestyle", 1,
                              "14:40.84", false },
                  { "Grant", "Hackett", "Australia", "1500m freestyle", 2,
                              "14:41.53", false },
                  { "Ryan", "Cochrane", "Canada", "1500m freestyle", 3,
                              "14:42.69", false },
                  { "Aaron", "Peirsol", "USA", "100m backstroke", 1, "52.54",
                              true },
                  { "Matt", "Grevers", "USA", "100m backstroke", 2, "53.11",
                              false },
                  { "Arkady", "Vyatchanin", "Russia", "100m backstroke", 3,
                              "53.18", false },
                  { "Hayden", "Stoeckel", "Australia", "100m freestyle", 3,
                              "53.18", false },
                  { "Ryan", "Lochte", "USA", "200m backstroke", 1, "1:53.94",
                              true },
                  { "Aaron", "Peirsol", "USA", "200m backstroke", 2, "1:54.33",
                              false },
                  { "Arkady", "Vyatchanin", "Russia", "200m backstroke", 3,
                              "1:54.93", false },
                  { "Kosuke", "Kitajima", "Japan", "100m breaststroke", 1,
                              "58.91", true },
                  { "Alexander", "Dale Oen", "Norway", "100m breaststroke", 2,
                              "59.20", false },
                  { "Hugues", "Duboscq", "France", "100m breaststroke", 3,
                              "59.37", false } };

      @Override
      public int getRowCount() {
            return data.length;
      }

      @Override
      public int getColumnCount() {
            return columnNames.length;
      }

      @Override
      public Object getValueAt(int row, int column) {
            return data[row][column];
      }

      // Used by the JTable object to set the column names
      @Override
      public String getColumnName(int column) {
            return columnNames[column];
      }

      // Used by the JTable object to render different
      // functionality based on the data type
      @Override
      public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
      }

      @Override
      public boolean isCellEditable(int row, int column) {
            if (column == 0 || column == 1) {
                  return false;
            } else {
                  return true;
            }
      }
}
Avatar of zzynx
zzynx
Flag of Belgium image

This is a possible solution:

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class FixedTableColumns extends JFrame {

    private int NUMBER_OF_FROZEN_COLUMNS = 2;
    private int columnValue = -1;
    private int columnNewValue = -1;

    public FixedTableColumns(String title) {
        super(title);
        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(new JScrollPane(getTable()), BorderLayout.CENTER);
    }

    private JTable getTable() {
        String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};
        Object[][] data = {
            {"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false)},
            {"John", "Doe", "Rowing", new Integer(3), new Boolean(true)},
            {"Sue", "Black", "Knitting", new Integer(2), new Boolean(false)},
            {"Jane", "White", "Speed reading", new Integer(20), new Boolean(true)},
            {"Joe", "Brown", "Pool", new Integer(10), new Boolean(false)}
        };

        JTable table = new JTable(data, columnNames);
        table.setFillsViewportHeight(true);
        table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
            @Override
            public void columnAdded(TableColumnModelEvent e) {
            }

            @Override
            public void columnRemoved(TableColumnModelEvent e) {
            }

            @Override
            public void columnMoved(TableColumnModelEvent e) {
                if (columnValue == -1) {
                    columnValue = e.getFromIndex();
                }
                columnNewValue = e.getToIndex();
            }

            @Override
            public void columnMarginChanged(ChangeEvent e) {
            }

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

        table.getTableHeader().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if ( columnValue != -1 &&
                     (columnValue < NUMBER_OF_FROZEN_COLUMNS || columnNewValue < NUMBER_OF_FROZEN_COLUMNS)
                   ) {
                    table.moveColumn(columnNewValue, columnValue);
                }
                columnValue = -1;
                columnNewValue = -1;
            }
        });
        return table;
    }

    public static void main(String[] args) {
        final JFrame frame = new FixedTableColumns("Unmovable columns example");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setLocationRelativeTo(null); // center
        frame.setVisible(true);
    }
}

Open in new window


By changing the value of
private int NUMBER_OF_FROZEN_COLUMNS = 2;

Open in new window

you can choose how much columns you want to be unmovable.
So in this example the columns "First name" and "Last name" can't be moved.
The other three can.
However, you can't eg. move the 3rd column to the 1st or the 2nd place since that would mean a move of the 1st or 2nd column which is impossible.
Avatar of jksung
jksung

ASKER

Thank you for your code, that was very informative for me!

However, I forgot to mention that in the case where there are many columns, when I scroll sideways through the columns, the frozen columns should not move, only the non-frozen columns can be scrolled through.  I cannot think of a way to do this other than using the two table model.
when I scroll sideways through the columns, the frozen columns should not move, only the non-frozen columns can be scrolled through.
Have a look at FixedColumnTable. I think it does what you want.

[Edit] It indeed does what you want. And in a very neat way.

Download the FixedColumnTable.java file.
And then this (slightly adapted) code does what you want:

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class FixedTableColumns extends JFrame {

    private int NUMBER_OF_FROZEN_COLUMNS = 2;
    private int columnValue = -1;
    private int columnNewValue = -1;

    public FixedTableColumns(String title) {
        super(title);
        getContentPane().setLayout(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(getTable());
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        FixedColumnTable fct = new FixedColumnTable(2, scrollPane);
    }

    private JTable getTable() {
        String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian", "Other", "Other 2", "Other 3"};
        Object[][] data = {
            {"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false), "test value", "test value", "test value"},
            {"John", "Doe", "Rowing", new Integer(3), new Boolean(true), "test value", "test value", "test value"},
            {"Sue", "Black", "Knitting", new Integer(2), new Boolean(false), "test value", "test value", "test value"},
            {"Jane", "White", "Speed reading", new Integer(20), new Boolean(true), "test value", "test value", "test value"},
            {"Joe", "Brown", "Pool", new Integer(10), new Boolean(false), "test value", "test value", "test value"}
        };

        JTable table = new JTable(data, columnNames);
        table.setFillsViewportHeight(true);
        table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
            @Override
            public void columnAdded(TableColumnModelEvent e) {
            }

            @Override
            public void columnRemoved(TableColumnModelEvent e) {
            }

            @Override
            public void columnMoved(TableColumnModelEvent e) {
                if (columnValue == -1) {
                    columnValue = e.getFromIndex();
                }
                columnNewValue = e.getToIndex();
            }

            @Override
            public void columnMarginChanged(ChangeEvent e) {
            }

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

        table.getTableHeader().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if ( columnValue != -1 &&
                     (columnValue < NUMBER_OF_FROZEN_COLUMNS || columnNewValue < NUMBER_OF_FROZEN_COLUMNS)
                   ) {
                    table.moveColumn(columnNewValue, columnValue);
                }
                columnValue = -1;
                columnNewValue = -1;
            }
        });

        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.getColumnModel().getColumn(0).setPreferredWidth(75);
        table.getColumnModel().getColumn(1).setPreferredWidth(75);
        return table;
    }

    public static void main(String[] args) {
        final JFrame frame = new FixedTableColumns("Unmovable columns example");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setLocationRelativeTo(null); // center
        frame.setVisible(true);
    }
}

Open in new window

Avatar of jksung

ASKER

Thank you!

One last thing, I am able to dynamically change the number of frozen columns by disposing of the entire frame, and recreating it, such as:


import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class FixedTableColumns extends JFrame {

    private int NUMBER_OF_FROZEN_COLUMNS = 0;
    private static int NUMBER_OF_UNMOVABLE_COLUMNS = 2;
    private int columnValue = -1;
    private int columnNewValue = -1;
    private FixedColumnTable fct;
    private JTable table;
    private static FixedTableColumns frame;

    public FixedTableColumns(String title, int numUnmoveable) {
        super(title);
        getContentPane().setLayout(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(getTable());
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        NUMBER_OF_UNMOVABLE_COLUMNS = numUnmoveable;
        fct = new FixedColumnTable(NUMBER_OF_UNMOVABLE_COLUMNS, scrollPane);
    }

    private JTable getTable() {
        String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian", "Other", "Other 2", "Other 3"};
        Object[][] data = {
            {"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false), "test value", "test value", "test value"},
            {"John", "Doe", "Rowing", new Integer(3), new Boolean(true), "test value", "test value", "test value"},
            {"Sue", "Black", "Knitting", new Integer(2), new Boolean(false), "test value", "test value", "test value"},
            {"Jane", "White", "Speed reading", new Integer(20), new Boolean(true), "test value", "test value", "test value"},
            {"Joe", "Brown", "Pool", new Integer(10), new Boolean(false), "test value", "test value", "test value"}
        };

        //JTable table = new JTable(data, columnNames);
        table = new JTable(data, columnNames);
        table.setFillsViewportHeight(true);
        table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
            @Override
            public void columnAdded(TableColumnModelEvent e) {
            }

            @Override
            public void columnRemoved(TableColumnModelEvent e) {
            }

            @Override
            public void columnMoved(TableColumnModelEvent e) {
                if (columnValue == -1) {
                    columnValue = e.getFromIndex();
                }
                columnNewValue = e.getToIndex();
            }

            @Override
            public void columnMarginChanged(ChangeEvent e) {
            }

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

        table.getTableHeader().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if ( columnValue != -1 &&
                     (columnValue < NUMBER_OF_FROZEN_COLUMNS || columnNewValue < NUMBER_OF_FROZEN_COLUMNS)
                   ) {
                    table.moveColumn(columnNewValue, columnValue);
                }
                columnValue = -1;
                columnNewValue = -1;
            }
        });

        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.getColumnModel().getColumn(0).setPreferredWidth(75);
        table.getColumnModel().getColumn(1).setPreferredWidth(75);
        return table;
    }
   
    private static void createTestFrame() {
            JFrame mButtonFrame = new JFrame();
            mButtonFrame.setLayout(new GridBagLayout());
            final JTextField mTextField = new JTextField();
            JButton mButton = new JButton("Set Frozen Columns");
            mButton.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent e) {
                        
                        System.out.println("Change the number of frozen columns here. " + mTextField.getText());
                        int numUnmoveable = Integer.parseInt(mTextField.getText());
                        
                        //dispose of the frame and recreate it with the new number of unmoveable columns
                        frame.dispose();
                        frame = new FixedTableColumns("Unmovable columns example", numUnmoveable);
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setSize(400, 300);
                    frame.setLocationRelativeTo(null); // center
                    frame.setVisible(true);
                  }
            });
            mTextField.setPreferredSize(mButton.getPreferredSize());
            mButtonFrame.add(mButton);
            mButtonFrame.add(mTextField);
            mButtonFrame.pack();
            mButtonFrame.setVisible(true);
      }

    public static void main(String[] args) {
          frame = new FixedTableColumns("Unmovable columns example", 2);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setLocationRelativeTo(null); // center
        frame.setVisible(true);
       
        //create the test widget, which will dispose of the frame and recreate it with the new number of unmoveable columns in the listener
        createTestFrame();
    }
}

Would this be possible without disposing of the entire frame, such as by recreating only the FixedColumnTable in the frame, and somehow updating the frame to show the new FixedColumnTable instead of disposing of the frame first?

I tried something like

                  public void actionPerformed(ActionEvent e) {
                        
                        System.out.println("Change the number of frozen columns here. " + mTextField.getText());
                        int numUnmoveable = Integer.parseInt(mTextField.getText());
                        
                        //dispose of the frame and recreate it with the new number of unmoveable columns
                        //frame.dispose();
                        //frame = new FixedTableColumns("Unmovable columns example", numUnmoveable);
                    //frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    //frame.setSize(400, 300);
                    //frame.setLocationRelativeTo(null); // center
                    //frame.setVisible(true);
                    System.out.println("Component Count: " + frame.getContentPane().getComponentCount());
                    frame.getContentPane().removeAll();
                    JScrollPane scrollPane = new JScrollPane(frame.getTable());
                    frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
                    frame.fct = new FixedColumnTable(NUMBER_OF_UNMOVABLE_COLUMNS, scrollPane);
                    frame.validate();
                    frame.repaint();
                  }

but it does not update the frame.
ASKER CERTIFIED SOLUTION
Avatar of zzynx
zzynx
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of jksung

ASKER

Yep, the code works great, thank you so much!
You're welcome.
Thanx 4 axxepting.