Close

Java Swing - Editable JTable Example

[Last Updated: Jul 4, 2018]

This example show how to use JTable default editors to edit cells. We are going to extend DisplayableObjectTableModel abstraction to make JTable editable.

Example

JTable can be made editable on cell level. As we are going to use our ObjectTableModel from previous examples, we have to override TableModel#isCellEditable() and TableModel#setValueAt() methods. Also our ObjectTableModel works with beans rather than rows of Object[], so we have to provide our beans a way to specify what rows and columns can be edited. For row level editing, our beans can implement following optional interface (if not implemented then all rows will be editable):

package com.logicbig.uicommon;

public interface Editable {
  boolean isEditable();
}

To specify what columns can be made editable, we are going to add an additional attribute 'editable' to our DisplayAs annotation:

package com.logicbig.uicommon;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DisplayAs {
  String value();
  int index();
  boolean editable() default false;
}

Now let's see how to integrate these new features in our TableModel abstractions.

ObjectTableModel

package com.logicbig.uicommon;

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

public abstract class ObjectTableModel<T> extends AbstractTableModel {
  private List<T> objectRows = new ArrayList<>();

  public List<T> getObjectRows() {
      return objectRows;
  }

  public void setObjectRows(List<T> objectRows) {
      this.objectRows = objectRows;
  }

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

  @Override
  public Object getValueAt(int rowIndex, int columnIndex) {
      T t = objectRows.get(rowIndex);
      return getValueAt(t, columnIndex);
  }

  @Override
  public boolean isCellEditable(int rowIndex, int columnIndex) {
      T t = objectRows.get(rowIndex);
      if(t instanceof Editable){
          if(!((Editable) t).isEditable()){
              return false;
          }
      }
      return isColumnEditable(columnIndex);
  }

  public void setValueAt(Object value, int row, int column) {
      if(!isCellEditable(row, column)){
          return;
      }
      T t = objectRows.get(row);
      if(setObjectFieldValue(t, column, value)){
          fireTableCellUpdated(row, column);
      }
  }

  public abstract  boolean isColumnEditable(int columnIndex);
  public abstract Object getValueAt(T t, int columnIndex);
  public abstract boolean setObjectFieldValue(T t, int column, Object value);

  @Override
  public abstract String getColumnName(int column);

  public abstract String getFieldName(int column);
}

DisplayableObjectTableModel

public class DisplayableObjectTableModel<T> extends ObjectTableModel<T> {
  private Map<Integer, ColumnInfo> columnInfoMap;

  public DisplayableObjectTableModel(Class<T> tClass) {
      init(tClass);
  }

  private void init(Class<T> tClass) {
      try {
          BeanInfo beanInfo = Introspector.getBeanInfo(tClass);
          this.columnInfoMap = new HashMap<>();
          for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
              Method m = pd.getReadMethod();
              DisplayAs displayAs = m.getAnnotation(DisplayAs.class);
              if (displayAs == null) {
                  continue;
              }
              ColumnInfo columnInfo = new ColumnInfo();
              columnInfo.displayName = displayAs.value();
              columnInfo.index = displayAs.index();
              columnInfo.editable = displayAs.editable();
              if (displayAs.editable()) {
                  columnInfo.setterMethod = pd.getWriteMethod();
              }
              columnInfo.getterMethod = m;
              columnInfo.propertyName = pd.getName();
              columnInfoMap.put(columnInfo.index, columnInfo);
          }

      } catch (Exception e) {
          throw new RuntimeException(e);
      }
  }

  @Override
  public Object getValueAt(T t, int columnIndex) {
      try {
          return columnInfoMap.get(columnIndex)
                  .getterMethod.invoke(t);
      } catch (Exception e) {
          throw new RuntimeException(e);
      }
  }

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

  @Override
  public String getColumnName(int column) {
      ColumnInfo columnInfo = columnInfoMap.get(column);
      if (columnInfo == null) {
          throw new RuntimeException("No column found for index " + column);
      }
      return columnInfo.displayName;
  }
  public Class<?> getColumnClass(int columnIndex) {
      ColumnInfo columnInfo = columnInfoMap.get(columnIndex);
      return columnInfo.getterMethod.getReturnType();
  }

  @Override
  public String getFieldName(int column) {
      ColumnInfo columnInfo = columnInfoMap.get(column);
      return columnInfo.propertyName;
  }

  @Override
  public boolean isColumnEditable(int columnIndex) {
      ColumnInfo columnInfo = columnInfoMap.get(columnIndex);
      return columnInfo.editable;
  }

  @Override
  public boolean setObjectFieldValue(T t, int column, Object value) {
      ColumnInfo columnInfo = columnInfoMap.get(column);
      try {
          if (columnInfo.setterMethod != null) {
              columnInfo.setterMethod.invoke(t, value);
              return true;
          }
      } catch (Exception e) {
          throw new RuntimeException(e);
      }
      return false;
  }
}

Client side example code.

public class Employee implements Editable {
  public static final String[] DEPT_LIST = {"Admin", "Account", "IT", "Sales"};
  public static final int DEPT_INDEX = 1;
  private String name;
  private String dept;
  private String phone;
  private Boolean fullTime;
  private LocalDate dateOfBirth;

  @DisplayAs(value = "Employee Name", index = 0, editable = true)
  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  @DisplayAs(value = "Department", index = DEPT_INDEX, editable = true)
  public String getDept() {
      return dept;
  }

  public void setDept(String dept) {
      this.dept = dept;
  }

  @DisplayAs(value = "Work Phone", index = 2, editable = true)
  public String getPhone() {
      return phone;
  }

  public void setPhone(String phone) {
      this.phone = phone;
  }

  @DisplayAs(value = "Full Time", index = 3, editable = true)
  public Boolean getFullTime() {
      return fullTime;
  }

  public void setFullTime(Boolean fullTime) {
      this.fullTime = fullTime;
  }

  @DisplayAs(value = "Date Of Birth", index = 4)//not editable
  public LocalDate getDateOfBirth() {
      return dateOfBirth;
  }

  public void setDateOfBirth(LocalDate dateOfBirth) {
      this.dateOfBirth = dateOfBirth;
  }

  @Override
  public boolean isEditable() {
      //all employees in Admin dept are not editable
      return !dept.equalsIgnoreCase("Admin");
  }
}
public class ExampleMain {
  public static void main(String[] args) {
      JFrame frame = createFrame();
      ObjectTableModel<Employee> tableModel = new DisplayableObjectTableModel<>(Employee.class);
      tableModel.setObjectRows(getEmployees());
      JTable table = new JTable(tableModel);
      initDeptComboBoxEditor(table);
      JScrollPane pane = new JScrollPane(table);
      frame.add(pane);
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
  }

  private static void initDeptComboBoxEditor(JTable table) {
      JComboBox comboBox = new JComboBox(Employee.DEPT_LIST);
      DefaultCellEditor editor = new DefaultCellEditor(comboBox);
      TableColumn column = table.getColumnModel().getColumn(Employee.DEPT_INDEX);
      column.setCellEditor(editor);
  }

  public static List<Employee> getEmployees() {
      final List<Employee> list = new ArrayList<>();
      for (int i = 1; i <= 20; i++) {
          Employee e = new Employee();
          e.setName(RandomUtil.getFullName());
          e.setPhone(Integer.toString(RandomUtil.getInt(111111111, 999999999)));
          e.setDept(RandomUtil.getAnyOf(Employee.DEPT_LIST));
          e.setFullTime(RandomUtil.getBoolean());
          e.setDateOfBirth(RandomUtil.getDate(1950, 2000));
          list.add(e);
      }
      return list;
  }

  private static JFrame createFrame() {
      JFrame frame = new JFrame("Editable JTable example");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setSize(new Dimension(600, 300));
      return frame;
  }
}

Output

All columns except for 'Date Of Birth' can be edited and all rows except for those having 'Admin' department can be edited.

Example Project

Dependencies and Technologies Used:

  • JDK 1.8
  • Maven 3.3.9

Editable JTable Example Select All Download
  • editable-table
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • ExampleMain.java
                • uicommon
                • util

    See Also