使用多个自定义表模型,避免重复代码 [英] Working with several custom table models avoiding repetitive code

查看:79
本文介绍了使用多个自定义表模型,避免重复代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在一个项目中,我们有几个域类来建模业务数据。这些类是简单的POJO,我必须使用它们显示几个表。例如,考虑这个类:

I'm working in a project in which we have several domain classes to model business data. Those classes are simple POJO's and I have to display several tables using them. For example, consider this class:

public class Customer {

    private Long id;
    private Date entryDate;
    private String name;
    private String address;
    private String phoneNumber;

    public Customer(Long id, Date entryDate, String name, String address, String phoneNumber) {
        this.id = id;
        this.entryDate = entryDate;
        this.nombre = name;
        this.domicilio = address;
        this.telefono = phoneNumber;
    }

    // Getters and setters here
}

我创建了自己的表格模型,扩展自 AbstractTableModel ,以便直接使用客户类:

I have created then my own table model extending from AbstractTableModel in order to work directly with Customer class:

public class CustomerTableModel extends AbstractTableModel {

    private final List<String> columnNames;
    private final List<Customer> customers;

    public CustomerTableModel() {
        String[] header = new String[] {
            "Entry date",
            "Name",
            "Address",
            "Phone number"
        };
        this.columnNames = Arrays.asList(header);
        this.customers = new ArrayList<>();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch (columnIndex) {
            case 0: return Date.class;
            case 1: return String.class;
            case 2: return String.class;
            case 3: return String.class;
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Customer customer = getCustomer(rowIndex);
        switch (columnIndex) {
            case 0: return customer.getEntryDate();
            case 1: return customer.getName();
            case 2: return customer.getAddress();
            case 3: return customer.getPhoneNumber();
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (columnIndex < 0 || columnIndex >= getColumnCount()) {
            throw new ArrayIndexOutOfBoundsException(columnIndex);
        } else {
            Customer customer = getCustomer(rowIndex);
            switch (columnIndex) {
                case 0: customer.setEntryDate((Date)aValue); break;
                case 1: customer.setName((String)aValue); break;
                case 2: customer.setAddress((String)aValue); break;
                case 3: customer.setPhoneNumber((String)aValue); break;
            }
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }

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

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

    @Override
    public String getColumnName(int columnIndex) {
        return this.columnNames.get(columnIndex);
    }

    public void setColumnNames(List<String> columnNames) {
        if (columnNames != null) {
            this.columnNames.clear();
            this.columnNames.addAll(columnNames);
            fireTableStructureChanged();
        }
    }

    public List<String> getColumnNames() {
        return Collections.unmodifiableList(this.columnNames);
    }

    public void addCustomer(Customer customer) {
        int rowIndex = this.customers.size();
        this.customers.add(customer);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void addCustomers(List<Customer> customerList) {
        if (!customerList.isEmpty()) {
            int firstRow = this.customers.size();
            this.customers.addAll(customerList);
            int lastRow = this.customers.size() - 1;
            fireTableRowsInserted(firstRow, lastRow);
        }
    }

    public void insertCustomer(Customer customer, int rowIndex) {
        this.customers.add(rowIndex, customer);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void deleteCustomer(int rowIndex) {
        if (this.customers.remove(this.customers.get(rowIndex))) {
            fireTableRowsDeleted(rowIndex, rowIndex);
        }
    }

    public Customer getCustomer(int rowIndex) {
        return this.customers.get(rowIndex);
    }

    public List<Customer> getCustomers() {
        return Collections.unmodifiableList(this.customers);
    }

    public void clearTableModelData() {
        if (!this.customers.isEmpty()) {
            int lastRow = customers.size() - 1;
            this.customers.clear();
            fireTableRowsDeleted(0, lastRow);
        }
    }
}

到现在为止一切都很好。但是这种方法至少有两个问题:

Until now everything is just fine. However this approach has at least two problems:


  1. 因为我必须为每个类实现一个表模型,所以我将生成很多重复的代码基本上做三件事:定义一个合适的表头,向/从底层结构(列表)添加/删除对象,覆盖 setValueAt() getValueAt()使用用户定义对象的方法。

  1. Since I have to implement one table model per class, then I'll generate a lot of repetitive code to essentially do three things: define an appropriate table header, add/remove objects to/from an underlying structure (list), override both setValueAt() and getValueAt() methods to work with user-defined objects.

假设我有相同的客户列表,但我必须在两个不同的表中,使用不同的标题或数据。我必须子类化我的表模型并覆盖它需要覆盖的任何内容才能满足此要求。它根本不会很优雅。

Let's say I have the very same list of Customers but I have to present this in two different tables, with different header or data. I would have to subclass my table model and override whatever it needs to be overriden in order to fulfill this requirement. It doesn't feel elegant at all.

问题:有什么方法可以摆脱样板代码使我的表模型灵活可重用?

Question: Is there some way to get rid of boilerplate code making my table model flexible and reusable?

推荐答案

像其他Swing模型一样(即:DefaultComboBoxModel DefaultListModel )我们可以使用泛型为了创建灵活且可重复使用的表模型,还提供了一个API来处理用户定义的POJO。

Like other Swing models (i.e.: DefaultComboBoxModel, DefaultListModel) we can use Generics in order to create a flexible and reusable table model, also providing an API to work with user-defined POJO's.

此表模型具有以下特殊功能功能:

This table model will have the following special features:


  • 它从 AbstractTableModel 扩展,以利用表模型事件处理。

  • 与上面显示的 CustomerTableModel 不同,此表模型必须是abstrac t因为它不能覆盖 getValueAt()方法:仅仅因为我们不知道这个表模型将处理的类或数据类型,覆盖上述方法的任务是留给子类。

  • 它从 AbstractTableModel setValueAt()实现>。这是有道理的,因为 isCellEditable()也从该类继承而且始终返回 false

  • getColumnClass()的默认实现也是继承的,并且始终返回 Object.class

  • It extends from AbstractTableModel to take advantage of table model events handling.
  • Unlike CustomerTableModel shown above, this table model has to be abstract because it must not override getValueAt() method: simply because we don't know the class or data type this table model will handle, the task to override the aforementioned method is left to the subclasses.
  • It inherits empty setValueAt() implementation from AbstractTableModel. It makes sense because isCellEditable() is also inherited from that class and always returns false.
  • Default implementation of getColumnClass() is also inherited and always returns Object.class.

根据我们的要求,这些功能使这个表模型非常容易实现:

These features make this table model really easy-to-implement depending on our requirements:


  • 如果我们需要显示一个只读表,那么我们必须重写2个方法top: getValueAt() getColumnClass()(推荐这最后一个但不是强制性的。)

  • 如果我们的表需要可编辑,那么我们必须覆盖4个方法top :上面提到的两个加上 isCellEditable() setValueAt()

  • If we need to display a read-only table, then we have to override 2 methods top: getValueAt() and getColumnClass() (this last one is recommended but not mandatory).
  • If our table needs to be editable, then we have to override 4 methods top: the two mentioned above plus isCellEditable() and setValueAt().

让我们来看看我们的表模型的代码:

Let's take a look to our table model's code:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
 * Abstract base class which extends from {@code AbstractTableModel} and 
 * provides an API to work with user-defined POJO's as table rows. Subclasses 
 * extending from {@code DataObjectTableModel} must implement 
 * {@code getValueAt(row, column)} method. 
 * <p />
 * By default cells are not editable. If those have to be editable then 
 * subclasses must override both {@code isCellEditable(row, column)} and 
 * {@code setValueAt(row, column)} methods.
 * <p />
 * Finally, it is not mandatory but highly recommended to override 
 * {@code getColumnClass(column)} method, in order to return the appropriate 
 * column class: default implementation always returns {@code Object.class}.
 * 
 * @param <T> The class handled by this TableModel.
 * @author dic19
 */
public abstract class DataObjectTableModel<T> extends AbstractTableModel {

    private final List<String> columnNames;
    private final List<T> data;

    public DataObjectTableModel() {
        this.data = new ArrayList<>();
        this.columnNames = new ArrayList<>();
    }

    public DataObjectTableModel(List<String> columnIdentifiers) {
        this();
        if (columnIdentifiers != null) {
            this.columnNames.addAll(columnIdentifiers);
        }
    }

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

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

    @Override
    public String getColumnName(int columnIndex) {
        return this.columnNames.get(columnIndex);
    }

    public void setColumnNames(List<String> columnNames) {
        if (columnNames != null) {
            this.columnNames.clear();
            this.columnNames.addAll(columnNames);
            fireTableStructureChanged();
        }
    }

    public List<String> getColumnNames() {
        return Collections.unmodifiableList(this.columnNames);
    }

    public void addDataObject(T dataObject) {
        int rowIndex = this.data.size();
        this.data.add(dataObject);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void addDataObjects(List<T> dataObjects) {
        if (!dataObjects.isEmpty()) {
            int firstRow = data.size();
            this.data.addAll(dataObjects);
            int lastRow = data.size() - 1;
            fireTableRowsInserted(firstRow, lastRow);
        }
    }

    public void insertDataObject(T dataObject, int rowIndex) {
        this.data.add(rowIndex, dataObject);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void deleteDataObject(int rowIndex) {
        if (this.data.remove(this.data.get(rowIndex))) {
            fireTableRowsDeleted(rowIndex, rowIndex);
        }
    }

    public void notifyDataObjectUpdated(T domainObject) {
        T[] elements = (T[])data.toArray();
        for (int i = 0; i < elements.length; i++) {
            if(elements[i] == domainObject) {
                fireTableRowsUpdated(i, i);
            }
        }
    }

    public T getDataObject(int rowIndex) {
        return this.data.get(rowIndex);
    }

    public List<T> getDataObjects(int firstRow, int lastRow) {
        List<T> subList = this.data.subList(firstRow, lastRow);
        return Collections.unmodifiableList(subList);
    }

    public List<T> getDataObjects() {
        return Collections.unmodifiableList(this.data);
    }

    public void clearTableModelData() {
        if (!this.data.isEmpty()) {
            int lastRow = data.size() - 1;
            this.data.clear();
            fireTableRowsDeleted(0, lastRow);
        }
    }
}

所以,拿这个表模型和 Customer 类,一个完整的实现将如下所示:

So, taking this table model and Customer class, a complete implementation will look like this:

String[] header = new String[] {"Entry date", "Name", "Address", "Phone number"};
DataObjectTableModel<Customer> model = new DataObjectTableModel<>(Arrays.asList(header)) {
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch (columnIndex) {
            case 0: return Date.class;
            case 1: return String.class;
            case 2: return String.class;
            case 3: return String.class;
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Customer customer = getDataObject(rowIndex);
        switch (columnIndex) {
            case 0: return customer.getEntryDate();
            case 1: return customer.getName();
            case 2: return customer.getAddress();
            case 3: return customer.getPhoneNumber();
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (columnIndex < 0 || columnIndex >= getColumnCount()) {
            throw new ArrayIndexOutOfBoundsException(columnIndex);
        } else {
            Customer customer = getDataObject(rowIndex);
            switch (columnIndex) {
                case 0: customer.setEntryDate((Date)aValue); break;
                case 1: customer.setName((String)aValue); break;
                case 2: customer.setAddress((String)aValue); break;
                case 3: customer.setPhoneNumber((String)aValue); break;
            }
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }
};

正如我们所看到的,在几行代码(LOC <50)中我们有一个完整的实施。

As we can see, in a few lines of code (LOC < 50) we have a complete implementation.

只要实体拥有公共getter和setter,它就会这样做。与JPA实现不同,此表模型不适用于反射,因此我们必须使用类的公共接口访问对象属性以实现 getValueAt() setValueAt() methods。

It does as long as entities have public getters and setters. Unlike JPA implementations this table model doesn't work with reflection so we'll have to access object properties using class' public interface to implement getValueAt() and setValueAt() methods.

否它没有。我们必须将结果集包装到域类中并使用上面提到的类提供的接口。

No it doesn't. We would have to wrap result sets into domain classes and use class' offered interface as mentioned above.

是的。再一次,使用类'提供的接口。例如,让我们使用 java.io.File 类,我们可以使用以下表模型实现:

Yes it does. Once again, using class' offered interface. For example let's take java.io.File class, we could have the following table model implementation:

String[] header = new String[] {
    "Name",
    "Full path",
    "Last modified",
    "Read",
    "Write",
    "Execute",
    "Hidden",
    "Directory"
};

DataObjectTableModel<File> model = new DataObjectTableModel<File>(Arrays.asList(header)) {
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch (columnIndex) {
            case 0: return String.class;
            case 1: return String.class;
            case 2: return Date.class;
            case 3: return Boolean.class;
            case 4: return Boolean.class;
            case 5: return Boolean.class;
            case 6: return Boolean.class;
            case 7: return Boolean.class;
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        File file = getDataObject(rowIndex);
        switch (columnIndex) {
            case 0: return file.getName();
            case 1: return file.getAbsolutePath();
            case 2: return new Date(file.lastModified());
            case 3: return file.canRead();
            case 4: return file.canWrite();
            case 5: return file.canExecute();
            case 6: return file.isHidden();
            case 7: return file.isDirectory();
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }
};

这篇关于使用多个自定义表模型,避免重复代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆