kinh nghiem Java

36
Sử dụng JTDS thay cho jdbc jTDS là một JDBC 3.0 driver thuần Java (type 4) mã nguồn mở dành cho Microsoft SQL Server và Sybase. Hiện nó được xem là JDBC Driver nhanh nhất cho SQL Server và Sybase. Như các bạn đã biết, để có thể kết nối phần mềm Java với database, chúng ta cần sử dụng những cầu nối trung gian được biết đến như các driver. Nó cũng giống như việc bạn cần cài đặt driver để có thể giao tiếp với các thiết bị ngoại vi vậy. Có 4 loại driver nhưng thường chúng ta sử dụng type 4 (100% pure Java). Với MS SQL , driver phổ biến là SqlJDBC do Microsoft cung cấp. Nó cài đặt dễ dàng và sự khác biệt về tốc độ cũng không quá quan trọng. Chúng ta chỉ thấy một thứ có vấn đề khi chính chúng ta gặp phải vấn đề với nó. Sẽ chẳng có bài viết này nếu như mình không gặp phải một bug rất ngớ ngẩn với SqlJDBC và phải tìm đến jTDS. Số là một ngày đẹp trời mình quyết định nâng cấp MS SQL Server lên bản mới nhất (SQL Server 2008 R2). Sau khi nâng cấp, mình chạy thử một ứng dụng Java mình đã làm trước đó thì nó báo không thể kết nối được với database. Mình vô Netbeans run project thì lại được. Clean rồi build đi build lại vẫn thế. Cứ chạy trong Netbeans thì ok, mà ra ngoài click file jar thì không kết nối được. Mất rất nhiều thời gian mình mới tìm hiểu ra đó là một bug của SqlJDBC với SQL Server 2008 R2. Tất cả được giải quyết khi mình dùng jTDS. Ngoài việc là mã nguồn mở có thể được chỉnh sửa dễ dàng và khắp phục những lỗi phát sinh so với SqlJDBC, jTDS còn được tối ưu hóa để tăng tốc độ kết nối cũng như hiệu năng làm việc. Trước hết, bạn tải về jTDS tại đây . Là một driver type 4, việc cài đặt jTDS rất đơn giản, chỉ cần thêm nó vào thư viện project của bạn, sau đó viết chuỗi kết nối dựa vào tên server, cổng kết nối, tên database. Mình demo phương thức getConnection trả về một đối tượng Connection khi kết nối thành công đến SQL Server: 1 2 3 4 5 public static Connection getConnection(Server server) { String url = "jdbc:jtds:sqlserver:// " + server.getServerName() + ":" + server.getP + "/" + server.getDatabaseName(); try { Class.forName("net.sourceforge.jtds.jdbc.Driver");

Transcript of kinh nghiem Java

Page 1: kinh nghiem Java

Sử dụng JTDS thay cho jdbcjTDS là một JDBC 3.0 driver thuần Java (type 4) mã nguồn mở dành cho Microsoft SQL Server và Sybase. Hiện nó

được xem là JDBC Driver nhanh nhất cho SQL Server và Sybase.

Như các bạn đã biết, để có thể kết nối phần mềm Java với database, chúng ta cần sử dụng những cầu nối trung gian

được biết đến như các driver. Nó cũng giống như việc bạn cần cài đặt driver để có thể giao tiếp với các thiết bị ngoại

vi vậy. Có 4 loại driver nhưng thường chúng ta sử dụng type 4 (100% pure Java). Với MS SQL, driver phổ biến là

SqlJDBC do Microsoft cung cấp. Nó cài đặt dễ dàng và sự khác biệt về tốc độ cũng không quá quan trọng.

Chúng ta chỉ thấy một thứ có vấn đề khi chính chúng ta gặp phải vấn đề với nó. Sẽ chẳng có bài viết này nếu như

mình không gặp phải một bug rất ngớ ngẩn với SqlJDBC và phải tìm đến jTDS. Số là một ngày đẹp trời mình quyết

định nâng cấp MS SQL Server lên bản mới nhất (SQL Server 2008 R2). Sau khi nâng cấp, mình chạy thử một ứng

dụng Java mình đã làm trước đó thì nó báo không thể kết nối được với database. Mình vô Netbeans run project thì

lại được. Clean rồi build đi build lại vẫn thế. Cứ chạy trong Netbeans thì ok, mà ra ngoài click file jar thì không kết

nối được. Mất rất nhiều thời gian mình mới tìm hiểu ra đó là một bug của SqlJDBC với SQL Server 2008 R2. Tất cả

được giải quyết khi mình dùng jTDS. Ngoài việc là mã nguồn mở có thể được chỉnh sửa dễ dàng và khắp phục

những lỗi phát sinh so với SqlJDBC, jTDS còn được tối ưu hóa để tăng tốc độ kết nối cũng như hiệu năng làm việc.

Trước hết, bạn tải về jTDS tại đây. Là một driver type 4, việc cài đặt jTDS rất đơn giản, chỉ cần thêm nó vào thư viện project của bạn, sau đó viết chuỗi kết nối dựa vào tên server, cổng kết nối, tên database. Mình demo phương thức getConnection trả về một đối tượng Connection khi kết nối thành công đến SQL Server:123456789101112131415

public static Connection getConnection(Server server) {     String url = "jdbc:jtds:sqlserver://" + server.getServerName() + ":" + server.getPort()            + "/" + server.getDatabaseName();    try {        Class.forName("net.sourceforge.jtds.jdbc.Driver");        Connection cn = DriverManager.getConnection(url, server.getUserName(), server.getPassword());        return cn;    } catch (ClassNotFoundException cnfe) {        JOptionPane.showMessageDialog(null, "Did you forget to import the jdbc library?");    } catch (SQLException se) {        System.out.println("Connection failed!");    }    return null;}

Page 2: kinh nghiem Java

Cập nhật: Microsoft đã nâng cấp JDBC Driver của mình lên 4.0 để hỗ trợ thêm SQL Server 2012. Mình cũng

không rõ nó khắp phục được bug trên chưa. Bạn có thể tải về tại đây.

DELETE MỘT ROW TRONG JTABLEXóa là một trong những chức năng cơ bản nhất mà chúng ta thực hiện trên JTable khi thao tác với cơ sở dữ liệu. Xóa một row trên JTable thường đi kèm với việc chúng ta xóa một bản ghi trong database. Cách làm khá đơn giản. Tuy nhiên để thực hiện một cách đúng đắn và hiệu quả, chúng ta cần nắm rõ về cấu trúc dữ liệu của JTable, hay nói cách khác là về data model của JTable.Có nhiều bạn có thể sẽ làm theo cách như sau: khi thực hiện xóa 1 row, các bạn xóa bản ghi tương ứng trong database, sau đó setModel lại cho JTable. Cách này cũng cho ra kết quả nhưng là một cách không thực tế và nên tránh. Trong trường hợp dữ liệu của bạn nhiều, việc setModel sẽ tốn khá nhiều thời gian cho việc truy xuất để lấy dữ liệu ra từ database.Có một cách khác hay hơn nhiều. Trong data model, bạn hãy tạo ra một phương thức như sau:1234

public void removeRow(int row) {    data.remove(row);    fireTableDataChanged();}

Phương thức này có nhiệm vụ remove 1 đối tượng (tương ứng với 1 row trên JTable) trong data của model xác định bởi tham số là chỉ số hàng được chọn để xóa của bảng (trong code ví dụ, data của mình là một ArrayList nên để xóa 1 đối tượng, mình dùng phương thức remove của nó). Khi phương thức này được gọi, JTable sẽ tự update giao diện của nó để phù hợp với data model.Chúng ta tạo một JButton để thực hiện xóa row được chọn trên JTable. Code xử lý sự kiện của nút như sau:

1234567891011

private void btnDeleteActionPerformed(java.awt.event.ActionEvent evt) {// TODO add your handling code here:    if (myTable.getSelectedRowCount() > 0) {        int realIndex = myTable.convertRowIndexToModel(myTable.getSelectedRow());        // delete record in database        //..........................        // update table UI        ((CustomTableModel) myTable.getModel()).removeRow(realIndex);        myTable.repaint();    }}

listselectionListener trong jtableTrong JTable chúng ta thường sử dụng 2 bộ lắng nghe sự kiện chính làListSelectionListener và TableModelListener để bắt các sự kiện tương ứng khi có sự

Page 3: kinh nghiem Java

thay đổi về ô đang được chọn và về dữ liệu của bảng. Nói về cách sử dụng TableModelListener, mình đã viết khá chi tiết trong bài viết Chỉnh sửa dữ liệu trực tiếp trên JTable, các bạn có thể tham khảo. Trong bài viết này, mình sẽ đề cập đến ListSelectionListener.Về cơ bản, bạn sử dụng bộ lắng nghe ListSelectionListener để bắt sự kiện khi JTable có sự thay đổi về lựa chọn trên bảng. Ví dụ như khi bạn click chuột vào một row khác row đang được chọn thì sự kiện sẽ được phát sinh. Nhiều bạn khi mới nhập môn có thể sẽ dùng sự kiện mouseClicked để xử lý sự lựu chọn trên JTable. Tuy nhiên, bạn nên nhớ sự lựu chọn trên bảng không chỉ được tạo ra bởi click chuột, mà còn bởi các phím lên xuống. Khi đó, MouseClick event không xử lý được.JTable mặc định cho phép chúng ta có thể chọn nhiều row một lúc ở các vị trí khác nhau (MULTIPLE_INTERVAL_SELECTION). Ngoài ra nó còn cung cấp 2 chế độ khác làSINGLE_INTERVAL_SELECTION cho phép chọn nhiều row nhưng phải liên tiếp nhau vàSINGLE_SELECTION chỉ cho phép chọn một row trong một thời điểm mà thôi. Bạn có thể thay đổi giữa các chế độ này bằng phương thức setSelectionMode. Do tính chất của bài viết, mình sử dụng chế độ chọn SINGLE_SELECTION. Mình sẽ tạo ra một demo mà sau khi hoàn thành sẽ như sau:

Một bảng dữ liệu mà khi click vào từng row, dữ liệu của row đó sẽ được viết vào cácJTextBox, JCheckBox phía dưới.Như thường lệ, bạn tạo một data model kế thừa từ AbstractTableModel làm dữ liệu cho bảng (chi tiết bạn có thể xem source code trong file attach). Code xử lý chính như sau:1234567

myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);final DateFormat df = new SimpleDateFormat("E MMM dd hh:mm:ss Z yyyy");myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {     @Override    public void valueChanged(ListSelectionEvent e) {        if(!e.getValueIsAdjusting()){

Page 4: kinh nghiem Java

89101112131415161718192021222324

            int selectedIndex = myTable.getSelectedRow();            int realIndex = myTable.convertRowIndexToModel(selectedIndex);            TableModel model = myTable.getModel();            txtFirst.setText(model.getValueAt(realIndex, 0).toString());            txtLast.setText(model.getValueAt(realIndex, 1).toString());            txtBalance.setText(model.getValueAt(realIndex, 3).toString());            String dateString = model.getValueAt(realIndex, 2).toString();            try {                Date birth = df.parse(dateString);                txtBirth.setText(DateFormat.getInstance().format(birth));            } catch (ParseException ex) {                Logger.getLogger(MyTable.class.getName()).log(Level.SEVERE, null, ex);            }            cbMale.setSelected(Boolean.parseBoolean(model.getValueAt(realIndex, 4).toString()));        }    }});

Trong đoạn code trên, bạn hãy lưu ý các điểm sau:1. Luôn luôn quy về table modelNguyên nhân bởi vì người dùng có thể dễ dàng thay đổi vị trí của các cột bằng cách kéo nó sang vị trí khác, hay thay đổi vị trí các hàng khi thực hiện sắp xếp. Nhưng trong model thì vị trí luôn được giữ nguyên. Vì vậy việc quy tất cả về table model giúp chúng ta xử lý dễ dàng hơn. JTable hỗ trợ sẵn các phương thức nhưconvertRowIndexToModel hay ngược lại convertRowIndexToView để chuyển đổi qua lại giữa vị trí trên View và trong Model.2. Sử dụng phương thức getValueIsAdjustingBạn hãy đặt một câu lệnh System out để test trước khối lệnh if trong đoạn code trên, sau đó chạy ứng dụng. Bạn có thể để ý thây điều này. Khi bạn click chọn một row trong bảng, bạn sẽ thấy ở cửa sổ output, lệnh System out được chạy 2 lần. Điều đó có nghĩa là khối code valueChanged đã được xử lý 2 lần mặc dù bạn chỉ click chọn 1 lần. Tại sao lại như vậy? Nguyên nhân bởi vì khi bạn click chuột chọn 1 row, có 2 event đã được phát sinh là mousePressed và mouseReleased, và cả 2 event này đều được gửi tới bộ lắng nghe sự kiện ListSelectionListener. Vì vậy mà đoạn code đã được xử lý đến 2 lần. Điều này sẽ không xảy ra nếu bạn chọn row trên JTable bằng các phím lên xuống. Bởi vì khi đó chỉ có 1 event được gửi đến bộ lắng nghe sự kiện làfocusGained.Phương thức getValueIsAdjusting trả về true nếu như sự kiện đang được tiếp nhận chỉ là một trong một chuỗi của nhiều sự kiện mà những sự kiện tiếp sau nó có thể vẫn tạo ra được sự thay đổi về lựa chọn. Nó chỉ trả về false nếu như sự kiện đó là cái cuối cùng trong chuỗi sự kiện (trong trường hợp trên mouseReleased là cái cuối cùng). Thường thì

Page 5: kinh nghiem Java

việc phải xử lý tất cả các event trong chuỗi là điều không mong muốn, nên chúng ta luôn kiểm tra nếu getValueIsAdjusting trả về false trước khi cho phép code được thực thi tiếp.

CĂN GIỮA HEADER TRONG JTABLEHeader của JTable chính là những thanh tiêu đề trên mỗi cột của nó. Trên hầu hết các Look and Feel, header của bảng luôn được căn lề bên trái. Điều này dẫn đến việc những người kỹ tính (như mình chẳng hạn) sẽ thấy nó không đẹp mắt chút nào, đặc biệt nếu một cột có độ rộng lớn. Đó là lý do tại mình muốn căn giữa header của JTable.Cũng không có gì nhiều để chia sẻ với các bạn. Nó chỉ là một vài dòng code mà thôi, giúp bạn tiết kiệm thời gian tìm kiếm trên Google.Để căn giữ header của bảng, bạn có thể code như sau:12345678910111213

// center the table headerTableColumnModel columnModel = myTable.getColumnModel();myTable.setTableHeader(new JXTableHeader(columnModel) {     @Override    public void updateUI() {        super.updateUI();        // need to do in updateUI to survive toggling of LAF        if (getDefaultRenderer() instanceof JLabel) {            ((JLabel) getDefaultRenderer()).setHorizontalAlignment(JLabel.CENTER);        }    }});

Hoặc một cách tương tự khác:

1234

// center title columnTableCellRenderer myRenderer = myTable.getTableHeader().getDefaultRenderer();JLabel label = (JLabel) myRenderer;label.setHorizontalAlignment(JLabel.CENTER);

Hai cách code trên về cơ bản là chung một nguyên tắc. Nhưng thực tế có một vài trường hợp đặc biệt 1 trong 2 cách có thể không hiệu quả. Mình nêu cả 2 cách để thêm một phương án dự phòng cho các bạn.Các bạn cũng để ý rằng, không chỉ có các ô của bảng, mà cả header của nó cũng được vẽ nên bởi các renderer. Nếu bạn sử dụng JXTable trong SwingX, cách làm hoàn toàn giống nhau. Bạn có thể thay JTableHeader bằng JXTableHeader ở đoạn code đầu tiên trong trường hợp này.

SỬ DỤNG JXTABLE TRONG SWING

Như các bạn đã biết, SwingX là thư viện được kế thừa từ Swing. Các component của SwingX

được kế thừa từ các component tương ứng trong Swing, mà cụ thể ở đây,JXTable được kế

thừa từ JTable. Do vậy, cách sử dụng của JXTable hoàn toàn tương tự như JTable. Cũng chính

Page 6: kinh nghiem Java

vì lẽ đó, sau bài giới thiệu về SwingX, mình đã có một loạt bài cơ bản về cách sử dụng của

JTable để làm nền cho bài viết này.

Sẽ chẳng có gì để viết thêm nếu như JXTable không có thêm một số tính năng hay ho mà

mình sắp giới thiệu ở đây. Bạn hãy dành 2 phút xem clip này:

Rất hay phải không các bạn    Giờ chúng ta sẽ cùng tìm hiểu cụ thể những tính năng

này nhé.

JXTable hỗ trợ sẵn sorting. Khi bạn click chuột vào header, các row của bảng sẽ được sắp

xếp theo giá trị của cột có header được click. Mỗi lần click, sự sắp xếp sẽ được tự động đảo

chiều tăng dần hay giảm dần. Bạn sẽ hỏi JXTable căn cứ vào đâu để thực hiện việc sắp xếp

các giá trị? Khác với JTable, JXTable mặc định enableautoCreateRowSorter. Do đó, nó tự

động tạo ra và sử dụng một row sorter mặc định được cung cấp bởi phương

thức createDefaultRowSorter.

Dĩ nhiên, bạn cũng có thể can thiệp vào quá trình này để tự mình chỉ ra comparatorso sánh

giá trị để sắp xếp, hay bạn có thể disable sorting trên một cột trong bảng.

Như bạn đã thấy trên clip, JXTable của mình có một nút nhỏ ở góc trên bên phải. Đó chính là

Column Control. Column Control các tác dụng giúp cho người dùng ẩn đi các cột không cần

thiết, để có tầm nhìn tốt hơn các cột muốn tập trung.

Mặc định, Column Control được ẩn đi. Bạn làm nó xuất hiện bằng

cáchsetColumnControlVisible(true).

Theo con mắt thẩm mỹ của mình, Highlighter giúp cho  giao diện của bảng đẹp hơn rất

nhiều. Có lẽ mình sẽ có một bài viết riêng nói về cách sử dụng Highlighter sau.

Trong clip demo, mình mới chỉ sử dụng một highlighter đơn giản làm background cho bảng được tạo ra bằng cách:

12

Highlighter simpleStripHL = HighlighterFactory.createSimpleStriping();jxTable.setHighlighters(simpleStripHL);

Rollover là hiệu ứng khi mà bạn di chuột đến row nào trong bảng thì row đó đổi màu. Đây cũng là một cách rất hay để làm đẹp giao diện. Bản chất của nó chính là một highlighter. Cách sử dụng như sau:

12

jxTable.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW,        null, new Color(255, 102, 0)));

Phương thức mình dùng sử dụng một constructor của lớp ColorHighlighter. Tham số đầu tiên

là kiểu rollover. Bạn có thể thay

thế ROLLOVER_ROW bằng ROLLOVER_CELL hayROLLOVER_COLUMN để có hiệu ứng rollover trên

từng ô, từng cột của bảng. Tham số thứ 2 là màu background. Mình không muốn để màu nền

nên set nó bằng null. Và tham số thứ 3 là màu text, được set mà màu da cam như bạn

thấy.

Page 7: kinh nghiem Java

Cái search popup hiện ra trên JXTable trong clip demo là hoàn toàn được support sẵn, mình

không hề tốn dòng code nào để tạo ra nó. Quá tuyệt phải không các bạn   Một box tìm

kiếm với đầy đủ tùy chọn như case sensitive hay backward.MỘT SỐ RENDERER HAY DUNG TRONG JTABLE

Trong bài viết này, mình sẽ tổng hợp cho các bạn một vài mẫu renderer hay được dùng

trong JTable. Mặc dù nhu cầu biểu diễn dữ liệu trên JTable rất đa dạng, bài tổng hợp này

cũng không thể bao quát hết được tất cả các trường hợp, nhưng cũng đừng lo, khi các bạn

làm nhiều, các bạn sẽ nhanh chóng rút được ra quy luật của nó thôi.

Với các cách biểu diễn đơn giản như dùng checkbox cho dữ liệu kiểu boolean, căn lề phải

cho kiểu Number, biểu diễn ngày tháng, hoặc nếu bạn còn lạ lẫm với khái niệm về renderer,

bạn hãy xem lại trong bài viết giới thiệu về cell renderer trước nhé.

DefaultTableCellRenderer mặc định sử dụng JLabel để render ô của bảng. Để tạo một

renderer có tác dụng đổi màu text trong JTable rất đơn giản. Bạn chỉ cần lợi dụng phương

thức setForeground của JLabel.12345678910

public static class ColorRenderer extends DefaultTableCellRenderer {     @Override    public Component getTableCellRendererComponent(JTable table, Object value, booleanint column) {        setForeground(new Color(153, 0, 0));        super.getTableCellRendererComponent(table, value, isSelected,                hasFocus, row, column);        return this;    }}

Bạn có thể căn lề trái, giữa, phải cho các cột trong JTable tùy ý.123456789101112131415

public static class AlignRenderer extends DefaultTableCellRenderer {     @Override    public Component getTableCellRendererComponent(JTable table, Object value, booleanint column) {        // align center        setHorizontalAlignment(SwingConstants.CENTER);        // align left        //setHorizontalAlignment(SwingConstants.LEFT);        // align right        //setHorizontalAlignment(SwingConstants.RIGHT);        super.getTableCellRendererComponent(table, value, isSelected,                hasFocus, row, column);        return this;    }}

Page 8: kinh nghiem Java

Lợi dụng khả năng biểu diễn hình ảnh của JLabel thông qua phương thức setIcon. Trong

demo, mình sẽ chèn 2 ảnh khác nhau vào cell của JTable dựa vào giá trị được đưa vào

là true hay false.12345678910111213141516171819

public static class ImageRenderer extends DefaultTableCellRenderer {     @Override    public Component getTableCellRendererComponent(JTable table, Object value, booleanint column) {        boolean bool = Boolean.parseBoolean(value.toString());        Image img = null;        if(bool) {            img = getToolkit().getImage(getClass().getResource("/images/male.png"));        } else {            img = getToolkit().getImage(getClass().getResource("/images/female.png"));        }        setSize(16, 16);        setHorizontalAlignment(SwingConstants.CENTER);        setIcon(new ImageIcon(img));        super.getTableCellRendererComponent(table, "", isSelected,                hasFocus, row, column);        return this;    }}

Tương tự từ demo này, bạn cũng có thể tạo ra renderer biểu diễn cả text và hình ảnh trên JTable.123456789101112131415

public static class CurrencyRenderer extends DefaultTableCellRenderer {     @Override    public Component getTableCellRendererComponent(JTable table, Object value, booleanint column) {        if ((value != null) && (value instanceof Number)) {            Number numberValue = (Number) value;            NumberFormat formater = NumberFormat.getCurrencyInstance();            value = formater.format(numberValue.doubleValue());        }        setHorizontalAlignment(javax.<a title="swing" href="http://codeimba.com/tag/swing/        super.getTableCellRendererComponent(table, value, isSelected,                hasFocus, row, column);        return this;    }}

Hoặc bạn làm như sau:123456

public static class CurrencyRenderer extends DefaultTableCellRenderer {

     public CurrencyRenderer() {        super();        setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);    }

 

Page 9: kinh nghiem Java

7891011121314151617

    @Override    public void setValue(Object value) {        if ((value != null) && (value instanceof Number)) {            Number numberValue = (Number) value;            NumberFormat formater = NumberFormat.getCurrencyInstance();            value = formater.format(numberValue.doubleValue());        }        super.setValue(value);    }}

Hai cách viết này tương đương nhau. Bản chất của phương thức setValue của

DefaultTableCellRenderer là gọi đến phương thức setText được kế thừa từ JLabel.

Các bạn tránh nhầm lẫn nhé. Giá trị mà renderer vẽ ra hoàn toàn không có ảnh hưởng gì đến

giá trị đầu vào. Nó chỉ có hiệu quả thị giác mà thôi. Bạn có thể hình dung rằng renderer chỉ

vẽ ra một tấm mặt nạ để che đậy khuôn mặt bên dưới vậy. Một cell có giá trị đầu vào là

“CodeBlue” nhưng được render ra hiển thị  là “Mr.CodeBlue”. Khi bạn lấy giá trị của cell này

(thông qua getValueAt của JTable chẳng hạn), thì giá trị bạn lấy được vẫn là “CodeBlue”.12345678910

public static class OutputRenderer extends DefaultTableCellRenderer {     @Override    public Component getTableCellRendererComponent(JTable table, Object value, booleanint column) {        value = "Mr." + value;        super.getTableCellRendererComponent(table, value, isSelected,                hasFocus, row, column);        return this;    }}

Đến đây, bạn có thể thấy rằng tất cả các renderer mà chúng ta vừa tạo đều được kế thừa từ

DefaultTableCellRenderer. Và trên thực tế, đây cũng là cách phổ biến nhất để tạo ra

renderer cho JTable. Như các bạn biết, DefaultTableCellRenderer được kế thừa từ JLabel.

Chúng ta có thể rút ra một kinh nghiệm từ đây:

Khi bạn muốn JTable được hiển thị theo một cách nào đó, bạn hãy nghĩ xem cách đó có thể

được biểu diễn bởi JLabel hay không? Nếu có thể, bạn chỉ cần viết các phương thức tương

ứng của JLabel trong khi override lại getTableCellRendererComponent của

DefaultTableCellRenderer. Nó sẽ trả về tham chiếu đến JLabel mà bạn đã sửa đổi để đạt

được sự hiển thị như mong muốn.

Vậy trường hợp nếu bạn muốn một cột trong JTable được hiển thị theo cách mà không thể

được biểu diễn bằng JLabel thì sao? Chúng ta cũng biết JLabel có những giới hạn. Chẳng hạn

nó không thể tạo ra màu background được. Trong trường hợp này, bạn có thể trả về cho

phương thức getTableCellRendererComponent một component khác mà có khả năng biểu

diễn được yêu cầu của bạn, hơn là trả về một JLabel như bình thường.

Page 10: kinh nghiem Java

Ví dụ mình tạo ra một renderer để render ra các ô có màu nền màu đỏ, text màu xanh, căn lề giữa:1234567891011121314151617

public static class BgRenderer extends DefaultTableCellRenderer {     @Override    public Component getTableCellRendererComponent(JTable table, Object value, booleanint column) {        JPanel pn = new JPanel(new BorderLayout());        JLabel lb = new JLabel();        pn.add(lb, BorderLayout.CENTER);        lb.setHorizontalAlignment(SwingConstants.CENTER);        lb.setText(value.toString());        lb.setForeground(Color.BLUE);        pn.setBackground(Color.RED);

         super.getTableCellRendererComponent(table, value, isSelected,                hasFocus, row, column);        return pn;    }}

Chúng ta đã có renderer, vậy làm thế nào để sử dụng chúng? Trong JTable, bạn có 2 cách

thường dùng sau:

Sử dụng renderer cho từng cột

Bạn xác định renderer cho từng cột bằng cách lấy về cột theo chỉ số của nó:

12

myTable.getColumnModel().getColumn(0).setCellRenderer(new MyTableRenderer.OutputRenderer());myTable.getColumnModel().getColumn(1).setCellRenderer(new MyTableRenderer.BgRenderer());

Sử dụng renderer cho từng kiểu dữ liệu

Áp dụng renderer cho tất cả các cột có cùng kiểu dữ liệu:

12

myTable.setDefaultRenderer(Number.class, new MyTableRenderer.CurrencyRenderer());myTable.setDefaultRenderer(Boolean.class, new MyTableRenderer.ImageRenderer());

Kết quả khi áp dụng các renderer đã được tạo:

Bài tổng hợp của mình không biết có bỏ sót renderer nào cơ bản không   Nếu có thiếu

sót, hãy để lại comment mình sẽ bổ sung nhé. Cảm ơn các bạn.

Page 11: kinh nghiem Java

SỬ DỤNG CELL RENDERER TRING JTABLE

Khi mới làm việc với JTable, chắc sẽ có lúc bạn tự hỏi làm thế nào để hiển thị hình ảnh,

checkbox trong JTable. Bạn sẽ có câu trả lời trong bài viết này, khi chúng ta cùng nhau tìm

hiểu về cell renderer.

Trong các bài viết trước về JTable, chúng ta đã tìm hiểu về data model – thành phần đảm

nhận nhiệm vụ cung cấp dữ liệu cho JTable. Bạn đã biết cách cơ bản để hiển thị dữ liệu, biết

cách để chỉnh sửa dữ liệu trực tiếp trên bảng. Tuy nhiên, để có thể tùy biến sự hiển thị của

dữ liệu trên các ô của bảng theo ý mình, bạn cần phải biết về cell renderer.

Có thể nói, kiến trúc của Swing mang đến cho các component của nó một sự uyển chuyển

tuyệt vời trong việc hiển thị. UI được tách biệt với phần data. Bạn hoàn toàn có thể làm cho

một ô trong JTable hiển thị một thứ chẳng liên quan gì đến dữ liệu được cung cấp cho ô đó.

Để dễ hình dung hơn, bạn hãy tưởng tượng cell renderer giống như một họa sĩ, dữ liệu ví như

một cô gái. Đối với bạn, cô gái đó có xấu hay đẹp đều hoàn toàn phụ thuộc vào người họa sĩ

vẽ cô gái ấy như thế nào. Tương tự với cell renderer, dữ liệu được hiển thị ra sao đều do nó

quyết định.

Mỗi một ô trong JTable được vẽ ra bởi một đối tượng cell renderer. Đối tượng này được tạo ra

từ các lớp implements interface TableCellRenderer. Trong interface này định nghĩa một

phương thức là getTableCellRendererComponent. Phương thức này trả về tham chiếu đến

một component mà component này sẽ đóng vai trò như một cây bút vẽ để cell renderer vẽ

nên hình hài ô của bảng. Phương thức getTableCellRendererComponent gồm 6 tham số đầu

vào như sau:

Một tham chiếu đến cái JTable có các ô dữ liệu đang được vẽ

Một tham chiếu đến giá trị của ô dữ liệu

Một biến cờ để xác định xem ô này có đang được chọn (kích chuột vào) hay không

Một biến cờ để xác định xem ô này có là một ô để nhập dữ liệu đầu vào và đang được

trỏ đến hay không

Một chỉ số hàng của ô đang được vẽ

Một chỉ số cột của ô đang được vẽ

Ngoài việc trả về tham chiếu đến một component có khả năng hiển thị, phương thức

getTableCellRendererComponent còn có nhiệm vụ khởi tạo trạng thái ban đầu cho

component đó. Chú ý rằng một trong những tham số đầu vào của phương thức là một tham

chiếu đến giá trị của một ô. Và giá trị này sẽ được lưu trong component trước khi phương

thức trả về một tham chiếu đến nó.

JTable đã cung cấp sẵn cho chúng ta một tập các renderer đã được định nghĩa trước cho một

số kiểu dữ liệu thông dụng. Mặc định, nếu chúng ta không chỉ ra renderer cho cột của bảng,

JTable sẽ căn cứ theo kiểu dữ liệu của các cột đó để xác định xem renderer nào sẽ được áp

dụng. Vậy làm thế nào JTable xác định được cột nào thuộc kiểu dữ liệu nào? Đó chính là nhờ

Page 12: kinh nghiem Java

vào phương thức getColumnClass của data model. Đây là lý do chúng ta nên override lại phương thức này khi chúng ta tạo data model cho bảng. Nếu không override lại phương thức này, JTable sẽ hiểu tất cả các cột của JTable mang kiểu Object.1234567891011121314151617181920

public class CustomTableModel extends AbstractTableModel {     ...................................

     /*     * JTable uses this method to determine the default renderer/     * editor for each cell.  If we didn't implement this method,     * then the last column would contain text ("true"/"false"),     * rather than a check box.     */    @Override    public Class getColumnClass(int c) {        if (getValueAt(0, c) == null) {            return String.class;        }        return getValueAt(0, c).getClass();    }

     ...................................}

Các kiểu dữ liệu mà được JTable hỗ trợ sẵn renderer gồm có:

java.lang.Number

Đây là kiểu cha cho các kiểu số như Integer, Float, Long … Renderer dành cho kiểu này dùng

một JLabel để biểu diễn dữ liệu theo dạng chuỗi, nhưng được căn lề bên phải.

java.lang.Boolean

Renderer áp dụng cho kiểu này sẽ thay thế các giá trị true, false bằng một JCheckBox với

true tương đương với checked, false tương đương với unchecked.

java.util.Date

Renderer cho kiểu này cũng đơn giản là sử dụng một JLabel để biểu diễn giá trị thời gian đưa

vào theo Locale mà Java xác định được.

javax.swing.ImageIcon

Page 13: kinh nghiem Java

Renderer kết hợp với lớp này cho phép chúng ta hiển thị một icon trên bảng. Nó lợi dụng khả

năng hiển thị cả ảnh và chữ của JLabel (phương thức setIcon cho phép chèn ảnh vào JLabel).

java.lang.Object

Khi JTable xác định kiểu dữ liệu cho cột của nó, nếu nó không tìm thấy renderer nào phù hợp

với kiểu dữ liệu được xác định, nó sẽ tiến hành tìm kiếm renderer của kiểu dữ liệu cha của

kiểu trước. Và cứ thế, nếu vẫn không tìm được, nó sẽ sử dụngDefaultTableCellRenderer –

renderer tương ứng với kiểu Object. Renderer này dùng một JLabel để biểu diễn dữ liệu dưới

dạng chuỗi, được căn lề bên trái.

Có nhiều cách để bạn tạo một custom renderer. Bạn có thể kế thừa lớp

DefaultTableCellRenderer, implements interface TableCellRenderer, hoặc vừa kế thừa một

component có khả năng biểu diễn dữ liệu vừa implements TableCellRenderer.

Trong demo dưới đây, mình sẽ biến chữ trong cột First Name thành màu xanh, trong cột Last

Name thành màu đỏ.123456789101112131415

public class ColorRenderer extends DefaultTableCellRenderer {     @Override    public Component getTableCellRendererComponent(JTable table, Object value, booleanint column) {        if (column == 0) {            setForeground(Color.BLUE);        }        if (column == 1) {            setForeground(Color.red);        }        super.getTableCellRendererComponent(table, value, isSelected,                hasFocus, row, column);        return this;    }}

Bạn có thể đặt câu hỏi tại sao lại return this? this ở đây chính là một JLabel. Điều này là

bởi vì DefaultTableCellRenderer được kế thừa từ JLabel. Đây là kết quả:

Trong bài tiếp theo, mình sẽ đưa cho các bạn một số demo về các cell renderer phổ biến mà

các bạn có thể hay dùng.SỬ DỤNG HYPERLINKS TRONG PANEL

Page 14: kinh nghiem Java

Hôm trước có bạn đã hỏi mình về sử dụng hyperlink trong Java. Qua tìm kiếm trên Google,

mình thấy có nhiều hướng dẫn hơi phức tạp và chưa có bài viết nào mang tính tổng quát. Do

vậy, mình viết bài viết này nhằm tổng hợp cũng như giới thiệu cho các bạn những cách dùng

của hyperlink trong Java Swing.

Có khá nhiều cách dùng của hyperlink, tùy theo từng mục đích cụ thể. Đầu tiên, ta sẽ xét

trường hợp bạn chỉ muốn chèn 1 link dạng label trên panel ứng dụng. Cách đầu tiên và đơn

giản nhất để sử dụng đó là sử dụng JXHyperlink trong SwingX.

Thư viện mở rộng SwingX cung cấp một compoment có tên gọi JXHyperlink để giúp chúng ta

dễ dàng chèn siêu liên kết ở bất kì đâu trong panel.

Cách tạo rất đơn giản. Bạn kéo thả JXHyperlink từ Palette của Netbeans vào panel. Sau đó bạn thiết lập title và URI cho nó.123456

try {    jxLink.setURI(new URI("http://codeimba.com/"));} catch (URISyntaxException ex) {    ex.printStackTrace();}jxLink.setText("Visit my site");

Thế là xong. Bạn đã tạo ra được một hyperlink như trên web vậy. Khi được click, nó sẽ mở ra

trang web có URI mà bạn đã thiết lập bằng trình duyệt mặc định trên máy tính của bạn.

Một cách khác bạn có thể sử dụng là làm giả giao diện (“look”) của các compenent như label hay button để nó trông giống như một siêu liên kết. Sau đó bạn sẽ viết sự kiện để mở một URI khi người dùng click vào. Đây là ví dụ mình làm với JButton:123456789101112131415161718192021

btnLink.setForeground(new java.awt.Color(0, 0, 255));btnLink.setText("Go codeimba.com");btnLink.setBorder(null);btnLink.setBorderPainted(false);btnLink.setContentAreaFilled(false);btnLink.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));btnLink.addActionListener(new java.awt.event.ActionListener() {    public void actionPerformed(java.awt.event.ActionEvent evt) {        btnLinkActionPerformed(evt);    }});..............

 // handling event on click buttonprivate void btnLinkActionPerformed(java.awt.event.ActionEvent evt) {        // TODO add your handling code here:    try {        Desktop.getDesktop().browse(new URI("http://codeimba.com"));    } catch (URISyntaxException ex) {        ex.printStackTrace();    } catch (IOException io) {        io.printStackTrace();    }}

Page 15: kinh nghiem Java

222324

Mình copy ra cho các bạn nhìn thấy thôi. Các bạn hãy sử dụng cửa sổ Properties trong

Netbeans để tiết kiệm thời gian nhé.

Trường hợp sử dụng thường thấy nữa là bạn có một đoạn văn bản có bao gồm cả các liên

kết. Bạn muốn hiển thị đoạn văn bản này lên giao diện phần mềm Java sao cho các liên kết

vẫn được giữ nguyên. Khi người dùng click vào chúng, mỗi liên kết sẽ duyệt đến trang web

được chỉ ra trong thuộc tính href của liên kết đó. Trong trường hợp này, các bạn có thể sử

dụng JEditorPane để làm điều đó.

Điều đầu tiên, bạn phải set thuộc tính editable là false cho JEditorPane. Tiếp theo, bạn set

content type của văn bản trong JEditorPane là text/html. Bạn cung cấp nội dung html cho

JEditorPane hiển thị thông qua phương thức setText() của nó. Làm đến đây, chúng ta đã

được một đoạn văn bản với các liên kết như mong muốn. Cuối cùng, để người dùng click vào

liên kết thì mở lên kết chứa trong nó, bạn cần phải bắt sự kiện HyperlinkUpdate của JEditorPane. Cụ thể, đoạn code hoàn chỉnh cho phần này sẽ như sau nếu bạn gõ tay (nhớ sử dụng tool nhé bạn):123456789101112131415161718

jep.setEditable(false);jep.setContentType("text/html");jep.setText("<html>rn  <head>rnrn  </head>rn  <body>rn    <p style="margin-top: 0">rn  </body>rn</html>rn");.............

 // eventprivate void jepHyperlinkUpdate(javax.swing.event.HyperlinkEvent evt) {    // TODO add your handling code here:    if (evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {        try {            Desktop.getDesktop().browse(evt.getURL().toURI());        } catch (URISyntaxException ex) {            ex.printStackTrace();        } catch (IOException io) {            io.printStackTrace();        }    }}

Đó là cách trường hợp và cách thực hiện phổ biến mà mình tổng hợp và giới thiệu đến các

bạn. Hi vọng bài viết sẽ giúp bạn nhiều trong việc sử dụng hyperlink trong Java.CHỈNH SỬA DỮ LIỆU TRỰC TIẾP TRÊN JTABLE

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à

Page 16: kinh nghiem Java

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ứcisCellEditable() 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ì.123456789101112131415

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:1234567891011121314

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(),

Page 17: kinh nghiem Java

15161718192021222324252627282930313233343536373839404142434445

            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ứcfireTableCellUpdated đượ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ự.1234

public class CustomTableModel extends AbstractTableModel {     ...................

 

Page 18: kinh nghiem Java

567891011121314151617181920212223242526

    /*     * 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.

Page 19: kinh nghiem Java

Để 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:12345678910

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ứcsetValueAt() 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.CÁCH SỬ DỤNG JTABLE CƠ BẢN TRONG JAVA

Trong Java, JTable là một trong những component chủ đạo trong việc hiển thị dữ liệu. Nó là

một Swing component cung cấp khả năng biểu diễn dữ liệu ở dạng bảng biểu. Ngoài ra,

JTable còn cho phép bạn có thể dễ dàng chỉnh sửa thông tin ngay trên các cell của nó như

khi bạn làm việc với Excel vậy. Trong bài viết này, chúng ta sẽ cùng tìm hiểu cách thao tác

với dữ liệu của bảng để hiển thị chúng lên JTable.

Như các bạn đã biết, kiến trúc của JTable nói riêng và Swing nói chung, đều được xây dựng

dựa theo mô hình MVC nên phần data (Model) sẽ tách biệt với phần giao diện (UI). Điều

này tạo nên sự rõ ràng, mạch lạc cho code, nhưng có thể gây khó khăn với những ai mới bắt

đầu.

Để hiển thị dữ liệu với JTable, chúng ta có nhiều cách. Bạn có thể lợi dụng một contructor của JTable như sau:123

public class MyTable extends javax.swing.JFrame {     public final static boolean GENDER_MALE = true;    public final static boolean GENDER_FEMALE = false;

Page 20: kinh nghiem Java

4567891011121314151617181920212223242526272829303132333435

    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}    };

     /** Creates new form MyTable */    public MyTable() {        initComponents();        setLayout(new BorderLayout());        JTable myTable = new JTable(values, columnNames);        JScrollPane jsp = new JScrollPane(myTable);        getContentPane().add(jsp, BorderLayout.CENTER);

     }

Bạn cũng lưu ý rằng, để JTable có thể hiển thị tên cột, các bạn cần bao nó lại bằng một JScrollPane.

Cách trên giúp bạn nhanh chóng tạo ra một JTable với dữ liệu được hiển thị. Mảng 1 chiều

cung cấp tên cột, mảng 2 chiều cung cấp data cho bảng. Tuy nhiên, cách này không thực tế,

chúng ta thường chỉ sử dụng nó trong demo, khi bạn mới học về JavaSE. Một cách khác mà

mình sẽ giới thiệu cho các bạn, cách mà các bạn sẽ thường dùng trong bài tập lớn, project…,

đó là sử dụng data model.

Data model đảm nhận nhiệm vụ cung cấp dữ liệu hiển thị cho JTable. Sử dụng data model

giúp ứng dụng “MVC” hơn bằng việc tách riêng phần data và phần UI, tạo ra sự custom tốt

hơn cho những bài toán phức tạp.

Về cơ bản, một data model được cài đặt 9 phương thức do interface TableModel định

nghĩa. Các phương thức đó được liệt kê trong hình dưới đây:

Page 21: kinh nghiem Java

Tác dụng của những phương thức trên bạn có thể dễ dàng suy ra thông qua tên của chúng,

mình không nói lại nữa nhé. Bạn cũng có thể tham khảo thêm trên Java docs. Bạn sẽ phải tự

mình cài đặt cả 9 phương thức trong trường hợp bạn tạo ra data model bằng cách

implements interface TableModel. Tuy nhiên Java cũng tạo ra một số các lớp khác được cài

đặt sẵn một số phương thức, giúp cho công việc của chúng ta đơn giản hơn. Cụ thể như

class DefaultTableModel và abstract class AbstractTableModel, tất cả đều implements

interface TableModel. Thông thường, chúng ta sẽ tạo ra data model bằng cách kế thừa

AbstractTableModel. Khi đó, chúng ta chỉ cần thiết phải override lại 4 phương thức.

Dưới đây, mình sẽ tạo ra lớp CustomTableModel như sau:12345678910111213141516171819202122232425

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}    };

     @Override    public int getRowCount() {

Page 22: kinh nghiem Java

2627282930313233343536373839404142434445464748

        return values.length;    }

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

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

     // Need for showing column name    @Override    public String getColumnName(int columnIndex) {        return columnNames[columnIndex];    }

 }

Trong thực tế, dữ liệu của bảng thường được cung cấp dưới dạng Vector hoặc List thay vì

mảng 2 chiều như demo này. Khi đó, bạn cần sửa lại những phương thức đã override cho

phù hợp.

Để JTable của chúng ta sử dụng data model vừa được tạo ra, chúng ta có thể sử dụng

phương thức setModel() của JTable:1 myTable.setModel(new CustomTableModel());

Kết quả khi chạy chương trình:

Nhìn từ bề nổi, bạn có thể nhận xét rằng cấu trúc của 2 lớp này chỉ khác nhau ở chỗ

DefaultTableModel được cài đặt nốt 3 phương thức mà AbstractTableModel còn bỏ ngỏ. Thế

nhưng còn có sự khác biệt khá lớn nữa, để AbstractTableModel được khuyến khích sử dụng,

chứ không phải DefaultTableModel.

Page 23: kinh nghiem Java

Sở dĩ như vậy là bởi vì AbstractTableModel cho phép chúng ta toàn quyền điều khiển dữ liệu

input cũng như output. Để xuất dữ liệu ra bảng, chúng ta phải tự mình cài đặt phương

thức getValueAt(). Phương thức này nhận 2 tham số là chỉ số cột và chỉ số dòng. Nó quy

định dữ liệu gì sẽ được cung cấp cho mỗi ô trong bảng (được xác định bởi 2 tham số hình

thức). Không giống như thế, DefaultTableModel thiếu tính mềm dẻo và mở rộng hơn nhiều.

Mặc định nó được cài sẵn tất cả các phương thức mà đã implements từ TableModel. Điều này

dẫn đến việc bạn sẽ khó khăn hơn khi muốn can thiệp vào quá trình xử lý bảng. Hơn nữa,

DefaultTableModel sử dụng Vector (chính xác hơn là một Vector bao gồm những Vector

khác) làm dữ liệu input. Và kéo theo đó, các phương thức như getValueAt(), setValueAt()

… trong quá trình thực thi cũng thao tác với dữ liệu dạng Vector này. Vector là một

Collection đã “lỗi thời” xuất hiện từ thời Java 1.0. Bạn có thể thấy trong Netbeans, bạn sẽ

nhận được thông báo “Obsolete Collection” nếu cố tình dùng Vector. Vector có khá nhiều

hạn chế như less thread-safe và chậm chạp hơn so với các Collection khác. ArrayList được

khuyến cáo thay thế cho Vector. Cụ thể mình sẽ có một bài viết khác về vấn đề này.

Vậy nên, nếu làm việc với data model, bạn nên sử dụng AbstractTableModel và sử dụng

ArrayList cho dữ liệu đầu vào.

Trong bài viết này, mình đã hướng dẫn cho các bạn cơ bản về cách hiển thị dữ liệu với

JTable. Ở vài viết tiếp theo, chúng ta vẫn sẽ xoay quanh data model. Mình sẽ hướng dẫn các

bạn override một số phương thức của TableModel để có thể chỉnh sửa dữ liệu trực tiếp trên

các ô của bảng và làm thế nào để xử lý kết quả người dùng vừa mới cập nhật đó.CÀI ĐẶT VÀ SỬ DỤNG SWINGX

Bản chất của việc cài đặt và sử dụng SwingX trong Netbeans là việc bạn đưa các component

của SwingX vào trong Palette của Netbeans để phục vụ cho việc kéo thả.

Trong bài viết này, mình sẽ hướng dẫn chi tiết cách để đưa một thư viện Java vào trong

Palette của Netbeans. Sau bước này, bạn có thể kéo thả các component của thư viện đó

tương tự như cách bạn vẫn làm với các component của Swing. Mình demo với SwingX, các

thư viện khác như JCalendar.., các bạn hoàn toàn làm tương tự.

Bước 1: Trong Netbeans, các bạn chọn Tools -> Palette -> Swing/AWT Components

Page 24: kinh nghiem Java

Bước 2: Click chọn New Category…, đặt tên cho category là SwingX, sau đó nhấn OK

Page 25: kinh nghiem Java

Bước 3: Click vào category SwingX mà bạn vừa tạo, nhấn nút Add from JAR…

Page 26: kinh nghiem Java

Bước 4: Trỏ đến file  jar của SwingX mà bạn đã download. Nếu chưa có, bạn có thể tìm link

download trong bài viết Giao diện trong Java: SwingX của mình.

Page 27: kinh nghiem Java

Bước 5: Sau khi chọn file jar ở bước 4, tất cả các component có trong file này sẽ được liệt kê

như hình dưới. Bạn chỉ nên chọn những component nào bắt đầu bằng “JX” mà thôi, vì đó mới

là những UI component nhìn thấy được.

Page 28: kinh nghiem Java

Bước 6: Tiếp theo, các bạn chọn lại category SwingX mà các bạn đã tạo, sau đó nhấn Finish.

Page 29: kinh nghiem Java

Cuối cùng, bạn sẽ thấy trên Palette của Netbeans xuất hiện thêm một category SwingX như

hình. Để sử dụng, các bạn chỉ cần kéo thả vào JFrame hay JPanel như bình thường.

Page 30: kinh nghiem Java

Happy Coding! 

LOOK AND FEEL

Nhiều bạn nghĩ rằng phần mềm Java có giao diện xấu, nhưng có thật như thế không? Các

bạn hãy stop 1 phút ghé thăm trang web này. Đó là trang web của một công ty chuyên thiết

kế giao diện phần mềm cho khách hàng đặt hàng. Bạn có thể thấy một vài demo của họ

trong ảnh slide. Không biết bạn nghĩ sao chứ mình thấy nó rất đẹp và chuyên nghiệp. Thứ họ

đã tạo ra và sử dụng là BizLaf Look-and-Feel. Trong bài viết này, chúng ta sẽ cùng tìm hiểu

một vài khái niệm cơ bản về Look-and-Feel (LaF). Ở bài sau, mình sẽ giới thiệu cho các bạn

một vài LaF đẹp mắt để các bạn tham khảo.

Quả thật trước đây, giao diện phần mềm Java thật sự rất nghèo nàn. Nhưng vấn đề này đã

được giải quyết sau khi Swing ra đời. Swing mang đến cho Java một kiến trúc thiết kế rất

hiểu quả và rõ ràng. Pluggable Look-and-Feel là một trong số những tính năng của Swing. Nó

cho phép ứng dụng Swing có thể thay đổi toàn bộ giao diện chỉ với một hai dòng

Page 31: kinh nghiem Java

code. “Look” ở đây đại diện cho thành phần GUI bên ngoài của component, trong

khi “Feel” đại diện cho phần behave (ví dụ như hiệu ứng khi hover, khi click,…). Bạn có thể

tưởng tượng LaF giống như theme trong điện thoại vậy. Chỉ với vài thao tác bấm, bạn có thể

chọn qua lại giữa các theme trong điện thoại.

Về cơ bản, LaF có 2 loại:

Skinable

Non-skinable

Skinable LaF không chỉ thay đổi giao diện của các component trong ứng dụng mà còn thay

đổi giao diện của thanh tiêu đề window và border.

JRE cung cấp các LaF sau:

1. CrossPlatformLookAndFeel: còn được gọi là Java LaF hay Metal LaF. LaF này tạo ra

giao diện giống nhau trên tất cả các nền tảng. Nó là một phần của Java API. Đây sẽ là

LaF mặc định nếu bạn không chỉ ra LaF trong code.

2. SystemLookAndFeel: LaF của riêng nền tảng mà ứng dụng đang chạy, được xác định

lúc runtime.

3. Synth: giúp bạn tạo LaF của riêng bạn với 1 file XML.

4. Multiplexing: một cách để sử dụng nhiều LaF cùng lúc.

Với Linux và Solaris, System LaF là “GTK+” nếu GTK+ 2.2 hoặc mới hơn được cài đặt, ngược

lại, System LaF sẽ là “Motif”. Với Windows, System LaF là “Windows”. GTK+, Motif và

Windows LaF được cung cấp bởi Sun và tích hợp trong SDK, JRE mặc dù chúng không phải là

1 phần của Java API. Trên MAC, Apple cung cấp JVM riêng. JVM này bao gồm cả LaF riêng cho

các ứng dụng chạy trên MAC.

Bạn có thể để ý, trong Netbean 7.0+, khi bạn tạo một JFrame, trong hàm main sẽ tự động được chèn code cài đặt Nimbus LaF. Bạn có thể chuyển về Windows nếu bạn muốn (trong trường hợp bạn đang dùng Windows).

12345678910111213141516

try {    for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {        if ("Windows".equals(info.getName())) {            javax.swing.UIManager.setLookAndFeel(info.getClassName());            break;        }    }} catch (ClassNotFoundException ex) {    java.util.logging.Logger.getLogger(LabourManagement.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);} catch (InstantiationException ex) {    java.util.logging.Logger.getLogger(LabourManagement.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);} catch (IllegalAccessException ex) {    java.util.logging.Logger.getLogger(LabourManagement.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);} catch (javax.swing.UnsupportedLookAndFeelException ex) {    java.util.logging.Logger.getLogger(LabourManagement.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);}

Page 32: kinh nghiem Java

UIManager cài đặt LaF theo các bước sau:

1. Nếu trong code có set LaF (như trên), UIManager sẽ tạo ra một thể hiện của LaF Class

được chỉ ra. Nếu thành công, tất cả component sẽ sử dụng LaF này.

2. Nếu không thành công, UIManager sẽ sử dụng LaF được chỉ ra bởi thuộc

tínhswing.defaultlaf. Nếu thuộc tính này được chỉ ra trong cả swing.propertiesfile

và trên command line thì trên command line sẽ được ưu tiên.

3. Nếu kết quả 2 bước trên vẫn không xác định được 1 LaF hợp lệ, JRE sẽ sử dụng Java

LaF. Ngoại trừ trên MAC sử dụng LaF riêng.

Bạn có thể thay đổi LaF ngay cả sau khi ứng dụng đã hiện lên. Để làm được việc này, bạn gọi

phương thức updateComponentTreeUI của SwingUtilities cho mỗi top-level container. Sau đó, bạn có thể phải resize mỗi top-level container này tới size mới để phù hợp với LaF mới.123

UIManager.setLookAndFeel(lnfName);SwingUtilities.updateComponentTreeUI(frame);frame.pack();

Bạn có thể tham khảo thêm về Look-and-Feel tại đây.