GlinesMome
GlinesMome

Reputation: 1629

Swing dependent components and event system

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:

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 && oldValue != null) || (newValue != null && !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);
        }
    }
}

And the following action:

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;

import javax.swing.Action;

public class UpdateTableListener<N> extends AbstractTableListener implements Action
{
    protected boolean enabled;
    public UpdateTableListener(AbstractTableGui<N> obs)
    {
        super(obs);
        this.enabled = true;
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        if (null != e && 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;

    }

}

This code works well, data are well persisted. Then I add this code to refresh dependent components:

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)
    {
    }
}

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.

My question is simple: how do you manage inter-dependent components?

For your help, In advance, Thanks.

Edit : Here an example of TableCellEditor.

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();
    }
}

An example of TableModel :

import java.util.ArrayList;

public class GroupModelTable extends AbstractModelTable<Groups>
{
    protected GroupsService service;

    public GroupModelTable(AbstractTableGui<Groups> content)
    {
        super(content, new ArrayList<String>(), new ArrayList<Groups>());
        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 && g.getTitle() != null && g.getTitle().length() > 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)
            {

            }
        }
    }
}

An example of Table :

public class GroupTable extends AbstractTable<Groups>
{
    public GroupTable()
    {
        new GroupModelTableGui(this);
        new CellListenerTableGui(this.getContent(), new UpdateTableListenerGui<Groups>(this));
        this.getContent().getColumnModel().getColumn(0).setCellEditor(new TextColumnEditorGui());
    }

}

I hope it will help you to understand :/

Upvotes: 2

Views: 1730

Answers (1)

trashgod
trashgod

Reputation: 205805

Your TabCellListener is unfamiliar to me. Your TableCellEditor should not interact with the TableModel directly. It should implement getTableCellEditorComponent() and getCellEditorValue(), as shown in this example. When the editor concludes, the model will have the new value. Your TableModel should handle persistence. More than one view may listen to a single TableModel via TableModelListener.

Addendum: Your CellEditor, TextColumnEditor, probably shouldn't invoke fireEditingCanceled(). Pressing Escape should be sufficient to revert the edit, as shown in this example. You might also look at the related tutorial section and example.

Upvotes: 4

Related Questions