[Java swing] Tùy biến JTabbedPane
Ở bài trước mình đã giới thiệu về JTabbenPane một số điểm cơ bản, bài này chúng ta tìm hiểu thêm một chút để tùy biến JTabbedPane cho ngon hơn tý, giống như hình dưới đây, có đóng tab, thêm tab,…
Trong đó phần quan trọng nhất đó là ta làm sao để có nút để thêm tab mới, làm sao mỗi tab nó có nút đóng tab. Chúng ta sẽ tạo 2 file, 1 file thực hiện tùy biến tab (có JLabel hiện tiêu đề tab và nút đóng tab) và 1 file tạo JTabbedPane dựa trên Tab được tùy biến ở file kia.
Bắt sự kiện thêm tab mới cho JTabbedPane
Về việc tạo nút để thêm tab mới thì thực chất chúng ta tạo ra 1 tab có tiêu đề là dấu cộng (“+”) và thực hiện bắt sự kiện thay đổi cho JTabbedPane. Mỗi khi click chuyển tab, nếu bạn click vào tab này thì thực hiện thêm 1 tab mới. Dưới đây là code file tạo tùy biến JTabbedPane.
package nguyenvanquan7826.JTabbedPane; import java.awt.Color; import java.awt.GridLayout; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * --------------------- @author nguyenvanquan7826 --------------------- * ------------------ website: cachhoc.net ------------------- * ---------- date: Jul 24, 2014 - filename: DemoCustomJTabPane.java ---------- */ public class DemoCustomJTabbedPane extends JFrame { JTabbedPane tabbedPane; int numTabs; public DemoCustomJTabbedPane() { createGUI(); setDisplay(); } /** set diplay for JFrame */ private void setDisplay() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(450, 300); setLocationRelativeTo(null); setVisible(true); } /** set title and add JTabbedPane into JFrame */ private void createGUI() { setTitle("Demo custum JTabbedPane"); createJTabbedPane(); add(tabbedPane); } /** create JTabbedPane contain 2 tab */ private void createJTabbedPane() { /* create JTabbedPane */ tabbedPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); /* add first tab */ tabbedPane.add(createJPanel(), "Tab " + String.valueOf(numTabs), numTabs++); tabbedPane.setTabComponentAt(0, new DemoCustomTab(this)); /* add tab to add new tab when click */ tabbedPane.add(new JPanel(), "+", numTabs++); tabbedPane.addChangeListener(changeListener); } /** create JPanel contain a JLabel */ private JPanel createJPanel() { JPanel panel = new JPanel(new GridLayout(1, 1)); panel.add(new JScrollPane(createTextArea(10, 40))); return panel; } private JTextArea createTextArea(int row, int col) { JTextArea ta = new JTextArea(row, col); ta.setWrapStyleWord(true); ta.setLineWrap(true); ta.setForeground(Color.BLUE); return ta; } ChangeListener changeListener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { addNewTab(); } }; private void addNewTab() { int index = numTabs - 1; if (tabbedPane.getSelectedIndex() == index) { /* if click new tab */ /* add new tab */ tabbedPane.add(createJPanel(), "Tab " + String.valueOf(index), index); /* set tab is custom tab */ tabbedPane.setTabComponentAt(index, new DemoCustomTab(this)); tabbedPane.removeChangeListener(changeListener); tabbedPane.setSelectedIndex(index); tabbedPane.addChangeListener(changeListener); numTabs++; } } public void removeTab(int index) { tabbedPane.remove(index); numTabs--; if (index == numTabs - 1 && index > 0) { tabbedPane.setSelectedIndex(numTabs - 2); } else { tabbedPane.setSelectedIndex(index); } if (numTabs == 1) { addNewTab(); } } public static void main(String[] args) { new DemoCustomJTabbedPane(); } }
Các bạn chú ý ở dòng 51 chúng ta thêm tab đầu tiên vào như bình thường, sau đó tại dòng 53 chúng ta thực hiện setTabComponentAt để thay cái tab mặc định bằng tab mà chúng ta tùy biến (code bên dưới) có nút đóng tab.
Dòng 58 thực hiện lắng nghe sự kiện chuyển tab của JTabbedPane.
Phương thức addNewTab thực hiện thêm tab mới. Bạn để ý dòng 91, sau khi thêm tab mới thì chúng ta cần ngắt sự lắng nghe của JTabbedPane vì nếu không thì cái điều kiện tabbedPane.getSelectedIndex() == index luôn đúng và nó sẽ thực hiện thêm rất nhiều tab. Sau khi ngắt sự lắng nghe, chúng ta đặt tab được chọn là tab vừa tạo rồi lại gắn sự lắng nghe trở lại.
Phương thức removeTab thực hiện đóng tab tại vị trí index. Chú ý cái lệnh if-else đầu tiên là kiểm tra nếu đóng tab cuối cùng thì sẽ tập trung về tab trước nó, nếu không sẽ tập trung về tab sau nó. Lệnh if thứ 2 để kiểm tra nếu đóng hết các tab (chỉ còn cái nút để thêm tab) thì tự động tạo ra 1 tab mới.
Thêm nút đóng tab cho JTabbedPane
Về nút đóng tab, ta cần thực hiện tùy biến thành phần (component) của tab là 1 JPanel, JPanel này chứa 1 JLabel hiển thị tiêu đề (title) tab và 1 JButton để bắt sự kiện đóng tab như thế này
Dưới đây là code tùy biến Tab
package nguyenvanquan7826.JTabbedPane; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import javax.swing.border.EtchedBorder; /** * --------------------- @author nguyenvanquan7826 --------------------- * ------------------ website: cachhoc.net ------------------- * ---------- date: Jul 24, 2014 - filename: DemoButtonTab.java ---------- */ public class DemoCustomTab extends JPanel { DemoCustomJTabbedPane customJTabbedPane; /** JPanel contain a JLabel and a JButton to close */ public DemoCustomTab(DemoCustomJTabbedPane customJTabbedPane) { this.customJTabbedPane = customJTabbedPane; setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); setBorder(new EmptyBorder(5, 2, 2, 2)); setOpaque(false); addLabel(); add(new CustomButton("x")); } private void addLabel() { JLabel label = new JLabel() { /** set text for JLabel, it will title of tab */ public String getText() { int index = customJTabbedPane.tabbedPane .indexOfTabComponent(DemoCustomTab.this); if (index != -1) { return customJTabbedPane.tabbedPane.getTitleAt(index); } return null; } }; /** add more space between the label and the button */ label.setBorder(new EmptyBorder(0, 0, 0, 10)); add(label); } class CustomButton extends JButton implements MouseListener { public CustomButton(String text) { int size = 15; setText(text); /** set size for button close */ setPreferredSize(new Dimension(size, size)); setToolTipText("close the Tab"); /** set transparent */ setContentAreaFilled(false); /** set border for button */ setBorder(new EtchedBorder()); /** don't show border */ setBorderPainted(false); setFocusable(false); /** add event with mouse */ addMouseListener(this); } /** when click button, tab will close */ @Override public void mouseClicked(MouseEvent e) { int index = customJTabbedPane.tabbedPane .indexOfTabComponent(DemoCustomTab.this); if (index != -1) { customJTabbedPane.removeTab(index); } } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } /** show border button when mouse hover */ @Override public void mouseEntered(MouseEvent e) { setBorderPainted(true); setForeground(Color.RED); } /** hide border when mouse not hover */ @Override public void mouseExited(MouseEvent e) { setBorderPainted(false); setForeground(Color.BLACK); } } }
Tại class này chúng ta có 1 đối tượng DemoCustomJTabbedPane, đó là đối tượng được truyền sang mà chúng ta tạo ra trong code trước đó mà các bạn để ý là ta có gọi new DemoCustomTab(this). Mục đích truyền nó sang là để lấy được JTabbedPane đang thực hiện, chúng ta sẽ thao tác với nó và còn để thực hiện đóng tab nữa. Cụ thể là:
Các bạn thấy class này kế thừa JPanel do đó nó là 1 JPanel như chúng ta muốn. Chúng ta thực hiện đặt JLabel và JButton vào nó tại các dòng 30 và 31.
Phương thức addLabel thực hiện thêm JLabel vào JPanel. Tuy nhiên chúng ta cần phải lấy tiêu đề của tab để hiện lên JLabel bằng cách viết đè hàm getText của JLabel và thực hiện tìm đến tab hiện tại của JTabbedPane để lấy tiêu đề.
Việc thêm JButton vào rất đơn giản với việc tạo ra 1 JButton với text là “x” tuy nhiên nếu bạn làm như vậy thì JButton đó sẽ rất to và xấu, do đó mình cũng đã tùy biến lại JButton này bằng class CustomButton.
Class này implements MouseListener để thực hiện bắt sự kiện với chuột. Khi di chuột qua nút close thì phương thức mouseEntered sẽ thực thi và màu sẽ chuyển thành đỏ, đường viền cũng hiện lên và 1 tool tip text hiện lên “close the tab”. Khi đưa chuột ra ngoài thì trở về trạng thái bình thường qua phương thức mouseExited.
Để có được những điều trên thì cần đặt một số giá trị cho nó như:
– Đặt kích thước phù hợp: setPreferredSize(new Dimension(size, size));
– Làm button trong suốt: setContentAreaFilled(false);
– Đặt đường viền: setBorder(new EtchedBorder()); setBorderPainted(false);
Khi click vào button đóng tab thì phương thức mouseClicked được gọi và thực hiện gọi phương thức removeTab của DemoCustomJTabbedPane để đóng tab.
anh ơi cái custombutton có nhất thiết phải là inner class ko anh
Không nhé bạn.
package TabbedPane;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import javax.swing.JLabel;
import javax.swing.JButton;
@SuppressWarnings(“serial”)
public class CustomPanel extends javax.swing.JPanel{
private TabbedPane tabbedPane;
private JLabel labelTitle;
private JButton buttonClose;
public CustomPanel(TabbedPane obj) {
tabbedPane = obj;
setOpaque(true);
setSize(75, 25);
setLayout(new java.awt.GridLayout());
add(createLabelTitle());
add(createButtonClose());
listenEvent();
}
private JLabel createLabelTitle() {
labelTitle = new JLabel() {
public String getText() {
int index = tabbedPane.tabbedPane.indexOfComponent(CustomPanel.this);
if(index != -1)
return tabbedPane.tabbedPane.getTitleAt(index);
else
return null;
}
};
return labelTitle;
}
private JButton createButtonClose() {
buttonClose = new JButton(“x”);
buttonClose.setToolTipText(“Close This Tab”);
buttonClose.setPreferredSize(new java.awt.Dimension(15, 15));
buttonClose.setBorderPainted(false);
buttonClose.setContentAreaFilled(false);
buttonClose.setBorder(new javax.swing.border.EtchedBorder());
buttonClose.setFocusable(false);
return buttonClose;
}
private void listenEvent() {
for(java.awt.Component comp: getComponents())
if(comp instanceof JButton) {
JButton button = (JButton)comp;
button.addMouseListener
(
new MouseListener() {
public void mouseClicked(MouseEvent event) {
int index = tabbedPane.tabbedPane.indexOfComponent(CustomPanel.this);
if(index != -1)
tabbedPane.removeTabClick(index);
else return;
}
public void mouseEntered(MouseEvent event) {
buttonClose.setBorderPainted(true);
buttonClose.setBackground(java.awt.Color.red);
}
public void mouseExited(MouseEvent event) {
buttonClose.setBorderPainted(false);
buttonClose.setBackground(java.awt.Color.black);
}
public void mousePressed(MouseEvent event) {
}
public void mouseReleased(MouseEvent event) {
}
}
);
}
else;
}
}
no ko hien labeltitle va buttonclose cung ko bat duoc su kien
Bạn có sửa hoặc bỏ bớt đoạn nào đi không? Mình làm vẫn hiện như hình mà
code của anh thì customButtom là 1 class. còn của em thì button là 1 object của class JButton. vì cho dù nó extends JButton thì cũng là 1 Button thôi. em ko làm giống anh vì code kiểu như trên là style code của em. code chỉ sửa lại như thế thôi chứ chẳng bớt cái gi hết. còn code anh thì chạy ok rồi. cái code của em thêm tabbedpane ok nên ko post lên đây, nó ko hiện title tabbedpane và ko removetab được.(code removeTAB cũng ok). nếu để CútomButton là 1 class(ko inner) cũng chạy được. mà sao add vào panel như này lại ko hiện title và button ko bắt được sự kiện click chuột nhưng sự kiện set trạng thái nền khi rê chuột là vẫn được.
sory anh. do code lại chứ ko copy rồi paste nên sai sót nhỏ. indexOfTabComponent mà thiếu chữ Tab. thanks anh đã rep.