kinh nghiem Java
-
Upload
hoangduy01 -
Category
Documents
-
view
740 -
download
1
Transcript of 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;}
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ự
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()){
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ì
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
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.
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; }}
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); }
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.
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.
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ờ
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
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
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(); }}
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à
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(),
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 { ...................
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.
Để 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;
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:
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() {
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.
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
Bước 2: Click chọn New Category…, đặt tên cho category là SwingX, sau đó nhấn OK
Bước 3: Click vào category SwingX mà bạn vừa tạo, nhấn nút Add from JAR…
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.
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.
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.
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.
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
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);}
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.