Album ảnh

Sử dụng JTable của Swing trong java (phần 1)


https://codersontrang.com/2012/08/20/74/

Ngày trước ở blog cũ, mình có dịch một loạt 9 bài viết khá hay về JTable của Swing trong java. Hôm nay quyết định chuyển loạt bài này sang “nhà mới” để cùng chia sẻ với mọi người 😉

Nếu để ý chúng ta hẳn sẽ thấy rằng rất nhiều ứng dụng cần hiển thị dữ liệu dưới dạng bảng biểu. Trong java, Swing cung cấp cho chúng ta một lớp để thực hiện điều này đó là lớp JTable.

Bên cạnh khả năng hiển thị thông tin, JTable cũng cho phép chúng ta dễ dàng sửa đổi thông tin, đặt kích cỡ và đầu đề cho từng cột, và điều khiển cách dữ liệu hiển thị trong bảng biểu. Đầu tiên chúng ta hãy học cách để hiển thị thông tin lên bảng biểu vì đây là chức năng cơ bản và cũng cần thiết nhất. Bản chất của JTable là nó lấy dữ liệu từ một data model và hiển thị dữ liệu từ đó lên. Vì thế, trước tiên chúng ta sẽ nghiên cứu về cái data model.

Data model

Ngoài lớp JTable, Swing còn cung cấp một số các lớp, các interface khác nữa. Các lớp, interface này sẽ được sử dụng bởi lớp JTable và chúng được định nghĩa trong gói javax.swing.table. Một trong số đó là interface TableModel. Interface TableModel tạo nên một sự giao tiếp giữa một cái JTable và cái data model của nó. Giống như các thành phần khác của Swing, JTable được thiết kế theo mô hình model/view/controller mà ở đó các thành phần dùng để hiển thị (JTable )sẽ được tách riêng biệt so với các thành phần lưu trữ dữ liệu (TableModel). Điều này mang đến một sự linh động hơn trong thiết kế và tăng khả năng sử dụng lại các thành phần. Tuy nhiên, nó cũng làm cho việc sử dụng JTable trở nên phức tạp hơn. Nhưng cũng thật may mắn, Swing cũng cung cấp một số cài đặt mặc định làm cho việc sử JTable trở nên đỡ phức tạp hơn trước.

Như đã nói, TableModel có nhiệm vụ là cung cấp dữ liệu hiển thị cho JTable. Bên cạnh đó, nó cũng có nhiệm vụ cung cấp cho JTable một vài thông tin khác như:

  • Số lượng dòng và số lượng cột trong bảng
  • Kiểu dữ liệu trong từng cột
  • Tiêu đề cho từng cột
  • Có cho phép sửa giá trị trong một ô hay không

Interface TableModel bao gồm 9 phương thức cần được cài đặt giống như dưới đây.

Cần phải cài đặt cả 9 phương thức thực sự không thích hợp trong những trường hợp chúng ta muốn tạo nhanh một bảng. Nhưng java cũng đã cung cấp cho chúng ta các lớp khác như là AbstractTableModelDefaultTableModel. Cả hai lớp này đều đã cài đặt phần nào các phương thức của interface TableModel. Vì thế, khi sử dụng thì chúng ta sẽ tốn ít công hơn. Chẳng hạn như nếu chúng ta kế thừa lớp AbstractTableModel thì chúng ta chi cần cài đặt 3 phương thức sau:

  • Phương thức trả về số dòng của bảng
  • Phương thức trả về số cột của bảng
  • Phương thức trả về giá trị của mỗi ô

Dưới đây, chúng ta tạo một lớp là TableValues trong file TableValues.java. Lớp này kế thừa lớp AbstractTableModel và sẽ là data model cho một cái JTable.

 
public class TableValues extends AbstractTableModel{

    public final static boolean GENDER_MALE = true;
    public final static boolean GENDER_FEMALE = false;

    public Object[][] values = {
        {
            "Clay", "Ashworth",
            new GregorianCalendar(1962, Calendar.FEBRUARY, 20).getTime(),
            new Float(12345.67), new Boolean(GENDER_MALE)
        }, {
            "Jacob", "Ashworth",
            new GregorianCalendar(1987, Calendar.JANUARY, 6).getTime(),
            new Float(23456.78), new Boolean(GENDER_MALE)
        }, {
            "Jordan", "Ashworth",
            new GregorianCalendar(1989, Calendar.AUGUST, 31).getTime(),
            new Float(34567.89), new Boolean(GENDER_FEMALE)
        }, {
            "Evelyn", "Kirk",
            new GregorianCalendar(1945, Calendar.JANUARY, 16).getTime(),
            new Float(-456.70), new Boolean(GENDER_FEMALE)
        }, {
            "Belle", "Spyres",
            new GregorianCalendar(1907, Calendar.AUGUST, 2).getTime(),
            new Float(567.00), new Boolean(GENDER_FEMALE)
        }
    };

    public int getRowCount() {
        return values.length;
    }

    public int getColumnCount() {
        return values[0].length;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        return values[rowIndex][columnIndex];
    }
    
}

Ở đây, dữ liệu chính là mảng hai chiều values. Vì để cho ví dụ đơn giản nên chúng ta viết dữ liệu ở trong code (hard-code). Tuy nhiên, ở ngoài thực tế, dữ liệu của chúng ta thường sẽ lấy ra từ cơ sở dữ liệu. Như đã nói ở trên, vì kết thừa lớp AbstractTableModel, nên chúng ta phải cài đặt 3 phương thức đó là getRowCount(), getCollumnCount()getValueAt()

Bây giờ chúng ta tạo một lớp SimpleTableTest để tạo một JTable và gán cho nó cái model vừa tạo ở trên
Đoạn code trong file SimpleTableTest.java sẽ như sau:

 
public class SimpleTableTest extends JFrame{
    protected JTable table;

    public SimpleTableTest(){
        Container pane = getContentPane();
        pane.setLayout(new BorderLayout());
        TableValues tv = new TableValues();
        table = new JTable(tv);
        pane.add(table, BorderLayout.CENTER);
    }

    public static void main(String [] args){
        SimpleTableTest stt = new SimpleTableTest();
        stt.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        stt.setSize(400, 200);
        stt.setVisible(true);
    }
}

Trong contructor của lớp SimpleTableTest ta có hai dòng lệnh sau:

 
TableValues tv = new TableValues(); 
table = new JTable(tv);

Dòng lệnh thứ nhất là tạo ra một cái data model tên là tv. Dòng lệnh thứ hai tạo ra một cái JTable có tên là table và gán cho nó cái model tv. Sau khi chạy chương trình, dữ liệu trong model tv sẽ được hiển thị lên cái bảng table như sau:

Như vậy, chúng ta cũng thấy được, việc sử dụng AbstractTableModel có vẻ nhẹ nhành hơn rất nhiều so với việc sử dụng TableModel. Bên cạnh đó, việc sử dụng DefaultTableModel còn dễ dàng hơn nhiều so với việc sử dụng AbstractTableModel tuy nhiên nó lại không được khuyến khích để sử dụng. Nguyên nhân là bởi vì bản thân DefaultTableModel tự động tạo ra các tham chiếu đến các ô dữ liệu thay vì chúng ta phải cài đặt một phương thức nào đó giống như getValueAt() khi sử dụng AbstracTableModel. Điều này dẫn tới một sự không mềm dẻo và không thể mở rộng trong quá trình sử dụng. Bên cạnh đó, một vấn đề khác gặp phải chính là vấn đề sửa dữ liệu ở các ô. Để hiểu tại sao DefaultTableModel không có khả năng mở rộng, chúng ta cần phải hiểu cái JTable nó làm việc như thế nào.

Như chúng ta đã biết, TableModel có nhiệm vụ chỉ ra trong bảng có bao nhiêu dòng và có bao nhiêu cột. Hai phương thức getRowCount() và getColumnCount() sẽ được gọi ngay khi một bảng được tạo và hiển thị. Tuy nhiên, một bảng sẽ không bao giờ tham chiếu đến toàn bộ dữ liệu trong TableModel mà chỉ tham chiếu đến một phần nào đó để đủ hiển thị. Cho ví dụ như sau, giả sử chúng ta tạo một data model để trả về giá trị là 100 từ phương thức getRowCount(), nhưng cái bảng của chúng ta thì nằm trong một cái cửa sổ cuộn mà mỗi lần chúng ta chỉ nhìn tối đa được 10 dòng. Khi bảng được hiển thị, đầu tiên nó sẽ truy cập đến 10 dòng đầu của dữ liệu trong TableModel, và sẽ truy cập đến dữ liệu cho các dòng khác chỉ khi chúng ta cuộn thanh cuộn xuống. Tại sao điều này lại quan trọng như vậy? Bởi vì như thế nó cho phép chúng ta hiển thị một lượng lớn dữ liệu vào trong JTable mà không cần tải toàn bộ dữ liệu vào bộ nhớ một cách đồng thời. Lúc này, TableModel chỉ cần tải dữ liệu theo nhu cầu, và giảm thiểu tối đa dung lượng bộ nhớ được sử dụng.

Với điều này, giờ chúng ta quay lại việc sử dụng DefaultTableModel và việc nó tự tạo ra các tham chiếu đến dữ liệu bên trong nó. Bởi vì nó yêu cầu tham chiếu tới toàn bộ dữ liệu, nên toàn bộ dữ liệu phải nằm ở bên trong bộ nhớ chừng nào chúng ta vẫn còn sử dụng cái model đó. Đây chính là nhược điểm của việc sử dụng DefaultTableModel và một lời khuyên là chúng ta nên dùng AbstractTableModel để thay thế. Tuy nhiên, với một lượng dữ liệu nhỏ, chúng ta vẫn có thể cân nhắc sử dụng DefaultTableModel bởi vì nó đơn giản. Hơn nữa dữ liệu được cache sẵn vào trong bộ nhớ sẽ giúp chúng ta truy cập nhanh hơn.

Trở lại với ví dụ ở trên, chúng ta thấy rằng có một vấn đề đó là khi của sổ của chúng ta thay đổi sao cho kích thước của nó nhỏ hơn kích thước của bảng thì có một phần dữ liệu sẽ bị mất không thể nhìn thấy như hình dưới đây:

Thêm vào nữa, các cột của chúng ta chưa có tiêu đề. Tất cả những vẫn đề này sẽ được giải quyết trong phần 2.


Nguồn: Brett Spell – Pro Java Programming, Second Edition

5 comments on “Sử dụng JTable của Swing trong java (phần 1)

  1. anh ơi cho em hỏi : anh có thể hướng dẫn việc tạo 1 bảng trong giao diện , sau đó nhập dữ liệu vào bảng đó . sau đó là việc lấy dữ liệu từ bảng đó vào cơ sở dữ liệu ạ. Mong anh giúp vì em đang rất cần ạ .

    Thích

Gửi phản hồi cho Hoàng Phúc Hủy trả lời