Компоненты, зависимые от Swing, и система событий

Мне нужно создать сложный графический интерфейс на JAVA с помощью Swing (на данный момент у меня около 80 классов). Графическая часть приложения разделена следующим образом: первая серия вкладок (например, «Управление», «Администрирование», «Конфигурация»), затем второй уровень (например, «Пользователь», «Группа», «Игра» ). На данный момент у меня два уровня обучения (по одному на каждый уровень вкладок). Следующий уровень — это JPanel, которая управляет бизнес-объектом (весь мой графический интерфейс построен вокруг моей бизнес-модели), на этом уровне есть 2 типа JPanel: кто управляет простыми объектами (например, «Пользователь», «Категория», «Игра»). ", "Уровень") и те, которые управляют объектами "составного первичного ключа" (например, "User_Game", которые представляют форму таблицы с двойной записью для каждого игрового уровня для всех пользователей). Мой второй уровень вкладок может содержать несколько JPanel. Когда моя JPanel управляет одним объектом, состоящим из JTable и двух кнопок (Добавить и Удалить), на которые я помещаю события, если нет, то это простой JTable. Когда у меня есть внешние ключи (например, «Группа» для «Пользователь» и «Категория» для «Игра» или «Уровень» для «User_Game»), это JComboBox, который берет свою информацию непосредственно из JTableModel. Когда дело доходит до управления объектом JTable для «составного первичного ключа», столбцы и строки также напрямую зависят от моделей (например, «Игра» и «Пользователь», «User_Game»). Каждый из них имеет свою собственную модель JTable, которая имеет дело с уровнем сохраняемости (Hibernate для информации) и другой TableModel. Для управления изменениями (такими как добавление, изменение или удаление «Пользователя») я использую следующий код:

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

И следующее действие:

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;

    }

}

Этот код работает хорошо, данные хорошо сохраняются. Затем я добавляю этот код для обновления зависимых компонентов:

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 перестраивает содержимое JTable (вызывает super.fireTableDataChanged и fireTableStructureChanged), JTable.repaint сбрасывает визуализаторы, и он работает для Combobox (внешние ключи) и хорошо обновляет заголовок в таблицах с двойной записью, но он не может добавить или удалять столбцы или строки в таблицах с двойной записью. Более того, я вижу более высокую задержку, если есть малейшее изменение.

Мой вопрос прост: как вы управляете взаимозависимыми компонентами?

За вашу помощь, заранее, спасибо.

Изменить: вот пример 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();
    }
}

Пример 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)
            {

            }
        }
    }
}

Пример таблицы:

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

}

Надеюсь, это поможет вам понять :/


person GlinesMome    schedule 21.08.2012    source источник
comment
слишком ленив, чтобы взглянуть на это более, чем поверхностно, поэтому просто пара комментариев: а) никогда-никогда (как в реально никогда) запускать событие от имени модели, это ее собственная основная ответственность . б) никогда (в той же степени, что и выше :) необходимости вызывать перерисовку в представлении модели - если это что-то исправляет, модель неправильно уведомляет своих слушателей в) в реализации редактора, не повторяйте угадать намерение клиента - если отредактированное значение недействительно, вернуть false, не более г) реализовать постоянно отключенное действие бесполезно - клиенты с хорошим поведением не должны его выполнять   -  person kleopatra    schedule 21.08.2012


Ответы (1)


Ваш TabCellListener мне незнаком. Ваш TableCellEditor не должен не взаимодействовать с TableModel напрямую. Он должен реализовать getTableCellEditorComponent() и getCellEditorValue(), как показано в этом примере. Когда редактор завершит работу, модель будет иметь новое значение. Ваш TableModel должен обрабатывать постоянство. Более одного представления могут прослушивать один TableModel через TableModelListener.

Приложение: Ваши CellEditor, TextColumnEditor, вероятно, не должны вызывать fireEditingCanceled(). Нажатия Escape должно быть достаточно, чтобы отменить редактирование, как показано в этом примере. Вы также можете просмотреть соответствующий учебный раздел и пример.

person trashgod    schedule 21.08.2012
comment
Привет, спасибо за ваш ответ, я добавляю код, надеюсь, это поможет вам лучше понять проблему. Видите ли, мой TableCellEditor не взаимодействует напрямую с моей TableModel, так что я хорошо разбираюсь в этой части. Я попытаюсь заменить свой TabCellListener на TableModelListener. - person GlinesMome; 21.08.2012
comment
Я подробно изложил выше, но я до сих пор не понимаю проблемы; sscce от вас может помочь. Вне контекста, я не понимаю других ваших выводов. - person trashgod; 21.08.2012
comment
Я усердно работал, я перестроил всю систему событий с момента моего первого поста, но это не проблема, количество сгенерированных событий было разделено на 100, поэтому мое приложение работает значительно быстрее. Спасибо за это. Моя последняя проблема: когда я пытаюсь добавить, удалить или переименовать столбец, модификация не появляется. - person GlinesMome; 22.08.2012