Jtable_Swing ( Part 4)


Trong phần 3, chúng ta đã biết cách để hiển thị dữ liệu lên trên bảng. Trong phần này, chúng ta sẽ học cách để sửa giá trị trong một ô của bảng.

Trở lại ví dụ của chúng ta mặc dù các ô trong cột Gender đều hiển thị một JComboBox, tuy nhiên chúng ta không thể thay đổi giá trị trong các ô này. Với những gì đang có, không ô nào của bảng trong ví dụ là có thể sửa được. Khi chúng ta nhấp chuột trên một ô, toàn bộ dòng đó sẽ được chọn thay vì chỉ một ô mà chúng ta muốn sửa đổi. Để thay đổi điều này, chúng ta phải cài đặt lại phương thức isCellEditable trong TableModel bởi vì cài đặt mặc định của phương thức này luôn trả về giá trị là false. Chúng ta thay đổi lớp TableValues như sau:

  1. public class TableValues extends AbstractTableModel{
  2.     public final static int FIRST_NAME = 0;
  3.     […]
  4.     public Object[[ values = {
  5.         […]
  6.     };
  7.     public int getRowCount() {
  8.         return values.length;
  9.     }
  10.     public int getColumnCount() {
  11.         return values[0].length;
  12.     }
  13.     public Object getValueAt(int rowIndex, int columnIndex) {
  14.         return values[rowIndex][columnIndex];
  15.     }
  16.     @Override
  17.     public String getColumnName(int column){
  18.         return columnNames[column];
  19.     }
  20.     @Override
  21.     public Class getColumnClass(int column){
  22.         […]
  23.     }
  24.     @Override
  25.     public boolean isCellEditable(int row, int column){
  26.         if(column == GENDER){
  27.             return true;
  28.         }
  29.         return false;
  30.     }
  31. }

Bằng việc sửa đổi như trên, các ô trong cột Gender giờ đây đã có thể sửa được. Tuy nhiên nếu chúng ta nhấp chuột đơn vào môt ô trong cột đó chúng ta lại có thêm chút ngạc nhiên bởi vì không có điều gì xảy ra ngoại trừ cả dòng đó được chọn. Trong khi đó, cái mà chúng ta mong chờ đó là có thể chọn một giá trị khác từ một JComboBox. Nếu chúng ta kích đúp chuột vào ô đó, một JTextField xuất hiện và trong nó là giá trị kiểu Boolean của ô Gender (true hoặc false), và chúng ta có thể sửa giá trị này trong JTextField giống như hình dưới đây:

Tại sao lại là một JTextField mà lại không phải là một JComboBox. Để giải thích điều này chúng ta phải nhớ rằng các ô trong bảng không thực sự chứa bất cứ một thành phần nào. Các ô chỉ đơn giản là được vẽ bởi các renderer và trong trường hợp này các ô trong cột Gender được vẽ như là các JComboBox. Quá trình sửa đổi giá trị trong các ô là một quá trình hoàn toàn tách rời riêng biệt và nó có thể được xử lý bởi một thành phần giống như là thành phần được vẽ bởi renderer. Ví dụ, thành phần hiển thị mặc định cho một JTable là một JLabel trong khi thành phần sửa đổi mặc định của nó là một JTextField và đây là lý do tại sao JTextField lại xuất hiện khi mà chúng ta chọn để sửa một ô trong cột Gender.

Bất kể là thành phần nào được sử dụng, mục tiêu cuối cùng vẫn là làm sao để cho giá trị trong một ô có thể thay đổi được. Tuy nhiên trong trường hợp của chúng ta giá trị sau khi được sửa đổi sẽ bị loại bỏ và lại quay trở về giá trị ban đầu. Để hiểu tại sao điều này lại xảy ra và chúng ta phải làm những gì, trước hết chúng ta hãy làm quen với khái niệm editor cho một ô và biết cách một JTable xử lý sự thay đổi giá trị trong các ô của nó như thế nào.

Editor cho một ô trong JTable

Giống như renderer điều khiển cách hiển thị giá trị của một ô, editor xử lý khi mà giá trị trong ô đó được sửa đổi. Editor có thể nói là phức tạp hơn so với renderer một chút nhưng nó so với renderer thì cũng có nhiều điểm tương đồng như sau:

  • Một editor có thể gán tới một hay nhiều đối tượng của lớp TableColumn
  • Một editor có thể gán tới một hay nhiều kiểu dữ liệu và sẽ được sử dụng để làm editor của các cột có kiểu dữ liệu đó trong trường hợp không có một editor nào được chỉ định trực tiếp để kết hợp với các cột đó.
  • Các thành phần hiển thị có thể được sử dụng trong quá trình chỉnh sửa, giống như cách mà chúng được sử dụng bởi các renderer. Trong thiết kế thực tế, một renderer và một editor cho một ô của bảng thường sử dụng chung một thành phần hiển thị.

Chúng ta có thể gán editor đến một đối tượng của TableColumn hoặc đến một kiểu dữ liệu bằng cách lần lượt sử dụng phương thức setCellEditor trong TableColumn hoặc phương thức setDefaultEditor trong JTable. Việc cài đặt interface TableCellEditor thì phức tạp hơn so với trường hợp cài đặt TableCellRenderer và để hiểu được các phương thức trong TableCellEditor, chúng ta hãy tìm hiểu editor tương tác với JTable như thế nào.

Khi một JTable phát hiện ra một thao tác nhấp chuột trên một ô, nó gọi phương thức isCellEditable trong TableModel. Nếu phương thức này trả về một giá trị là false thì khi đó có nghĩa là ô đó không thể sửa được và quá trình xử lý dừng ở đây, không làm thêm bất cứ gì khác nữa. Trong trường hợp phương thức này trả về giá trị là true thì sau đó JTable sẽ nhận diện editor của ô này và gọi phương thức isCellEditable của editor. Mặc dù TableModel và CellEditor đều có phương thức là isCellEditable, nhưng giữa chúng có một sự khác biệt. Phương thức trong TableModel chỉ lấy giá trị của chỉ số cột và chỉ số dòng làm tham số đầu vào, trong khi đó, phương thức trong CellEditor lấy tham số đầu vào là một đối tượng của EventObject biểu diễn cho sự kiện kích chuột. Chúng ta có thể sử dụng đối đượng này để lấy ra số lần kích chuột trong sự kiện. Một ô phải được kích đúp trước khi chúng ta có thể sửa nó, và chúng ta đã quan sát hiện tượng này trong cột Gender của ví dụ. Nói một cách khác phương thức isCellEditable trả về một giá trị là false khi mà số lần kích chuột là 1, và sẽ trả về là true nếu như số lần kích chuột lớn hơn 1.

Chúng ta có thể thực hiện sửa đổi giá trị cho một ô khi và chỉ khi cả hai phương thức isCellEditable trên đều trả về giá trị là true. Khi trường hợp này xảy ra, quá trình sửa đổi được khởi tạo bằng việc gọi phương thứcgetTableCellEditorComponent. Phương thức này gồm những tham số đầu vào sau:

  • Một tham chiếu đến JTable của ô đang được sửa
  • Một tham chiếu đến giá trị hiện thời của ô
  • Một biến boolean để xác định xem ô đó có được chọn hay không
  • Chỉ số hàng của ô đang được sửa
  • Chỉ số cột của ô đang được sửa

Các tham số ở trên tương tự với các tham số đầu vào trong phương thức getTableCellRendererComponent trong TableCellRenderer. Sự khác biệt duy nhất ở đây chính là phương thức getTableCellEditorComponent không có một tham số đầu vào kiểu boolean để chỉ ra ô đó có phải là một ô để nhập dữ liệu và đang được trỏ đến hay không, bởi vì cái này có thể tự suy diễn ra luôn là true khi mà ô đó đang ở trạng thái được sửa.

Trước khi trả về một tham chiếu đến thành phần hiển thị cho việc xử lý chỉnh sửa, phương thức getTableCellEditorComponent phải thực hiện khởi tạo sao cho giá trị thể hiện trên thành phần phải trùng khớp với giá trị hiện thời của ô đó. Giả sửu, chúng ta đang tạo một editor để cho phép người sử dụng chọn giá trị cho cột Gender là Male hoặc Female từ một JComboBox. Trong trường hợp đó, chúng ta cần phải khởi tạo JComboBox sao cho ban đầu giá trị được chọn trong nó phải giống với giá trị hiện thời của các ô trong cột Gender.

Sau khi thành phần tham chiếu đã được trả về từ phương thức getTableCellEditorComponent, JTable sẽ đặt kích thước và vị trí cho thành phần đó sao cho nó trực tiếp nằm đè trùng khớp lên ô đang được sửa. Điều này làm cho ta tưởng rằng ô đó đang thực sự ở trạng thái chỉnh sửa nhưng thực ra là các thao tác chỉnh sửa chỉ thực hiện trên một thành phần nằm phía trên cái ô đó.

Việc đặt thành phần dùng cho việc chỉnh sửa bên trên một ô cũng có nghĩa là sự kiện khởi tạo quá trình chỉnh sửa được truyền vào thành phần này. Ví dụ, trong trường hợp một editor dùng JComboBox cho việc chỉnh sửa thì sự kiện kích chuột được truyền vào JComboBox. Chính vì thế khi kích chuột vào một ô để chỉnh sửa cũng có nghĩa là JComboBox cũng nhận sự kiện này và nó sẽ sổ ra một cửa sổ với nhiều lựa chọn. Chúng ta chọn một trong số chúng cũng bằng thao tác kích chuột. Cuối cùng của quá trình này, khi phương thức shouldSelectCell của editor được truyền vào sự kiện kích chuột trên và trả về giá trị là true thì đây là lúc việc chỉnh sửa kết thúc.

Mỗi một editor cần phải cài đặt phương thức addCellEditorListener và removeCellEditorListener, và interface CellEditorListener có định nghĩa hai phương thức là editingStopped và editingCanceled. Trong thực tế, listener thường chính là bản thân JTable, nó sẽ được thông báo khi mà việc chỉnh sửa một ô dừng lại hoặc hủy bỏ. Thêm vào nữa, editor còn phải cài đặt hai phương thức nữa là cancelCellEditing và stopCellEditing. Hai phương thức này lần lượt gọi hai phương thức editingCanceled và editingStopped của listener đã đăng kí.

Một yêu cầu để kết thúc việc chỉnh sửa cho một ô có thể bắt nguồn từ JTable chứa ô đó hoặc từ ngay chính bản thân editor. Lấy ví dụ, giả sử chúng ta kích chuột vào một ô và bắt đầu chỉnh sửa giá trị của nó. Nếu sau đó chúng ta kích sang một ô thứ hai khác, thì JTable sẽ gọi phương thức stopCellEditing trong editor của ô thứ nhất trước khi nó khởi tạo quá trình chỉnh sửa cho ô thứ hai. Một editor hoàn toàn có thể dừng việc chỉnh sửa khi một sự kiện nào đó xảy ra để chỉ định việc chỉnh sửa đã hoàn tất. Cho ví dụ, khi sử dụng một JComboBox như một editor, nếu nó nhận một thông điệp từ ActionEvent thì nó ngầm định rằng đấy là dấu hiệu chúng ta vừa thực hiện chọn một giá trị và kết thúc quá trình chỉnh sửa. Tương tự như vậy, một JTextField nhận một tín hiệu để dừng việc chỉnh sửa đó chính là khi phím Enter được bấm.

Bất kể là các yêu cầu cho việc dừng chỉnh sửa được sinh ra ở đâu thì phương thức editingStopped của JTable vẫn cứ được gọi bởi vì nó thuộc về một CellEditorListener đã được đăng kí. Trong phương thức này, JTable gọi phương thứcgetCellEditorValue của editor để lấy về giá trị mới trong ô được chỉnh sửa sau đó truyền giá trị này vào phương thức setValueAt trong TableModel của JTable. Điều này có nghĩa rằng, giá trị mới của ô được lấy về từ editor và được gửi đến model để có thể được lưu trữ.

Trở về với ví dụ, giờ ta tạo một editor đặt tên là GenderEditor cho cột Gender. Editor này sử dụng một JComboBox để cho phép chúng ta có thể chọn một trong hai giá trị là Male hoặc Female trong cột Gender. Tạo lớpGenderEditor trong file GenderEditor.java như sau

  1. public class GenderEditor extends JComboBox implements TableCellEditor{
  2.     protected EventListenerList listenerList = new EventListenerList();
  3.     protected ChangeEvent changeEvent = new ChangeEvent(this);
  4.     public GenderEditor(){
  5.         super();
  6.         addItem(“Male”);
  7.         addItem(“Female”);
  8.         addActionListener(new ActionListener() {
  9.             public void actionPerformed(ActionEvent e) {
  10.                 fireEditingStopped();
  11.             }
  12.         });
  13.     }
  14.     public void addCellEditorListener(CellEditorListener listener){
  15.         listenerList.add(CellEditorListener.class, listener);
  16.     }
  17.     public void removeCellEditorListener(CellEditorListener listener){
  18.         listenerList.remove(CellEditorListener.class, listener);
  19.     }
  20.     protected void fireEditingStopped(){
  21.         CellEditorListener listener;
  22.         Object[ listeners = listenerList.getListenerList();
  23.         for(int i=0; i<listeners.length; i++){
  24.             if(listeners<img src=”http://hp-aptech.edu.vn/emoticons/emotion-55.gif” alt=”Idea” /> == CellEditorListener.class){
  25.                 listener = (CellEditorListener) listeners[i+1];
  26.                 listener.editingStopped(changeEvent);
  27.             }
  28.         }
  29.     }
  30.     protected void fireEditingCanceled(){
  31.         CellEditorListener listener;
  32.         Object[ listeners = listenerList.getListenerList();
  33.         for(int i=0; i< listeners.length; i++){
  34.             if(listeners<img src=”http://hp-aptech.edu.vn/emoticons/emotion-55.gif” alt=”Idea” /> == CellEditorListener.class){
  35.                 listener = (CellEditorListener) listeners[i+1];
  36.                 listener.editingCanceled(changeEvent);
  37.             }
  38.         }
  39.     }
  40.     public void cancelCellEditing(){
  41.         fireEditingCanceled();
  42.     }
  43.     public boolean stopCellEditing(){
  44.         fireEditingStopped();
  45.         return true;
  46.     }
  47.     public boolean isCellEditable(EventObject event){
  48.         return true;
  49.     }
  50.     public boolean shouldSelectCell(EventObject event){
  51.         return true;
  52.     }
  53.     public Object getCellEditorValue(){
  54.         return new Boolean(getSelectedIndex() == 0 ? true : false);
  55.     }
  56.     public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column){
  57.         boolean isMale = ((Boolean) value).booleanValue();
  58.         setSelectedIndex(isMale?0:1);
  59.         return this;
  60.     }
  61. }

Khi việc chỉnh sửa được kích hoạt trong một ô nào đó của cột Gender, phương thức getTableCellEditorComponent được gọi, editor lúc này chuẩn bị trạng thái cho nó trước khi hiển thị. Trong trường hợp này, giá trị Male hoặc Female sẽ được chọn dựa trên giá trị hiện tại của ô đang được chỉnh sửa. Khi chúng ta chọn một giá trị trong JComboBox, phương thức fireEditingStopped được gọi, và nó thông báo với JTable rằng quá trình chỉnh sửa kết thúc. JTable sau đó sẽ gọi phương thức getCellEditorValue để lấy về giá trị mới của ô đó và sẽ truyền giá trị này vào phương thức setValueAt của TableModel.

Tiếp theo chúng ta sửa lại lớp SimpleTableTest trong file SimpleTableTest.java như sau:

  1. public class SimpleTableTest extends JFrame{
  2.     protected JTable table;
  3.     public SimpleTableTest(){
  4.         […]
  5.         TableColumnModel tcm = table.getColumnModel();
  6.         TableColumn tc = tcm.getColumn(TableValues.GENDER);
  7.         tc.setCellRenderer(new GenderRenderer());
  8.         tc.setCellEditor(new GenderEditor());
  9.         table.setDefaultRenderer(Float.class, new CurrencyRenderer());
  10.         JScrollPane jsp = new JScrollPane(table);
  11.         pane.add(jsp, BorderLayout.CENTER);
  12.     }
  13.     public static void main(String [ args){
  14.         […]
  15.     }
  16. }

Khi chạy chương trình ta sẽ thấy khi kích vào một ô trong cột Gender để chỉnh sửa, một cửa sổ được sổ xuống với giá trị được bôi đen chính là giá trị hiện thời của ô đó.

Để cho sau khi chọn giá trị này thực sự được thay đổi hẳn thì chúng ta cần phải cài đặt lại phương thức setValueAt trong lớp TableValues như sau

  1. public class TableValues extends AbstractTableModel{
  2.     public final static int FIRST_NAME = 0;
  3.     […]
  4.     public Object[[ values = {
  5.         […]
  6.     };
  7.     public int getRowCount() {
  8.         return values.length;
  9.     }
  10.     public int getColumnCount() {
  11.         return values[0].length;
  12.     }
  13.     public Object getValueAt(int rowIndex, int columnIndex) {
  14.         return values[rowIndex][columnIndex];
  15.     }
  16.     @Override
  17.     public String getColumnName(int column){
  18.         return columnNames[column];
  19.     }
  20.     @Override
  21.     public Class getColumnClass(int column){
  22.         […]
  23.     }
  24.     @Override
  25.     public boolean isCellEditable(int row, int column){
  26.         […]
  27.     }
  28.     @Override
  29.     public void setValueAt(Object value, int row, int column){
  30.         values[row][column] = value;
  31.     }
  32. }
DefaultCellEditor

Chúng ta cũng không nhất thiết trong mọi trường hợp đều phải tự xây dựng một editor hoàn toàn mới. Trong thực tế, lớp DefaultCellEditor cho phép chúng ta dễ dàng tạo một editor sử dụng một JCheckBox, JComboBox, hoặc là JTextField. Tất cả mọi thứ phải làm là tạo một đối tượng của lớp DefaultCellEditor và sau đó truyền cho nó các đối tượng của một trong 3 thành phần hiển thị kể trên vào. Tuy nhiên, DefaultCellEditor không có tính mềm dẻo và trong nhiều trường hợp chúng ta vẫn phải tạo editor của riêng chúng ta.

Như vậy trong phần 4, chúng ta đã biết cách để chỉnh sửa dữ liệu ngay chính trên cái bảng của chúng ta. Các thao tác chọn thực hiện trên hàng và cột trong một bảng sẽ được giới thiệu trongphần 5.

One Response to “Jtable_Swing ( Part 4)”

  1. Swing in Java « Thái Hoàng Hải Says:

    […] 1 Part 2 Part 3 Part 4 Share this:TwitterFacebookLike this:LikeBe the first to like this post. Comments RSS […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: