Series Java Swing Bài 17: JSpinner: Xử lý tăng/giảm số liệu và chọn ngày tháng.
JSpinner là một Component nhìn lướt qua rất giống JTextField, nhưng nó được trang bị thêm hai mũi tên Lên/Xuống ở bên phải.
Mục đích sinh ra của JSpinner là để người dùng chọn một giá trị trong một chuỗi số liệu tuần tự (như số lượng hàng hóa từ 1 đến 100, số tuổi, hoặc ngày tháng năm) mà không cần phải gõ bàn phím, hạn chế tối đa việc nhập sai định dạng (như nhập chữ "abc" vào ô yêu cầu nhập số tuổi).
1. Kiến trúc của JSpinner: Mô hình SpinnerModel
Giống như JComboBox hay JTable, JSpinner tách biệt phần giao diện và phần logic (dữ liệu). Dữ liệu bên trong JSpinner được quản lý bởi một bộ máy gọi là SpinnerModel.
Java cung cấp sẵn 3 loại Model phổ biến nhất cho 3 tình huống sử dụng khác nhau:
-
SpinnerNumberModel: Dùng cho số nguyên hoặc số thực. -
SpinnerDateModel: Dùng cho thời gian (Ngày, tháng, năm, giờ, phút). -
SpinnerListModel: Dùng cho mảng chuỗi (Giống JComboBox nhưng dùng mũi tên bấm).
2. SpinnerNumberModel: Chọn số lượng (Số nguyên / Số thực)
Đây là Model được dùng nhiều nhất, đặc biệt trong các Form bán hàng để điều chỉnh cột "Số lượng" (Quantity).
Cấu trúc khởi tạo: SpinnerNumberModel(Giá_trị_ban_đầu, Tối_thiểu, Tối_đa, Bước_nhảy)
Java
// Ví dụ: Chọn tuổi (Bắt đầu từ 18, nhỏ nhất 0, lớn nhất 120, mỗi lần bấm mũi tên tăng/giảm 1)
SpinnerNumberModel ageModel = new SpinnerNumberModel(18, 0, 120, 1);
JSpinner spinnerAge = new JSpinner(ageModel);
// Lấy giá trị sau khi người dùng thao tác
// Lưu ý: getValue() luôn trả về kiểu Object, bắt buộc phải ép kiểu (Casting)
int selectedAge = (int) spinnerAge.getValue();
Lưu ý: Nếu bạn muốn giới hạn vô tận (không có Min hoặc Max), bạn có thể truyền null thay thế cho số.
3. SpinnerDateModel: Chọn ngày giờ thông minh
Nếu bạn muốn tạo một Form đặt lịch hẹn, bạn cần lấy ngày/giờ. SpinnerDateModel cho phép người dùng click vào phần Ngày, phần Tháng, hoặc phần Năm và bấm mũi tên để tăng/giảm riêng biệt phần đó.
Tuy nhiên, JSpinner mặc định sẽ hiển thị chuỗi ngày tháng cực kỳ thô và dài dòng của Java (kiểu Sat Sep 26 10:05:54 ICT 2026). Bạn phải kết hợp với JSpinner.DateEditor để ép nó hiển thị theo chuẩn Việt Nam.
Java
import java.util.Date;
import java.util.Calendar;
// 1. Tạo DateModel (Khởi tạo bằng giờ hiện tại)
SpinnerDateModel dateModel = new SpinnerDateModel(new Date(), null, null, Calendar.DAY_OF_MONTH);
JSpinner spinnerDate = new JSpinner(dateModel);
// 2. Định dạng lại thành chuẩn Việt Nam (Ngày/Tháng/Năm)
JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(spinnerDate, "dd/MM/yyyy");
spinnerDate.setEditor(dateEditor);
// Lấy giá trị
Date selectedDate = (Date) spinnerDate.getValue();
4. Chặn nhập liệu bằng bàn phím (Chỉ cho bấm mũi tên)
Mặc dù JSpinner có mũi tên, phần ô chữ bên trong thực chất là một JFormattedTextField. Người dùng hoàn toàn có thể click chuột vào đó và... gõ bậy (nhập chữ vào ô số lượng).
Nếu bạn muốn an toàn tuyệt đối, ép người dùng chỉ được phép bấm mũi tên Lên/Xuống, bạn phải vô hiệu hóa khả năng chỉnh sửa của ô Text bên trong:
Java
JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1));
// Lấy ô TextField bên trong ra và khóa lại (Read-only)
JFormattedTextField txtInside = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
txtInside.setEditable(false);
5. Source Code Thực Chiến: Form Chọn Mua Hàng & Đặt Lịch
Đoạn code dưới đây sử dụng GridBagLayout để thiết kế một form cho phép chọn "Số lượng mua" (dùng NumberModel) và "Thời gian giao hàng" (dùng DateModel có định dạng Giờ:Phút).
Java
import javax.swing.*;
import java.awt.*;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class JSpinnerDemo extends JFrame {
public JSpinnerDemo() {
setTitle("Đặt Hàng - JSpinner Demo");
setSize(400, 250);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
// ==========================================
// DÒNG 1: SPINNER SỐ LƯỢNG (Number Model)
// ==========================================
gbc.gridx = 0; gbc.gridy = 0;
gbc.weightx = 0.0;
add(new JLabel("Số lượng (Max 50):"), gbc);
gbc.gridx = 1; gbc.gridy = 0;
gbc.weightx = 1.0;
// Bắt đầu: 1, Min: 1, Max: 50, Bước nhảy: 1
SpinnerNumberModel qtyModel = new SpinnerNumberModel(1, 1, 50, 1);
JSpinner spinQuantity = new JSpinner(qtyModel);
// Khóa không cho nhập tay, chỉ cho bấm mũi tên
JFormattedTextField txtQty = ((JSpinner.DefaultEditor) spinQuantity.getEditor()).getTextField();
txtQty.setEditable(false);
txtQty.setBackground(Color.WHITE); // Giữ nền trắng để trông không giống bị disable
add(spinQuantity, gbc);
// ==========================================
// DÒNG 2: SPINNER THỜI GIAN GIAO (Date Model)
// ==========================================
gbc.gridx = 0; gbc.gridy = 1;
gbc.weightx = 0.0;
add(new JLabel("Giờ giao hàng:"), gbc);
gbc.gridx = 1; gbc.gridy = 1;
gbc.weightx = 1.0;
SpinnerDateModel timeModel = new SpinnerDateModel(new Date(), null, null, Calendar.HOUR_OF_DAY);
JSpinner spinTime = new JSpinner(timeModel);
// Định dạng hiển thị chỉ có Giờ và Phút
JSpinner.DateEditor timeEditor = new JSpinner.DateEditor(spinTime, "HH:mm");
spinTime.setEditor(timeEditor);
add(spinTime, gbc);
// ==========================================
// DÒNG 3: NÚT XÁC NHẬN
// ==========================================
gbc.gridx = 0; gbc.gridy = 2;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.CENTER;
JButton btnOrder = new JButton("Xác nhận Đặt Hàng");
btnOrder.addActionListener(e -> {
// Lấy và ép kiểu dữ liệu từ Spinner
int quantity = (int) spinQuantity.getValue();
Date deliveryTime = (Date) spinTime.getValue();
// Format lại giờ để hiển thị ra thông báo
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
String msg = String.format("Bạn đã đặt %d sản phẩm.\nSẽ giao lúc: %s",
quantity, sdf.format(deliveryTime));
JOptionPane.showMessageDialog(this, msg);
});
add(btnOrder, gbc);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new JSpinnerDemo().setVisible(true));
}
}
Hãy chạy thử chương trình, thử click vào số "Giờ" ở dòng thứ hai rồi bấm mũi tên lên/xuống, sau đó làm tương tự với số "Phút". Bạn sẽ thấy nó hoạt động cực kỳ mượt mà đúng chuẩn Form đặt lịch thực tế.
All rights reserved