Thursday, June 20, 2013

How to share a data among two tables

In this post, I'll explain how to share one data among two table.

First, I will define a DataContainer class that holds all the data I want.

package jtbl_2tbl_abst;


/**
 *
 * @author Lee
 */
public class DataContainer {
    
    private Object[][] data = {
        {"Singed", "male", false},
        {"Akali", "female", false},
        {"Tristina", "female", true},
        {"Udyr", "male", false},
        {"Janna", "female", true}
    };
    
    private final String[] columnNames = {
        "Name", "Sex", "Vegetarin"
    };

    /**
     * @return the data
     */
    public Object[][] getData() {
        return data;
    }

    /**
     * @param data the data to set
     */
    public void setData(Object[][] data) {
        this.data = data;
    }
    
    public void setData(Object data, int row, int col) {
        this.data[row][col] = data; 
    }

    /**
     * @return the columnNames
     */
    public String[] getColumnNames() {
        return columnNames;
    }
}

Now I want MyTableModel that extends AbstractTableModel.
(otherwise I should redefine MyTableModel for each table.)

package jtbl_2tbl_abst;

import javax.swing.table.AbstractTableModel;

/**
 *
 * @author Lee
 */
public class MyTableModel extends AbstractTableModel {
    
    // holds column names and data from outer source
    private String[] columnNames;
    private Object[][] data;
    
    public MyTableModel(Object[][] data, String[] columnNames) {
        this.data = data;
        this.columnNames = columnNames;
    }
    

    // the explanation is ommitted since I've covered it in the last post
    // "Using AbstractTableModel"
    @Override
        public int getRowCount() {
            return data.length;
        }

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

        @Override
        public Object getValueAt(int row, int col) {
            return data[row][col];
        }
        
        @Override
        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }
        
        @Override
        public boolean isCellEditable(int row, int col) {
            return true;
        }
        
        @Override
        public void setValueAt(Object value, int row, int col) {
            data[row][col] = value;
            fireTableCellUpdated(row, col);
        }
    
}

now, let's define Table1 class

package jtbl_2tbl_abst;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import net.miginfocom.swing.MigLayout;

/**
 *
 * @author Lee
 */
public class Table1 extends JPanel implements TableModelListener{
    
    // JScrollPane that takes JTable
    private JScrollPane scrollPane;
    // This is table
    private JTable table1;
    // this holds data from DataContainer
    DataContainer container = new DataContainer();
    // this holds AbstractTableModel from MyTableModel
    private AbstractTableModel model;
    
    public Table1(DataContainer container) {
        super(new MigLayout());
        this.container = container;
        initComponents();
    }
    
    private void initComponents() {
        // initiate the table model
        model = new MyTableModel(container.getData(), container.getColumnNames());
        // initiate the table
        table1 = new JTable(model);
        // add TableModelListener so that we can change the data, and the 
        // changed data can be detected
        table1.getModel().addTableModelListener(this);
        scrollPane = new JScrollPane(table1);
        
        this.add(scrollPane);
    }

    @Override
    public void tableChanged(TableModelEvent e) {
        // get the first row that has been changed
        int row = e.getFirstRow();
        // get the column that has been changed
        int column = e.getColumn();
        // get the changed model of the table
        TableModel model = (TableModel)e.getSource();
        
        // tableChanged method throws -1 when theres no change of row and columns
        // but wants to update the whole table.
        // And that causes unexpected errors, so I put if(column >=0) to prevent that
        if(column >= 0 ) {
            String columnName = model.getColumnName(column);
            // get the changed data from the table model
            Object data = model.getValueAt(row, column);
            // apply the change to the container data
            container.setData(data, row, column);
        }
    }

    /**
     * @return the table1
     */
    public JTable getTable1() {
        return table1;
    }

    /**
     * @param table1 the table1 to set
     */
    public void setTable1(JTable table1) {
        this.table1 = table1;
    }

    /**
     * @return the model
     */
    public AbstractTableModel getModel() {
        return model;
    }

    /**
     * @param model the model to set
     */
    public void setModel(AbstractTableModel model) {
        this.model = model;
    }
}

Similarly, we need to define Table2. The explanation is omitted since Table2 is identical to Table1

package jtbl_2tbl_abst;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import net.miginfocom.swing.MigLayout;

/**
 *
 * @author Lee
 */
public class Table2 extends JPanel implements TableModelListener{

    private JScrollPane scrollPane;
    private JTable table2;
    private AbstractTableModel model;
    DataContainer container = new DataContainer();
    
    public Table2(DataContainer container) {
        super(new MigLayout());
        this.container = container;
        initComponents();
    }
    
    public void initComponents() {
        model = new MyTableModel(container.getData(), container.getColumnNames());
        table2 = new JTable(model);
        table2.getModel().addTableModelListener(this);
        
        scrollPane = new JScrollPane(table2);
        this.add(scrollPane);
    }
    
    @Override
    public void tableChanged(TableModelEvent e) {
        int row = e.getFirstRow();
        int column = e.getColumn();
        TableModel model = (TableModel)e.getSource();

        if(column >= 0 ) {
            String columnName = model.getColumnName(column);
            Object data = model.getValueAt(row, column);
            System.out.println("changed data: " + data);
            container.setData(data, row, column);
        }
    }

    /**
     * @return the table2
     */
    public JTable getTable2() {
        return table2;
    }

    /**
     * @param table2 the table2 to set
     */
    public void setTable2(JTable table2) {
        this.table2 = table2;
    }

    /**
     * @return the model
     */
    public AbstractTableModel getModel() {
        return model;
    }

    /**
     * @param model the model to set
     */
    public void setModel(AbstractTableModel model) {
        this.model = model;
    }
}

Now we need JFrame that holds the two tables.

package jtbl_2tbl_abst;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;

/**
 *
 * @author Lee
 */
public class Frame {
    
    // JFrame that holds Table1
    JFrame frame1  = new JFrame();
    // JFrame that holds Table2
    JFrame frame2 = new JFrame();
    // This button will open frame2 with Table2
    JButton opnBttn = new JButton("Open Table2");
    // This button will close frame2 updating the changed data
    JButton mdfBttn = new JButton("OK");
    // This button will Update frame2(table2) when table1 has been changed
    JButton udtBttn = new JButton("Update");
    DataContainer container = new DataContainer();
    Table1 table1Panel;
    Table2 table2Panel;
    
    public Frame() {
        initTable1();
        initTable2();
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Frame();
            }
        });
    }
    
    // initiate Table1
    public void initTable1() {
        // add ActionListener to opnBttn
        opnBttn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                opnBttnClicked(evt);
            }
        });
        
        // add ActionListener to udtBttn
        udtBttn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                udtBttnClicked(evt);
            }
        });
        
        // initiate Table1 with the sharing data
        table1Panel = new Table1(container);
        
        // setup frame1
        frame1.setLayout(new MigLayout());
        frame1.add(table1Panel, "span, growx, wrap");
        frame1.add(opnBttn);
        frame1.add(udtBttn);
        frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame1.pack();
        frame1.setVisible(true);
    }
    
    // initiate Table2
    public void initTable2() {
        // add ActionListener to mdfBttn
        mdfBttn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                mdfBttnClicked(evt);
            }
        });
        
        // initiate Table2 with the sharing data
        table2Panel = new Table2(container);
        
        // set up frame2
        frame2.setLayout(new MigLayout());
        frame2.add(table2Panel, "wrap");
        frame2.add(mdfBttn, "center");
        frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame2.pack();
    }

    // when opnBttn is clicked, frame2(table2) become visible
    public void opnBttnClicked(ActionEvent evt) {
        // clikcing the opnBttn when one is editing the table cell will cause 
        // data lose since the changed value is still in Editor, not in Renderer.
        // To send the changed value from Editor to Render, I need the following
        if (table1Panel.getTable1().isEditing()) {
            table1Panel.getTable1().getCellEditor().stopCellEditing();
        }
        
        // open frame2(table2)
        frame2.setVisible(true);
    }
    
    // when mdfBttn is clicked, frame2 is closed 
    public void mdfBttnClicked(ActionEvent evt) {
        // same as above
        if (table2Panel.getTable2().isEditing()) {
            table2Panel.getTable2().getCellEditor().stopCellEditing();
        }
        frame2.setVisible(false);
        // notify table1 that the data may have been changed
        table1Panel.getModel().fireTableDataChanged();
    }
    
    // when udtBttn is clicked, table2 refreshes 
    public void udtBttnClicked(ActionEvent evt) {
        if (table1Panel.getTable1().isEditing()) {
            table1Panel.getTable1().getCellEditor().stopCellEditing();
        }
        table2Panel.getModel().fireTableDataChanged();
    }
}

No comments:

Post a Comment