3

I have been thinking a lot to solve this mistake. But unfortunately I came to final cnclusion that I need help of professionals.

Please, copy,paste this code to see issue:

public class DateFormatDemo extends JFrame
{
    private JTable dataSearchResultTable;

    public DateFormatDemo()
    {
        JButton updateTable = new JButton("Update table");
        updateTable.setMaximumSize(updateTable.getPreferredSize());
        updateTable.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                updateMyTableModel();
            }
        });
        JPanel panel = new JPanel(new GridLayout(2, 1, 5, 10));
        panel.setPreferredSize(new Dimension(500, 300));
        panel.add(new JScrollPane(initDataSearchResultTable()));
        panel.add(updateTable);
        super.getContentPane().add(panel);
        super.pack();
        super.setDefaultCloseOperation(EXIT_ON_CLOSE);
        super.setVisible(true);
    }

    private JTable initDataSearchResultTable()
    {
        dataSearchResultTable = new JTable();
        // dataSearchResultTable.setAutoCreateColumnsFromModel(false);
        dataSearchResultTable.setSelectionBackground(new Color(0xaaaaff));
        dataSearchResultTable.setFillsViewportHeight(true);
        dataSearchResultTable.setRowSelectionAllowed(true);
        dataSearchResultTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        dataSearchResultTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

        return dataSearchResultTable;
    }

    void updateMyTableModel()
    {
        TableModel tableModel = dataSearchResultTable.getModel();
        TableColumnModel columnModel = dataSearchResultTable.getColumnModel();
        if (tableModel instanceof MyTableModel) {
            ((MyTableModel) tableModel).updateModel();
            this.initColumnWidths(tableModel, columnModel);
        } else {
            tableModel = new MyTableModel();
            dataSearchResultTable.setModel(tableModel);
            this.makeColumnsNotResizable(columnModel);
            this.initColumnWidths(tableModel, columnModel);
        }
    }

    private void makeColumnsNotResizable(TableColumnModel columnModel)
    {
        for (int i = 0; i < columnModel.getColumnCount(); i++) {
            if (i == 0 || i == 1) {
                columnModel.getColumn(i).setResizable(false);
            }
        }
    }

    private void initColumnWidths(TableModel tableModel, TableColumnModel columnModel)
    {
        TableColumn column = null;
        Component comp = null;
        int cellWidth = 0;
        int headerWidth = 0;
        TableCellRenderer headerRenderer = dataSearchResultTable.getTableHeader().getDefaultRenderer();

        for (int i = 0; i < columnModel.getColumnCount(); i++) {
            column = columnModel.getColumn(i);
            comp = headerRenderer.getTableCellRendererComponent(null, column.getHeaderValue(), false, false, -1, 0);
            headerWidth = comp.getPreferredSize().width;
            Class<?> columnClass = tableModel.getColumnClass(i);
            for (int j = 0; j < tableModel.getRowCount(); j++) {
                comp = dataSearchResultTable.getDefaultRenderer(columnClass).getTableCellRendererComponent(
                        dataSearchResultTable, tableModel.getValueAt(j, i), false, false, j, i);
                int width = comp.getPreferredSize().width;
                // we cache width of first row. And compare widths of next
                // rows with width of first.
                // If some row has greater width it becomes width of whole
                // row(unless header has greater width)
                if (cellWidth < width || j == 0) {
                    cellWidth = width;
                }
            }
            System.out
                    .println("columnClass=" + columnClass + ",headerWidth=" + headerWidth + ",cellWidth=" + cellWidth);

            if (headerWidth > cellWidth) {
                TableCellRenderer centeredRenderer = dataSearchResultTable.getDefaultRenderer(columnClass);
                if (centeredRenderer instanceof DefaultTableCellRenderer) {
                    ((DefaultTableCellRenderer) centeredRenderer).setHorizontalAlignment(SwingConstants.CENTER);
                    column.setCellRenderer(centeredRenderer);
                    column.setPreferredWidth(headerWidth);
                }
            } else {
                column.setPreferredWidth(cellWidth + 5);
            }
        }
    }

    class MyTableModel extends AbstractTableModel
    {
        private String[] columnNames = { "First Name", "Last Name", "Timestamp", "Number", "Vegetarian" };
        private Object[][] data = new Object[5][];

        void updateModel()
        {
            data = new Object[][] {
                    { "Vova", "KipokKipokKipokKipok", "2013-04-12 11:20:41", new Integer(5), new Boolean(true) },
                    { "Olia", "Duo", "2010-01-11 11:11:41", new Integer(3), new Boolean(false) },
                    { "Oksana", "Stack", "2012-04-12 11:20:41", new Integer(2), new Boolean(false) },
                    { "Petro", "White", "2010-04-12 11:20:21", new Integer(20), new Boolean(true) },
                    { "Ivan", "Brown", "2011-04-11 11:20:41", new Integer(10), new Boolean(true) } };
            fireTableDataChanged();
        }

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

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

        public String getColumnName(int col)
        {
            return columnNames[col];
        }

        public Object getValueAt(int row, int col)
        {
            if (data.length > 0 && data[0] != null) {
                return data[row][col];
            }
            return null;
        }

        /*
         * JTable uses this method to determine the default renderer/ editor for
         * each cell. If we didn't implement this method, then the last column
         * would contain text ("true"/"false"), rather than a check box.
         */
        public Class getColumnClass(int c)
        {
            Object valueAt = getValueAt(0, c);
            return valueAt == null ? Object.class : valueAt.getClass();
        }

        /*
         * Don't need to implement this method unless your table's editable.
         */
        public boolean isCellEditable(int row, int col)
        {
            // Note that the data/cell address is constant,
            // no matter where the cell appears onscreen.
            if (col < 2) {
                return false;
            } else {
                return true;
            }
        }

        /*
         * Don't need to implement this method unless your table's data can
         * change.
         */
        public void setValueAt(Object value, int row, int col)
        {
            if (data.length > 0 && data[0] != null) {
                data[row][col] = value;
                fireTableCellUpdated(row, col);
            }
        }

    }

    public static void main(String[] args) throws ParseException
    {
        new DateFormatDemo();
    }
}

Now please click twice on that big button called 'Update Table'. As you see column that should display checkbox as it holds boolean values do not do that but instead displays String true or false.

This code emulates my real workflow. So, how to update tablemodel to have boolean columns with checkbozes.

Thanks!

Volodymyr Levytskyi
  • 3,364
  • 9
  • 46
  • 83
  • 1
    nice demonstration of why value-based lookup of columnClass is evil :-) On startup, the value is null thus returning Object.class. – kleopatra Nov 06 '13 at 13:10

2 Answers2

3

take this as simple start point

import java.awt.*;
import java.util.Random;
import java.util.Vector;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;

public class Forum implements ListSelectionListener {

    private JFrame frame = new JFrame("Frame");
    private JPanel fatherCenter = new JPanel();
    private JScrollPane tableScroll = new JScrollPane();
    private MyTableModel tableModel;
    private JTable dialogTable;
    private ListSelectionModel lsDialog;
    private Color clr;
    private Color clr1;

    private void addData() {
        Runnable doRun1 = new Runnable() {
            @Override
            public void run() {
                tableModel.resetTable();
                Vector<String> tbl = new Vector<String>();
                Vector<Object> tbl1 = new Vector<Object>();
                Random rnd = new Random();
                tbl.add("Integer");
                tbl.add("Double");
                tbl.add("Boolean");
                tbl.add("Boolean");
                tbl.add("String");
                tableModel.setColumnNames(tbl);
                for (int row = 0; row < 30; row++) {
                    tbl1 = null;
                    tbl1 = new Vector<Object>();
                    tbl1.addElement(row + 1);
                    tbl1.addElement(rnd.nextInt(25) + 3.14);
                    tbl1.addElement((row % 3 == 0) ? false : true);
                    tbl1.addElement((row % 5 == 0) ? false : true);
                    if (row % 7 == 0) {
                        tbl1.add(("Canc"));
                    } else if (row % 6 == 0) {
                        tbl1.add(("Del"));
                    } else {
                        tbl1.add(("New"));
                    }
                    tableModel.addRow(tbl1);
                }
                addTableListener();
            }
        };
        SwingUtilities.invokeLater(doRun1);
    }

    private void addTableListener() {
        tableModel.addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent tme) {
                if (tme.getType() == TableModelEvent.UPDATE) {
                    System.out.println("");
                    System.out.println("Cell " + tme.getFirstRow() + ", "
                            + tme.getColumn() + " changed. The new value: "
                            + tableModel.getValueAt(tme.getFirstRow(),
                            tme.getColumn()));
                }
            }
        });
    }

    @Override
    public void valueChanged(ListSelectionEvent le) {
        int row = dialogTable.getSelectedRow();
        int col = dialogTable.getSelectedColumn();
        String str = "Selected Row(s): ";
        int[] rows = dialogTable.getSelectedRows();
        for (int i = 0; i < rows.length; i++) {
            str += rows[i] + " ";
        }
        str += "Selected Column(s): ";
        int[] cols = dialogTable.getSelectedColumns();
        for (int i = 0; i < cols.length; i++) {
            str += cols[i] + " ";
        }
        str += "Selected Cell: " + dialogTable.getSelectedRow() + ", " + dialogTable.getSelectedColumn();
        System.out.println(str);
        Object value = dialogTable.getValueAt(row, col);
        System.out.println(String.valueOf(value));
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Forum osFrame = new Forum();
            }
        });
    }

    public Forum() {
        tableModel = new MyTableModel();
        dialogTable = new JTable(tableModel) {
            private static final long serialVersionUID = 1L;

            @Override
            public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
                Component comp = super.prepareRenderer(renderer, row, column);
                JComponent jc = (JComponent) comp;//for Custom JComponent
                if (!isRowSelected(row)) {
                    int modelRow = convertRowIndexToModel(row);
                    boolean type = (Boolean) getModel().getValueAt(modelRow, 2);
                    boolean type1 = (Boolean) getModel().getValueAt(modelRow, 3);
                    comp.setForeground(Color.black);
                    if ((type) && (!type1)) {
                        comp.setBackground(clr1);
                    } else if ((!type) && (type1)) {
                        comp.setBackground(Color.orange);
                    } else if ((!type) || (!type1)) {
                        comp.setBackground(Color.red);
                    } else {
                        comp.setBackground(row % 2 == 0 ? getBackground() : getBackground().darker());
                    }
                    dialogTable.convertRowIndexToView(0);
                } else {
                    comp.setForeground(Color.blue);
                }
                if (!isCellEditable(row, column)) {
                    comp.setForeground(Color.red);
                    comp.setBackground(Color.magenta);
                }
                return comp;
            }
        };
        tableScroll = new JScrollPane(dialogTable, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        tableScroll.setBorder(null);
        dialogTable.getTableHeader().setReorderingAllowed(false);
        dialogTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        lsDialog = dialogTable.getSelectionModel();
        dialogTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
        dialogTable.setRowHeight(20);
        dialogTable.setRowMargin(2);
        dialogTable.setPreferredScrollableViewportSize(dialogTable.getPreferredSize());
        ListSelectionModel rowSelMod = dialogTable.getSelectionModel();
        //ListSelectionModel colSelMod = dialogTable.getColumnModel().getSelectionModel();
        rowSelMod.addListSelectionListener(this);
        //colSelMod.addListSelectionListener(this);        
        fatherCenter = new JPanel();
        fatherCenter.setLayout(new BorderLayout(10, 10));
        fatherCenter.add(tableScroll, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout(10, 10));
        frame.add(fatherCenter);
        frame.setPreferredSize(new Dimension(400, 660));
        frame.pack();
        frame.setLocation(150, 150);
        frame.setVisible(true);
        addData();
    }

    private class MyTableModel extends AbstractTableModel {

        private static final long serialVersionUID = 1L;
        private Vector<Vector<Object>> _data;
        private Vector<String> _colNames;
        private boolean[] _columnsVisible = {true, true, true, true, true};

        public MyTableModel() {
            _colNames = new Vector<String>();
            _data = new Vector<Vector<Object>>();
        }

        public MyTableModel(Vector<String> colnames) {
            _colNames = colnames;
            _data = new Vector<Vector<Object>>();
        }

        public void resetTable() {
            _colNames.removeAllElements();
            _data.removeAllElements();
        }

        public void setColumnNames(Vector<String> colNames) {
            _colNames = colNames;
            fireTableStructureChanged();
        }

        public void addRow(Vector<Object> data) {
            _data.add(data);
            fireTableRowsInserted(_data.size() - 1, _data.size() - 1);
        }

        public void removeRowAt(int row) {
            _data.removeElementAt(row);
            fireTableRowsDeleted(row - 1, _data.size() - 1);
        }

        @Override
        public int getColumnCount() {
            return _colNames.size();
        }

        @Override
        public Class<?> getColumnClass(int colNum) {
            switch (colNum) {
                case 0:
                    return Integer.class;
                case 1:
                    return Double.class;
                case 2:
                    return Boolean.class;
                case 3:
                    return Boolean.class;
                default:
                    return String.class;
            }
        }

        @Override
        public boolean isCellEditable(int row, int colNum) {
            switch (colNum) {
                case 2:
                    return false;
                default:
                    return true;
            }
        }

        @Override
        public String getColumnName(int colNum) {
            return _colNames.get(colNum);
        }

        @Override
        public int getRowCount() {
            return _data.size();
        }

        @Override
        public Object getValueAt(int row, int col) {
            Vector<Object> value = _data.get(row);
            return value.get(col);
        }

        @Override
        public void setValueAt(Object newVal, int row, int col) {
            Vector<Object> aRow = _data.elementAt(row);
            aRow.remove(col);
            aRow.insertElementAt(newVal, col);
            fireTableCellUpdated(row, col);
        }

        public void setColumnVisible(int index, boolean visible) {
            _columnsVisible[index] = visible;
            fireTableStructureChanged();
        }
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • Thanks for this great demo I shall cover it thorougly right now – Volodymyr Levytskyi Nov 06 '13 at 13:00
  • Please learn java naming conventions and stick to them +1 for **not** looking up the columnClass based on cell value :-) – kleopatra Nov 06 '13 at 13:05
  • @kleopatra lool DYM myTableModel and missing invokeLater in main class, right ............. ,,,, EDIT thank you, for notice to my description `take this as simple start point`, how easilly could be misspeled with an empty words – mKorbel Nov 06 '13 at 13:07
  • I don't know of naming convention that first letter is _. Second you populate tablemodel in separate non-edt thread, right? I follow jdbc tutorial on how to populate table from jdbc and that's why I used custom TableModel as described there. What is wrong here? – Volodymyr Levytskyi Nov 06 '13 at 13:34
  • @Volodymyr Levytskyi :-) too far from the end :-), 1. `that first letter is _` to avoiding `this.data`, better is for me `_data`, 2, `Second you populate ....` not true I love this form of `invokeLater` there isn't diff, by default I'm used `Runnable doRun1 = new Runnable() {`... `something inside` ... `SwingUtilities.invokeLater(doRun1);` this is the same as `SwingUtilities.invokeLater(new Runnable() {` – mKorbel Nov 06 '13 at 13:39
  • You use new Runnable.invokeLater to populate tableModel in separate thread or it is same thread on which user acts ? – Volodymyr Levytskyi Nov 06 '13 at 13:46
  • @Volodymyr Levytskyi everything wrapped inside `java.awt.EventQueue.invokeLater(new Runnable() {` / `SwingUtilities.invokeLater(new Runnable() {` is done on `EDT` – mKorbel Nov 06 '13 at 13:55
  • @Volodymyr Levytskyi this is about [JDBC, FileIO, Socket, MQ](http://stackoverflow.com/a/6060678/714968), here is about how to update `XxxTableModel` from `Runnable#Thread` (the simplest `Workers Thread` out of `EDT`) – mKorbel Nov 06 '13 at 13:59
  • Thanks. That demo is what I wanted . I meant if two `{ / SwingUtilities.invokeLater(new Runnable() {` threads run in parallel on EDT or they run sequentially on EDT. – Volodymyr Levytskyi Nov 06 '13 at 14:43
  • mKorbel, every your new demo I cover is invaluable source of knowledge – Volodymyr Levytskyi Nov 06 '13 at 15:14
  • So, it means that new thread is placed on EDT and will be executed only after all previous threads finish. What is difference between calling invokeLater on EDT and merely do the same(on EDT) without invokeLater? – Volodymyr Levytskyi Nov 06 '13 at 15:46
  • not, all updates to the already visible Swing GUI should be done on EDT, but Swing GUI freeze in the case that you provide long or hard action, freeze untill action is done, you performing that on EDT, Workers threads are for redirect those actions out of EDT, then SWing GUI is responsible during Workers Thread, updates from Workers Thread must be wrapped into invokeLater – mKorbel Nov 06 '13 at 16:04
  • Yes. I use SwingWorker and I do not use `invokeLater`. I asked if two Threads can run in parallel on EDT or they can run only sequentially if those threads are called by `invokeLater` method – Volodymyr Levytskyi Nov 06 '13 at 16:58
1

Your problem in getColumnClass method as you see you also return Object because of that you also get String columns.

You can determine that method in next way:

   @Override
    public Class<?> getColumnClass(int arg0) {
         return longValues[arg0].getClass();
    }

Or return Classes for your columns in another way. But you must to determine this Classes while cunstruct your model.

alex2410
  • 10,904
  • 3
  • 25
  • 41
  • Thanks for reply. But I forgot to remove longValues array. I do not have equialent of longValues array. Just remove this array – Volodymyr Levytskyi Nov 06 '13 at 12:15
  • In real case array data is replaced with cachedRowSet. So I don't know class before data is fetched from db. – Volodymyr Levytskyi Nov 06 '13 at 12:17
  • I think you know about all types of your columns and you can return it by index in switch – alex2410 Nov 06 '13 at 12:18
  • And data is fetched from db after user pressed that button for the second time. – Volodymyr Levytskyi Nov 06 '13 at 12:18
  • After second time user presses that button data array is initialised with data . So why class cannot be determined while data itself is displayed? – Volodymyr Levytskyi Nov 06 '13 at 12:22
  • Now I printed value in getCoumnCLass and it is determined therefore its class is determined. So the issue is how to tell jtable to update all its renderers or update its display. – Volodymyr Levytskyi Nov 06 '13 at 12:53
  • Because Columns initialized while you create your table, their renderer and editors also, You can try some trick with swaping models or table when you try to display new data in existed table. – alex2410 Nov 06 '13 at 12:53
  • @VolodymyrLevytskyi the return value of getColumnClass must not be changed during the lifetime of the model, at least not without firing a structureChanged – kleopatra Nov 06 '13 at 13:13
  • Yes, you caught me. After I replace cachedRowSet in myTableModel I call structureChanged and then once again I set up all column's widths and resizable state. – Volodymyr Levytskyi Nov 06 '13 at 13:37