[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,…

tùy biến JTabbedPane

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

thêm nút đóng tab vào JTabbedPane

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.

Đọc bản tiến Anh tại đây