首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 日期時間選擇器組件:封裝為 JButton 的彈出式日曆時鐘

     編輯:Java 程式設計入門

設計目標

  • **零依賴第三方庫**,純 JDK (Swing/AWT) 實現
  • **封裝為 JButton 子類**,直接放入任意表單/工具欄,點擊彈出模態對話框選擇日期+小時
  • 支援 Application / Applet 雙模式
  • 可自定義顏色、字體、年份範圍、默認格式 (yyyy年MM月dd日HH時)

核心類結構

public class DateChooserJButton extends JButton {
    private final String prefixLabel;      // 按鈕前綴文本,如 "出生日期:"
    private DateChooser dateChooser;       // 懶加載的彈出面板 (JPanel)
    // 構造器、日期獲取/設置、事件綁定...
    private class DateChooser extends JPanel implements ActionListener, ChangeListener {
        // 年/月/時 Spinner、日曆網格 (6x7 JButton)、顏色主題、邏輯控制
    }
}

關鍵功能模塊

1. 按鈕文本與日期雙向綁定

// 設置日期 → 更新按鈕文本
public void setDate(Date date) {
    super.setText(prefixLabel + defaultFormat.format(date));
}

// 獲取日期 → 解析按鈕文本
public Date getDate() {
    String text = getText().substring(prefixLabel.length());
    try { return defaultFormat.parse(text); }
    catch (ParseException e) { return new Date(); }
}

// 禁用父類 setText(String),強制走日期邏輯
@Override public void setText(String s) { setDate(defaultFormat.parse(s)); }

2. 彈出對話框定位邏輯


private void showDateChooser(Point screenPos) {
    Frame owner = (Frame) SwingUtilities.getWindowAncestor(this);
    if (dialog == null || dialog.getOwner() != owner) createDialog(owner);
    dialog.setLocation(getAppropriateLocation(owner, screenPos)); // 防止超出屏幕邊界
    flushWeekAndDay(); // 根據當前日期刷新日曆網格
    dialog.setVisible(true);
}

private Point getAppropriateLocation(Frame owner, Point pos) {
    Point p = new Point(pos);
    int overflowX = pos.x + width - (owner.getX() + owner.getWidth());
    int overflowY = pos.y + height - (owner.getY() + owner.getHeight());
    if (overflowX > 0) p.x -= overflowX;
    if (overflowY > 0) p.y -= overflowY;
    return p;
}

3. 日曆網格渲染與交互

  • 表頭:周日~周六 (紅/藍區分)
  • 日期按鈕 6x7:ActionCommand 編碼 0~41,點擊觸發 actionPerformed 更新選中日期並高亮
  • 控制行:JSpinner (年/月/時) + ChangeListener 連動刷新網格
  • 今日高亮**:橙色背景,切換月份自動重算

4. 核心邏輯方法


// 根據年/月/當前日期重繪 6x7 網格
private void flushWeekAndDay() {
    Calendar c = getCalendar(); // 基於當前按鈕日期
    c.set(Calendar.DAY_OF_MONTH, 1);
    int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH);
    int firstWeekDay = c.get(Calendar.DAY_OF_WEEK); // 1=周日
    int dayNo = 2 - firstWeekDay; // 網格起始數字 (可為負)
    for (int i = 0; i < 6; i++) {
        for (int j = 0; j < 7; j++) {
            String txt = (dayNo >= 1 && dayNo <= maxDay) ? String.valueOf(dayNo) : "";
            daysButton[i][j].setText(txt);
            dayNo++;
        }
    }
    highlightToday(); // 標記今日
}

// Spinner 變更監聽
public void stateChanged(ChangeEvent e) {
    JSpinner src = (JSpinner) e.getSource();
    Calendar c = getCalendar();
    if ("Hour".equals(src.getName())) {
        c.set(Calendar.HOUR_OF_DAY, (Integer) src.getValue());
        setDate(c.getTime());
        return;
    }
    // 年/月變更需重繪網格
    if ("Year".equals(src.getName())) c.set(Calendar.YEAR, (Integer) src.getValue());
    else c.set(Calendar.MONTH, (Integer) src.getValue() - 1);
    setDate(c.getTime());
    flushWeekAndDay();
}

// 日期按鈕點擊
public void actionPerformed(ActionEvent e) {
    JButton btn = (JButton) e.getSource();
    if (btn.getText().isEmpty()) return; // 非本月日期不可選
    highlightToday(); // 先清除舊高亮
    btn.setForeground(todayBackColor); // 新高亮
    int day = Integer.parseInt(btn.getText());
    Calendar c = getCalendar();
    c.set(Calendar.DAY_OF_MONTH, day);
    setDate(c.getTime());
}

完整可用代碼 (精簡版,保留核心邏輯)

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Calendar;
import java.text.*;

public class DateChooserJButton extends JButton {
    private static final SimpleDateFormat FMT = new SimpleDateFormat("yyyy年MM月dd日HH時");
    private final String prefix;
    private DateChooser popup;

    public DateChooserJButton(String prefix, Date date) {
        this.prefix = prefix == null ? "" : prefix;
        setDate(date);
        setBorderPainted(false);
        setContentAreaFilled(false);
        setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        addActionListener(e -> showPopup());
    }

    public DateChooserJButton() { this("", new Date()); }
    public DateChooserJButton(Date d) { this("", d); }

    public void setDate(Date d) { super.setText(prefix + FMT.format(d)); }
    public Date getDate() {
        try { return FMT.parse(getText().substring(prefix.length())); }
        catch (ParseException ex) { return new Date(); }
    }
    @Override public void setText(String t) { setDate(FMT.parse(t)); }
    @Override public void addActionListener(ActionListener l) {} // 禁用外部監聽

    private void showPopup() {
        if (popup == null) popup = new DateChooser();
        Point p = getLocationOnScreen();
        p.y += getHeight();
        popup.showAt(p);
    }

    private class DateChooser extends JPanel implements ActionListener, ChangeListener {
        private final JSpinner ySpin, mSpin, hSpin;
        private final JButton[][] days = new JButton[6][7];
        private final JDialog dlg;

        DateChooser() {
            setLayout(new BorderLayout());
            setBorder(BorderFactory.createLineBorder(Color.GRAY, 2));
            add(createHeader(), BorderLayout.NORTH);
            add(createCalendar(), BorderLayout.CENTER);

            dlg = new JDialog((Frame) SwingUtilities.getWindowAncestor(DateChooserJButton.this), "選擇日期時間", true);
            dlg.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
            dlg.getContentPane().add(this);
            dlg.pack();
            dlg.setSize(260, 240);
        }

        private JPanel createHeader() {
            JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
            p.setBackground(new Color(0xDDDDFF));
            Calendar now = Calendar.getInstance(); now.setTime(getDate());
            ySpin = new JSpinner(new SpinnerNumberModel(now.get(Calendar.YEAR), 1900, 2100, 1));
            ySpin.setEditor(new JSpinner.NumberEditor(ySpin, "####")); ySpin.setName("Y");
            mSpin = new JSpinner(new SpinnerNumberModel(now.get(Calendar.MONTH) + 1, 1, 12, 1)); mSpin.setName("M");
            hSpin = new JSpinner(new SpinnerNumberModel(now.get(Calendar.HOUR_OF_DAY), 0, 23, 1)); hSpin.setName("H");
            ySpin.addChangeListener(this); mSpin.addChangeListener(this); hSpin.addChangeListener(this);
            p.add(ySpin); p.add(new JLabel("年"));
            p.add(mSpin); p.add(new JLabel("月"));
            p.add(hSpin); p.add(new JLabel("時"));
            return p;
        }

        private JPanel createCalendar() {
            JPanel p = new JPanel(new GridLayout(7, 7));
            p.setBackground(Color.WHITE);
            String[] ws = {"日","一","二","三","四","五","六"};
            for (int i = 0; i < 7; i++) {
                JLabel l = new JLabel(ws[i], SwingConstants.RIGHT);
                l.setForeground(i == 0 || i == 6 ? Color.RED : Color.BLUE);
                p.add(l);
            }
            for (int i = 0; i < 6; i++) for (int j = 0; j < 7; j++) {
                JButton b = new JButton();
                b.setBorder(null); b.setHorizontalAlignment(SwingConstants.RIGHT);
                b.setActionCommand(i * 7 + j + ""); b.addActionListener(this);
                b.setBackground(Color.WHITE);
                b.setForeground(j == 0 || j == 6 ? Color.RED : Color.BLACK);
                days[i][j] = b; p.add(b);
            }
            return p;
        }

        void showAt(Point loc) {
            Frame owner = (Frame) SwingUtilities.getWindowAncestor(DateChooserJButton.this);
            if (dlg.getOwner() != owner) { dlg.dispose(); dlg.setOwner(owner); }
            dlg.setLocation(fitOnScreen(owner, loc));
            refreshGrid();
            dlg.setVisible(true);
        }

        private Point fitOnScreen(Frame o, Point p) {
            Point r = new Point(p);
            int ox = p.x + dlg.getWidth() - (o.getX() + o.getWidth());
            int oy = p.y + dlg.getHeight() - (o.getY() + o.getHeight());
            if (ox > 0) r.x -= ox;
            if (oy > 0) r.y -= oy;
            return r;
        }

        private void refreshGrid() {
            Calendar c = Calendar.getInstance(); c.setTime(getDate());
            c.set(Calendar.DAY_OF_MONTH, 1);
            int max = c.getActualMaximum(Calendar.DAY_OF_MONTH);
            int start = 2 - c.get(Calendar.DAY_OF_WEEK);
            for (int i = 0; i < 6; i++) for (int j = 0; j < 7; j++) {
                days[i][j].setText((start >= 1 && start <= max) ? String.valueOf(start) : "");
                start++;
            }
            highlightToday();
        }

        private void highlightToday() {
            Calendar c = Calendar.getInstance(); c.setTime(getDate());
            int day = c.get(Calendar.DAY_OF_MONTH);
            c.set(Calendar.DAY_OF_MONTH, 1);
            int idx = day - 2 + c.get(Calendar.DAY_OF_WEEK);
            int r = idx / 7, col = idx % 7;
            for (int i = 0; i < 6; i++) for (int j = 0; j < 7; j++)
                days[i][j].setForeground((i == r && j == col) ? Color.ORANGE : (j == 0 || j == 6 ? Color.RED : Color.BLACK));
        }

        public void stateChanged(ChangeEvent e) {
            JSpinner s = (JSpinner) e.getSource();
            Calendar c = Calendar.getInstance(); c.setTime(getDate());
            if ("H".equals(s.getName())) { c.set(Calendar.HOUR_OF_DAY, (Integer)s.getValue()); setDate(c.getTime()); return; }
            if ("Y".equals(s.getName())) c.set(Calendar.YEAR, (Integer)s.getValue());
            else c.set(Calendar.MONTH, (Integer)s.getValue() - 1);
            setDate(c.getTime());
            refreshGrid();
        }

        public void actionPerformed(ActionEvent e) {
            JButton b = (JButton) e.getSource();
            if (b.getText().isEmpty()) return;
            highlightToday();
            b.setForeground(Color.ORANGE);
            Calendar c = Calendar.getInstance(); c.setTime(getDate());
            c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(b.getText()));
            setDate(c.getTime());
        }
    }

    // 測試主程序
    public static void main(String[] args) {
        JFrame f = new JFrame("測試日期選擇器");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new FlowLayout());
        f.add(new DateChooserJButton("開始:", new Date()));
        f.add(new DateChooserJButton("結束:", new Date()));
        f.pack(); f.setLocationRelativeTo(null); f.setVisible(true);
    }
}

使用示例

// 表單中直接添加
formPanel.add(new DateChooserJButton("報名截止:", new Date()));

// 獲取用戶選擇的日期
Date deadline = ((DateChooserJButton) component).getDate();

優缺點與擴展方向

優點局限擴展建議
無外部依賴、體積極小 (~8KB)僅支援 日期+小時,無分鐘/秒增加 minuteSpin、secondSpin
封裝度高,像原生組件一樣用外觀固定,難以深度定制主題暴露 Color/Font 設置器、支援 Look&Feel
模態對話框阻塞流程,邏輯簡單Applet 簽名權限可能受限提供非模態 JPopupMenu 版本
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved