JTable trong Java có một tính năng rất hay ho. Đó là ngoài việc hiển thị dữ liệu, nó còn hỗ trợ bạn sửa trực tiếp dữ liệu trên nó.
Tiếp nối bài viết trước, trong bài viết này, mình sẽ hướng dẫn các bạn implements một số phương thức trong data model để có thể sửa dữ liệu ngay trên JTable. Và quan trọng hơn là cách xử lý dữ liệu mà người dùng vừa nhập đó để update dữ liệu gốc trong database hay file…
Trước hết, muốn JTable có thể chỉnh sửa được, bạn cần phải override phương thức
Đoạn code trên chỉ ra rằng bạn chỉ muốn cột thứ 2 (chỉ mục 1) editable mà thôi, các cột còn lại thì không.
Tiếp theo, bạn cần phải override lại phương thức
Công việc mà bạn cần làm trong phương thức này là khi người dùng đưa vào giá trị mới cho một ô, bạn sẽ cập nhật lại dữ liệu của ô đó bằng giá trị mới này. Sau khi cập nhật, JTable sẽ tự rebuild UI để hiển thị giá trị mới. Phương thức
Có một điều rất hay. Bạn dễ dàng nhận thấy JTable chỉ cập nhật giá trị mới trên giao diện của nó khi nào câu lệnh cập nhật giá trị trong phương thức
Kết quả khi thử nhập sai:
Cơ bản như vậy là đã xong. Tuy nhiên, trong bài toán thực tế, khi mà data của bạn thường được lấy ra từ database, từ file, không phải là mảng 2 chiều được khởi tạo sẵn như trong demo trên kia, thì những thay đổi mà chúng ta vừa làm không có ý nghĩa gì. Thật vậy, nếu chúng ta dừng lại ở đây, những gì đã được chỉnh sửa chỉ có ảnh hưởng đến phần giao diện của JTable, có nghĩa là lần sau bạn vào lại ứng dụng thì đâu lại vào đấy, vẫn nguyên như lúc đầu.
Để cập nhật sự thay đổi lên nguồn dữ liệu gốc (database, file,…), bạn sử dụng bộ lắng nghe sự kiện TableModelListener của data model để bắt những thông báo được phát sinh bởi
Ngoài cách trên, bạn cũng có thể viết đoạn code xử lý trong phương thức
Bạn cũng nên lưu ý rằng, không phải lúc nào cách trên cũng hiệu quả. Nó sẽ không thích hợp nếu bảng của bạn có quá nhiều cột editable. Khi đó, thay vì chỉ cần sửa tất cả các cột rồi update 1 lần, nó sẽ update liên tục mỗi khi bạn sửa 1 cột. Điều này gây tiêu tốn tài nguyên, giảm hiệu năng hệ thống.
Happy Coding!
Tiếp nối bài viết trước, trong bài viết này, mình sẽ hướng dẫn các bạn implements một số phương thức trong data model để có thể sửa dữ liệu ngay trên JTable. Và quan trọng hơn là cách xử lý dữ liệu mà người dùng vừa nhập đó để update dữ liệu gốc trong database hay file…
Trước hết, muốn JTable có thể chỉnh sửa được, bạn cần phải override phương thức
isCellEditable()
của AbstractTableModel trong data model mà bạn tạo (ở đây mình mặc định là data model được tạo ra bằng cách kế thừa AbstractTableModel). Mặc định phương thức isCellEditable()
đã được implements trong AbstractTableModel để luôn trả về giá trị false
. Bạn có thể đơn giản làm cho cả bảng editable bằng 1 dòng lệnh return true
. Tuy nhiên, không phải trong trường hợp nào bạn cũng muốn tất cả các cột trong JTable editable. Ví dụ như bạn lấy ra một bảng mà có cột Id có thuộc tính Identity trong database và show nó lên JTable. Bạn chắc sẽ không muốn cột Id của mình editable làm gì.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class CustomTableModel extends AbstractTableModel { ................... /* * Don't need to implement this method unless your table's * editable. */ @Override public boolean isCellEditable( int row, int col) { //Note that the data/cell address is constant, //no matter where the cell appears onscreen. return col == 1 ; } } |
Đoạn code trên chỉ ra rằng bạn chỉ muốn cột thứ 2 (chỉ mục 1) editable mà thôi, các cột còn lại thì không.
Tiếp theo, bạn cần phải override lại phương thức
setValueAt()
. Phương thức này có tham số đầu tiên là giá trị mà người dùng vừa sửa, các tham số tiếp theo lần lượt là chỉ số hàng và cột của ô được sửa. Giả sử data của chúng ta là một mảng 2 chiều, bạn có thể implements như sau:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public class CustomTableModel extends AbstractTableModel { public final static boolean GENDER_MALE = true ; public final static boolean GENDER_FEMALE = false ; public final static String[] columnNames = { "First Name" , "Last Name" , "Date of Birth" , "Account Balance" , "Gender" }; public Object[][] values = { { "Clay" , "Ashworth" , new GregorianCalendar( 1962 , Calendar.FEBRUARY, 20 ).getTime(), new Float( 12345.67 ), GENDER_MALE}, { "Jacob" , "Ashworth" , new GregorianCalendar( 1987 , Calendar.JANUARY, 6 ).getTime(), new Float( 23456.78 ), GENDER_MALE}, { "Jordan" , "Ashworth" , new GregorianCalendar( 1989 , Calendar.AUGUST, 31 ).getTime(), new Float( 34567.89 ), GENDER_FEMALE}, { "Evelyn" , "Kirk" , new GregorianCalendar( 1945 , Calendar.JANUARY, 16 ).getTime(), new Float(- 456.70 ), GENDER_FEMALE}, { "Belle" , "Spyres" , new GregorianCalendar( 1907 , Calendar.AUGUST, 2 ).getTime(), new Float( 567.00 ), GENDER_FEMALE} }; ................... /* * Don't need to implement this method unless your table's * editable. */ @Override public boolean isCellEditable( int row, int col) { //Note that the data/cell address is constant, //no matter where the cell appears onscreen. return col == 1 ; } @Override public void setValueAt(Object value, int row, int col) { values[row][col] = value; fireTableCellUpdated(row, col); } } |
Công việc mà bạn cần làm trong phương thức này là khi người dùng đưa vào giá trị mới cho một ô, bạn sẽ cập nhật lại dữ liệu của ô đó bằng giá trị mới này. Sau khi cập nhật, JTable sẽ tự rebuild UI để hiển thị giá trị mới. Phương thức
fireTableCellUpdated
được gọi ra có mục đích thông báo đến tất cả các bộ lắng nghe sự kiện rằng data model của JTable vừa có sự thay đổi ở hàng thứ row, cột thứ col. Bạn có thể tự hỏi liệu có cần thiết gọi fireTableCellUpdated
không? Hãy bình tĩnh đọc tiếp đã nhé.Có một điều rất hay. Bạn dễ dàng nhận thấy JTable chỉ cập nhật giá trị mới trên giao diện của nó khi nào câu lệnh cập nhật giá trị trong phương thức
setValueAt()
được thực thi thành công. Bạn có thể lời dụng điều này để bắt lỗi khi người dùng sửa dữ liệu. Giả sử mình sẽ bắt lỗi cột “Last Name” không được ít hơn 3 ký tự.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class CustomTableModel extends AbstractTableModel { ................... /* * Don't need to implement this method unless your table's * editable. */ @Override public boolean isCellEditable( int row, int col) { //Note that the data/cell address is constant, //no matter where the cell appears onscreen. return col == 1 ; } @Override public void setValueAt(Object value, int row, int col) { if (value.toString().length() < 3 ) { JOptionPane.showMessageDialog( null , "Must be more than 3 letters" ); return ; } values[row][col] = value; fireTableCellUpdated(row, col); } } |
Kết quả khi thử nhập sai:
Cơ bản như vậy là đã xong. Tuy nhiên, trong bài toán thực tế, khi mà data của bạn thường được lấy ra từ database, từ file, không phải là mảng 2 chiều được khởi tạo sẵn như trong demo trên kia, thì những thay đổi mà chúng ta vừa làm không có ý nghĩa gì. Thật vậy, nếu chúng ta dừng lại ở đây, những gì đã được chỉnh sửa chỉ có ảnh hưởng đến phần giao diện của JTable, có nghĩa là lần sau bạn vào lại ứng dụng thì đâu lại vào đấy, vẫn nguyên như lúc đầu.
Để cập nhật sự thay đổi lên nguồn dữ liệu gốc (database, file,…), bạn sử dụng bộ lắng nghe sự kiện TableModelListener của data model để bắt những thông báo được phát sinh bởi
fireTableCellUpdated
khi cập nhật JTable thành công (xem lại đoạn trên để hiểu rõ hơn). Mỗi khi bắt được 1 thông báo, event tableChanged sẽ được kích hoạt. Đến đây là lời giải thích cho câu hỏi ở phía trên. Bởi vì đơn giản bạn có thể hiểu nôm na rằng, nếu bạn không dùng fireTableCellUpdated
để ném ra thông báo thì bộ lắng sự kiện TableModelListener cũng chẳng bao giờ kích hoạt được sự kiện tableChanged cả. Và như thế những gì bạn viết trong sự kiện đó cũng sẽ chẳng bao giờ được thực thi. Ví dụ minh họa:1 2 3 4 5 6 7 8 9 10 | myTable.getModel().addTableModelListener( new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { // ví dụ // lấy về giá trị Id // lấy về giá trị mới được sửa // thực hiện cập nhật tới database } }); |
Ngoài cách trên, bạn cũng có thể viết đoạn code xử lý trong phương thức
setValueAt()
khi bạn override nó. Nhưng theo mình nên tách riêng model ra để phần code được rõ ràng, mạch lạc.Bạn cũng nên lưu ý rằng, không phải lúc nào cách trên cũng hiệu quả. Nó sẽ không thích hợp nếu bảng của bạn có quá nhiều cột editable. Khi đó, thay vì chỉ cần sửa tất cả các cột rồi update 1 lần, nó sẽ update liên tục mỗi khi bạn sửa 1 cột. Điều này gây tiêu tốn tài nguyên, giảm hiệu năng hệ thống.
Happy Coding!
Đăng nhận xét