Series Java Swing Bài 14: JTextArea và JScrollPane: Xử lý văn bản nhiều dòng và thanh cuộn.
Ở bài trước, JTextField đã làm rất tốt nhiệm vụ thu thập dữ liệu ngắn (tài khoản, email). Tuy nhiên, khi bạn cần xây dựng tính năng như: form nhập ghi chú (Notes), khung chat, hoặc cửa sổ hiển thị log hệ thống, bạn sẽ cần đến một vùng không gian lớn hơn. Đó là lúc JTextArea xuất hiện.
1. JTextArea: Vùng nhập liệu đa dòng
JTextArea cho phép hiển thị và nhập văn bản trên nhiều dòng. Bạn có thể khởi tạo nó với số hàng (rows) và số cột (columns) mặc định để ước lượng kích thước ban đầu.
Java
// Tạo vùng văn bản cao 10 hàng, rộng 30 ký tự
JTextArea textArea = new JTextArea(10, 30);
Các phương thức thao tác dữ liệu cốt lõi
Ngoài getText() và setText() giống như JTextField, JTextArea có một phương thức cực kỳ mạnh mẽ để làm việc với dữ liệu nối tiếp (như ứng dụng chat hay log):
-
append(String str): Nối thêm chuỗi mới vào cuối đoạn văn bản hiện tại mà không làm mất dữ liệu cũ. Việc này tối ưu hiệu năng hơn rất nhiều so với việc gọisetText(getText() + str). -
insert(String str, int pos): Chèn một chuỗi vào vị trí index (pos) bất kỳ.
Kiểm soát ngắt dòng (Word Wrap)
Mặc định, nếu bạn gõ một câu quá dài, JTextArea sẽ cứ thế kéo dài dòng đó ra vô tận theo chiều ngang. Để ép văn bản tự động xuống dòng khi chạm đến lề phải của ô nhập, bạn cần bật 2 thuộc tính sau:
Java
// Bật tính năng tự động xuống dòng
textArea.setLineWrap(true);
// Đảm bảo khi xuống dòng sẽ ngắt theo từng chữ (Word),
// thay vì cắt ngang giữa một từ gây sai chính tả.
textArea.setWrapStyleWord(true);
2. Vấn đề tràn viền và "Vị cứu tinh" JScrollPane
Nếu bạn thiết kế một JTextArea có 10 hàng, nhưng người dùng lại copy/paste một bài văn dài 50 hàng vào đó thì sao? Kết quả: Đoạn văn bản sẽ tràn ra ngoài, phần nội dung dư thừa bị che khuất hoàn toàn, và JTextArea không tự sinh ra thanh cuộn (scrollbar).
Trong kiến trúc Swing, các Component đồ họa không tự quản lý thanh cuộn. Thay vào đó, Java sử dụng một Design Pattern là Decorator, cung cấp một Container đặc biệt tên là JScrollPane.
Quy tắc bất di bất dịch: Bạn KHÔNG bao giờ add(textArea) trực tiếp vào cửa sổ JFrame. Thay vào đó, bạn phải "bọc" textArea vào bên trong một JScrollPane, sau đó mới add(scrollPane) lên giao diện.
Java
// 1. Khởi tạo vùng văn bản
JTextArea textArea = new JTextArea(10, 30);
// 2. Bọc vùng văn bản vào thanh cuộn
JScrollPane scrollPane = new JScrollPane(textArea);
// 3. Thêm thanh cuộn vào giao diện (Tuyệt đối không add textArea)
frame.add(scrollPane);
Quản lý chính sách hiển thị thanh cuộn
Bạn có thể ra lệnh cho JScrollPane lúc nào thì hiện thanh cuộn ngang (Horizontal) và thanh cuộn dọc (Vertical) thông qua các hằng số:
-
AS_NEEDED(Mặc định): Chỉ hiện thanh cuộn khi nội dung thực sự bị tràn. -
ALWAYS: Luôn luôn vẽ thanh cuộn (dù bị mờ đi khi chưa cần thiết) để giữ cố định Layout không bị giật/nhảy khi thanh cuộn xuất hiện. -
NEVER: Giấu nhẹm thanh cuộn đi.
Java
// Luôn hiển thị thanh cuộn dọc
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
// Ẩn hoàn toàn thanh cuộn ngang (vì đã có setLineWrap ở trên)
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
3. Source Code Thực Chiến: Cửa sổ Console Log mini
Đoạn code dưới đây kết hợp BorderLayout, JTextArea và JScrollPane để tạo ra một giao diện khung Chat / Log hệ thống kinh điển: Nhập chữ ở dưới, hiển thị lịch sử ở trên.
Java
import javax.swing.*;
import java.awt.*;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class JTextAreaDemo extends JFrame {
public JTextAreaDemo() {
setTitle("Hệ thống Log - JTextArea & JScrollPane");
setSize(500, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(new BorderLayout(10, 10)); // Khoảng cách giữa các vùng là 10px
// ==========================================
// VÙNG TRUNG TÂM (CENTER): Hiển thị Log
// ==========================================
JTextArea txtLog = new JTextArea();
txtLog.setEditable(false); // Biến thành vùng chỉ đọc
txtLog.setLineWrap(true);
txtLog.setWrapStyleWord(true);
txtLog.setFont(new Font("Consolas", Font.PLAIN, 14)); // Font code dễ nhìn
txtLog.setBackground(new Color(43, 43, 43)); // Nền tối (Dark Mode)
txtLog.setForeground(new Color(169, 183, 198)); // Chữ sáng
// BỌC JTEXTAREA VÀO JSCROLLPANE
JScrollPane scrollPane = new JScrollPane(txtLog);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
// Tạo viền (Border) mỏng cho cuộn
scrollPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(scrollPane, BorderLayout.CENTER);
// ==========================================
// VÙNG PHÍA NAM (SOUTH): Khu vực nhập liệu
// ==========================================
JPanel inputPanel = new JPanel(new BorderLayout(5, 0));
inputPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
JTextField txtInput = new JTextField();
txtInput.setFont(new Font("Arial", Font.PLAIN, 14));
JButton btnSend = new JButton("Ghi Log");
btnSend.setFocusPainted(false);
inputPanel.add(txtInput, BorderLayout.CENTER);
inputPanel.add(btnSend, BorderLayout.EAST);
add(inputPanel, BorderLayout.SOUTH);
// ==========================================
// XỬ LÝ SỰ KIỆN NÚT BẤM VÀ PHÍM ENTER
// ==========================================
// Dùng Lambda gán chung 1 logic cho cả nút bấm và khi gõ phím Enter ở ô input
Action appendLogAction = new AbstractAction() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
String input = txtInput.getText().trim();
if (!input.isEmpty()) {
String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
// Nối thêm text mới bằng append() thay vì setText()
txtLog.append(String.format("[%s] INFO: %s\n", time, input));
txtInput.setText(""); // Xóa rỗng ô nhập liệu
}
}
};
btnSend.addActionListener(appendLogAction);
txtInput.addActionListener(appendLogAction); // Kích hoạt khi bấm Enter trong ô Text
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new JTextAreaDemo().setVisible(true));
}
}
Bạn hãy chạy thử chương trình, gõ liên tục hoặc copy/paste các chuỗi thật dài và bấm Enter để thấy JTextArea tự động ngắt dòng và JScrollPane mượt mà cuộn màn hình như thế nào.
All Rights Reserved