title: Swing dependent components and event system
body: <p>I have to build a complex GUI in JAVA with Swing (for the moment I have near 80 classes).
  The graphic partv of the application is split as follows: a first series of tabs (eg "Management", "Administration", "Configuration"), then a second level (for example, "User", "Group", "Game"). For now I'm two grade levels (one for each level of tabs). The next level is a JPanel that manages a business object (my whole GUI is built around my business model), at this level there are 2 type of JPanel: who manages simple objects (eg, "User", "Category" , "Game", "Level") and those which manages objects "composite primary key" (eg "User_Game" which represent the form of a double-entry table for each game level for all users).
  My second level of tabs can contain multiple JPanel.
  When my JPanel manages a single object is composed of a JTable and two buttons (Add and Remove) on which I put events, if not it is a simple JTable. When I have foreign keys (eg "Group" for "User" and "Category" to "Game" or "Level" to "User_Game") it is a JComboBox that takes its information directly from JTableModel. When it comes to managing a JTable object to "composite primary key" the columns and rows also directly dependent models (eg "Game" and "User" "User_Game").
  Each has its own JTable model that deals with the persistence layer (Hibernate for information) and other TableModel.
  To manage changes (such as adding, modifying or deleting a "User") I use the code below:</p>
  
  <pre><code>import java.awt.event.*;
  import javax.swing.*;
  import java.beans.*;
  
  /*
   *  This class listens for changes made to the data in the table via the
   *  TableCellEditor. When editing is started, the value of the cell is saved
   *  When editing is stopped the new value is saved. When the oold and new
   *  values are different, then the provided Action is invoked.
   *
   *  The source of the Action is a TableCellListener instance.
   */
  public class TabCellListener implements PropertyChangeListener, Runnable
  {
      private JTable table;
      private Action action;
  
      private int row;
      private int column;
      private Object oldValue;
      private Object newValue;
  
      /**
       *  Create a TableCellListener.
       *
       *  @param table   the table to be monitored for data changes
       *  @param action  the Action to invoke when cell data is changed
       */
      public TabCellListener(JTable table, Action action)
      {
          this.table = table;
          this.action = action;
          this.table.addPropertyChangeListener( this );
          this.table.getModel().addTableModelListener(new ModelListenerTableGui(this.table, this.action));
      }
  
      /**
       *  Create a TableCellListener with a copy of all the data relevant to
       *  the change of data for a given cell.
       *
       *  @param row  the row of the changed cell
       *  @param column  the column of the changed cell
       *  @param oldValue  the old data of the changed cell
       *  @param newValue  the new data of the changed cell
       */
      private CellListenerTableGui(JTable table, int row, int column, Object oldValue, Object newValue)
      {
          this.table = table;
          this.row = row;
          this.column = column;
          this.oldValue = oldValue;
          this.newValue = newValue;
      }
  
      /**
       *  Get the column that was last edited
       *
       *  @return the column that was edited
       */
      public int getColumn()
      {
          return column;
      }
  
      /**
       *  Get the new value in the cell
       *
       *  @return the new value in the cell
       */
      public Object getNewValue()
      {
          return newValue;
      }
  
      /**
       *  Get the old value of the cell
       *
       *  @return the old value of the cell
       */
      public Object getOldValue()
      {
          return oldValue;
      }
  
      /**
       *  Get the row that was last edited
       *
       *  @return the row that was edited
       */
      public int getRow()
      {
          return row;
      }
  
      /**
       *  Get the table of the cell that was changed
       *
       *  @return the table of the cell that was changed
       */
      public JTable getTable()
      {
          return table;
      }
  //
  //  Implement the PropertyChangeListener interface
  //
      @Override
      public void propertyChange(PropertyChangeEvent e)
      {
          //  A cell has started/stopped editing
  
          if ("tableCellEditor".equals(e.getPropertyName()))
          {
              if (table.isEditing())
                  processEditingStarted();
              else
                  processEditingStopped();
          }
      }
  
      /*
       *  Save information of the cell about to be edited
       */
      private void processEditingStarted()
      {
          //  The invokeLater is necessary because the editing row and editing
          //  column of the table have not been set when the "tableCellEditor"
          //  PropertyChangeEvent is fired.
          //  This results in the "run" method being invoked
  
          SwingUtilities.invokeLater( this );
      }
      /*
       *  See above.
       */
      @Override
      public void run()
      {
          row = table.convertRowIndexToModel( table.getEditingRow() );
          column = table.convertColumnIndexToModel( table.getEditingColumn() );
          oldValue = table.getModel().getValueAt(row, column);
          newValue = null;
      }
  
      /*
       *  Update the Cell history when necessary
       */
      private void processEditingStopped()
      {
          newValue = table.getModel().getValueAt(row, column);
  
          //  The data has changed, invoke the supplied Action
  
          if ((newValue == null &amp;&amp; oldValue != null) || (newValue != null &amp;&amp; !newValue.equals(oldValue)))
          {
              //  Make a copy of the data in case another cell starts editing
              //  while processing this change
  
              CellListenerTableGui tcl = new CellListenerTableGui(
                  getTable(), getRow(), getColumn(), getOldValue(), getNewValue());
  
              ActionEvent event = new ActionEvent(
                  tcl,
                  ActionEvent.ACTION_PERFORMED,
                  "");
              action.actionPerformed(event);
          }
      }
  }
  </code></pre>
  
  <p>And the following action:</p>
  
  <pre><code>import java.awt.event.ActionEvent;
  import java.beans.PropertyChangeListener;
  
  import javax.swing.Action;
  
  public class UpdateTableListener&lt;N&gt; extends AbstractTableListener implements Action
  {
      protected boolean enabled;
      public UpdateTableListener(AbstractTableGui&lt;N&gt; obs)
      {
          super(obs);
          this.enabled = true;
      }
  
      @Override
      public void actionPerformed(ActionEvent e)
      {
          if (null != e &amp;&amp; e.getSource() instanceof CellListenerTableGui)
          {
              TabCellListener tcl = (TabCellListener)e.getSource();
  
              this.obs.getModel().setValueAt(tcl.getNewValue(), tcl.getRow(), tcl.getColumn());
              int sel = this.obs.getModel().getNextRequiredColumn(tcl.getRow());
  
              if (sel == -1)
                  this.obs.getModel().save(tcl.getRow());
          }
      }
  
      @Override
      public void addPropertyChangeListener(PropertyChangeListener arg0)
      {
      }
  
      @Override
      public Object getValue(String arg0)
      {
          return null;
      }
  
      @Override
      public boolean isEnabled()
      {
          return this.enabled;
      }
  
      @Override
      public void putValue(String arg0, Object arg1)
      {
  
      }
  
      @Override
      public void removePropertyChangeListener(PropertyChangeListener arg0)
      {
  
      }
  
      @Override
      public void setEnabled(boolean arg0)
      {
          this.enabled = arg0;
  
      }
  
  }
  </code></pre>
  
  <p>This code works well, data are well persisted.
  Then I add this code to refresh dependent components:</p>
  
  <pre><code>import java.awt.event.ActionEvent;
  import java.beans.PropertyChangeListener;
  
  import javax.swing.Action;
  
  public class ChangeTableListener implements Action
  {
  
      protected AbstractTableGui table;
  
      public ChangeTableListener(AbstractTableGui table)
      {
          this.table = table;
      }
  
      @Override
      public void actionPerformed(ActionEvent arg0)
      {
          this.table.getModel().fireTableDataChanged();
          this.table.repaint();
      }
  
      @Override
      public void addPropertyChangeListener(PropertyChangeListener arg0)
      {
      }
  
      @Override
      public Object getValue(String arg0)
      {
          return null;
      }
  
      @Override
      public boolean isEnabled()
      {
          return false;
      }
  
      @Override
      public void putValue(String arg0, Object arg1)
      {
      }
  
      @Override
      public void removePropertyChangeListener(PropertyChangeListener arg0)
      {
      }
  
      @Override
      public void setEnabled(boolean arg0)
      {
      }
  }
  </code></pre>
  
  <p>My TableModel.fireTableDataChanged rebuild JTable content (calle super.fireTableDataChanged and fireTableStructureChanged), JTable.repaint reset the Renderers, and it works for Combobox (forein keys) and it update well title on double-entry tables, but it can't add or delete columns or rows on double-entry tables.
  Moreover I see more high latency if there is the slightest change.</p>
  
  <p>My question is simple: how do you manage inter-dependent components?</p>
  
  <p>For your help,
  In advance,
  Thanks.</p>
  
  <p>Edit :
  Here an example of TableCellEditor.</p>
  
  <pre><code>import javax.swing.DefaultCellEditor;
  import javax.swing.JTextField;
  
  public class TextColumnEditor extends DefaultCellEditor
  {
  
      public TextColumnEditor()
      {
          super(new JTextField());
      }
  
      public boolean stopCellEditing()
      {
          Object v = this.getCellEditorValue();
          if(v == null || v.toString().length() == 0)
          {
              this.fireEditingCanceled();
              return false;
          }
          return super.stopCellEditing();
      }
  }
  </code></pre>
  
  <p>An example of TableModel :</p>
  
  <pre><code>import java.util.ArrayList;
  
  public class GroupModelTable extends AbstractModelTable&lt;Groups&gt;
  {
      protected GroupsService service;
  
      public GroupModelTable(AbstractTableGui&lt;Groups&gt; content)
      {
          super(content, new ArrayList&lt;String&gt;(), new ArrayList&lt;Groups&gt;());
          this.headers.add("Group");
          this.content.setModel(this);
          this.service = new GroupsService();
          this.setLines(this.service.search(new Groups()));
      }
  
      public Object getValueAt(int rowIndex, int columnIndex)
      {
          switch (columnIndex)
          {
              case 0:
                  return this.lines.get(rowIndex).getTitle();
              default:
                  return "";
          }
      }
  
      public void setValueAt(Object aVal, int rowIndex, int columnIndex)
      {
          switch (columnIndex)
          {
              case 0:
                  this.lines.get(rowIndex).setTitle(aVal.toString());
                  break;
              default:
                  break;
          }
      }
  
       @Override
       public Groups getModel(int line, int column)
       {
           return null;
       }
  
       @Override
       public Groups getModel(int line)
       {
           return this.lines.get(line);
       }
  
       public boolean isCellEditable(int row, int column)
       {
          return true;
       }
  
      @Override
      public GroupModelTableGui newLine()
      {
          this.lines.add(new Groups());
          return this;
      }
  
      @Override
      public int getNextRequiredColumn(int row)
      {
          Groups g = this.getModel(row);
          if (g != null &amp;&amp; g.getTitle() != null &amp;&amp; g.getTitle().length() &gt; 0)
              return -1;
  
          return 0;
      }
  
      @Override
      public void save(int row)
      {
          Groups g = this.getModel(row);
          if (g != null)
          {
              try
              {
                  if (g.getId() == null)
                      this.service.create(g);
                  else
                      this.service.update(g);
              }
              catch (Exception e)
              {
  
              }
          }
      }
  
      @Override
      public void removeRow(int row)
      {
          Groups g = this.getModel(row);
          if (g != null)
          {
              try
              {
                  if (g.getId() != null)
                      this.service.delete(g);
  
                  super.removeRow(row);
              }
              catch (Exception e)
              {
  
              }
          }
      }
  }
  </code></pre>
  
  <p>An example of Table :</p>
  
  <pre><code>public class GroupTable extends AbstractTable&lt;Groups&gt;
  {
      public GroupTable()
      {
          new GroupModelTableGui(this);
          new CellListenerTableGui(this.getContent(), new UpdateTableListenerGui&lt;Groups&gt;(this));
          this.getContent().getColumnModel().getColumn(0).setCellEditor(new TextColumnEditorGui());
      }
  
  }
  </code></pre>
  
  <p>I hope it will help you to understand :/</p>