javaSwing的日期组件

发布时间 2023-12-13 10:29:34作者: 帅气的我

先看效果:

 1 JToolBar jToolBar = new JToolBar();
 2 
 3 DptLocalDatePicker searchDate = new DptLocalDatePicker(null, DateTimeFormatter.ofPattern("yyyy-MM-dd")) {
 4                 @Override
 5                 public Dimension getMaximumSize() {
 6                     return new Dimension(140, 35);
 7                 }
 8 
 9                 @Override
10                 public void setValue(LocalDate value) {
11                     super.setValue(value);
12                     initData();
13                 }
14             };
15 
16             searchDate.setFocusable(false);
17             searchDate.setPreferredSize(new Dimension(140, 35));
18         }
19 
20 JButton clearDateBtn = new JButton(new FlatSVGIcon("icons/deletes.svg", 20, 20));
21         clearDateBtn.addActionListener(new ActionListener() {
22             @Override
23             public void actionPerformed(ActionEvent e) {
24                 searchDate.setValue(null);
25             }
26         });
27 
28 jToolBar.add(searchDate);
29 jToolBar.add(clearDateBtn);
业务应用
 <properties>
     <flatlaf.version>3.1.1</flatlaf.version>
</properties>

<dependency>
    <groupId>com.formdev</groupId>
     <artifactId>flatlaf</artifactId>
     <version>${flatlaf.version}</version>
      <scope>compile</scope>
</dependency>

<dependency>
     <groupId>com.formdev</groupId>
      <artifactId>flatlaf-intellij-themes</artifactId>
      <version>${flatlaf.version}</version>
</dependency>

<dependency>
     <groupId>com.formdev</groupId>
      <artifactId>flatlaf-extras</artifactId>
      <version>${flatlaf.version}</version>
</dependency>
               
pom.xml相关依赖
  1 package com.example.dpt.component;
  2 
  3 import com.formdev.flatlaf.extras.FlatSVGIcon;
  4 import com.formdev.flatlaf.ui.FlatComboBoxUI;
  5 
  6 import javax.swing.*;
  7 import javax.swing.event.PopupMenuEvent;
  8 import javax.swing.event.PopupMenuListener;
  9 import java.awt.*;
 10 import java.time.LocalDate;
 11 import java.time.format.DateTimeFormatter;
 12 import java.time.format.FormatStyle;
 13 import java.time.temporal.TemporalAccessor;
 14 
 15 
 16 /**
 17  * @Description: 日期组件
 18  * @param:
 19  * @return:
 20  */
 21 public class DptLocalDatePicker extends JComboBox<LocalDate> {
 22 
 23     public final DefaultComboBoxModel<LocalDate> comboModel = new DefaultComboBoxModel<>();
 24     public LocalDate min;
 25     public LocalDate max;
 26     public DptMonthView DptMonthView;
 27     public final JPopupMenu popupMenu = new JPopupMenu();
 28 
 29     /**
 30      * Constructs a LocalDateCombo with today's date, no upper or lower limits, formatted in a medium
 31      * style.
 32      *
 33      * @see FormatStyle#MEDIUM
 34      */
 35     public DptLocalDatePicker() {
 36         this(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));
 37     }
 38 
 39     /**
 40      * Constructs a LocalDateCombo with today's date and no upper or lower limits, formatted according
 41      * to the provided formatter.
 42      *
 43      * @param formatter Formats the date for display
 44      * @see DateTimeFormatter
 45      */
 46     public DptLocalDatePicker(DateTimeFormatter formatter) {
 47         this(LocalDate.now(), formatter);
 48     }
 49 
 50     /**
 51      * Constructs a LocalDateCombo with the date provided and no lower or upper limits, formatted in a
 52      * medium style.
 53      *
 54      * @param value The initial value
 55      * @see FormatStyle#MEDIUM
 56      */
 57     public DptLocalDatePicker(LocalDate value) {
 58         this(value, DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));
 59     }
 60 
 61     /**
 62      * Constructs a LocalDateCombo with the date provided and no lower or upper limits, formatted
 63      * according to the provided formatter.
 64      *
 65      * @param value     The initial value
 66      * @param formatter Formats the date for display
 67      */
 68     public DptLocalDatePicker(LocalDate value, DateTimeFormatter formatter) {
 69         this(value, null, null, formatter);
 70     }
 71 
 72     /**
 73      * Constructs a LocalDateCombo with the date, lower (earliest) and upper (latest) limits provided,
 74      * formatted in a medium style.
 75      * <p>
 76      * Dates outside the specified range are not displayed.
 77      * <p>
 78      * This class does not attempt to verify that minDate <= value <= maxDate. It is the
 79      * responsibility of client code to supply sane values.
 80      *
 81      * @param value   The initial value
 82      * @param minDate The minimum value (earliest date); <CODE>null</CODE> for no limit.
 83      * @param maxDate The maximum value (latest date); <CODE>null</CODE> for no limit.
 84      * @see FormatStyle#MEDIUM
 85      */
 86     public DptLocalDatePicker(LocalDate value, LocalDate minDate, LocalDate maxDate) {
 87         this(value, minDate, maxDate, DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));
 88     }
 89 
 90     /**
 91      * Constructs a LocalDateCombo with the date, lower (earliest) and upper (latest) limits provided,
 92      * formatted according to the provided formatter.
 93      * <p>
 94      * Dates outside the specified range are not displayed.
 95      * <p>
 96      * This class does not attempt to verify that minDate <= value <= maxDate. It is the
 97      * responsibility of client code to supply sane values.
 98      *
 99      * @param value     The initial value
100      * @param minDate   The minimum value (earliest date); <CODE>null</CODE> for no limit.
101      * @param maxDate   The maximum value (latest date); <CODE>null</CODE> for no limit.
102      * @param formatter Formats the date for display
103      */
104     public DptLocalDatePicker(LocalDate value, LocalDate minDate, LocalDate maxDate,
105                               DateTimeFormatter formatter) {
106         // Thursday after 24 of September
107         initCompont(value, minDate, maxDate, formatter);
108         addPopupMenuListener(new PopupMenuListener() {
109             @Override
110             public void popupMenuWillBecomeVisible(PopupMenuEvent pme) {
111 
112                 final boolean popupShown = popupMenu.isShowing();
113                 SwingUtilities.invokeLater(() -> {
114                     hidePopup();
115                     if (popupShown) {
116                         popupMenu.setVisible(false);
117                     } else {
118                         popupMenu.show(DptLocalDatePicker.this, 0, getHeight());
119                     }
120                 });
121             }
122 
123             @Override
124             public void popupMenuWillBecomeInvisible(PopupMenuEvent pme) {
125             }
126 
127             @Override
128             public void popupMenuCanceled(PopupMenuEvent pme) {
129             }
130         });
131     }
132 
133     private void initCompont(LocalDate value, LocalDate minDate, LocalDate maxDate, DateTimeFormatter formatter) {
134         if (value == null) {
135             value = LocalDate.now();
136         }
137         LocalDate longNameDate = LocalDate.now().withDayOfMonth(24).withMonth(9);
138         longNameDate = longNameDate.plusDays(4 - longNameDate.getDayOfWeek().getValue());
139         setPrototypeDisplayValue(longNameDate);
140         DptMonthView = new DptMonthView(value, minDate, maxDate, true);
141         comboModel.addElement(value);
142 
143         setRenderer(new DefaultListCellRenderer() {
144             @Override
145             public Component getListCellRendererComponent(JList<?> list, Object value,
146                                                           int index, boolean isSelected, boolean hasFocus) {
147                 super.getListCellRendererComponent(list, value, index, isSelected, hasFocus);
148                 if (value != null) {
149                     setText(formatter.format((TemporalAccessor) value));
150                 }
151                 return this;
152             }
153         });
154         min = minDate;
155         max = maxDate;
156         popupMenu.add(DptMonthView);
157         DptMonthView.addPropertyChangeListener("Confirm", pce -> {
158             popupMenu.setVisible(false);
159         });
160         DptMonthView.addPropertyChangeListener("Value", pce -> {
161             setValue((LocalDate) pce.getNewValue());
162             firePropertyChange("Value", pce.getOldValue(), pce.getNewValue());
163         });
164     }
165 
166     /**
167      * Returns the current value
168      *
169      * @return the current value
170      */
171     public LocalDate getValue() {
172         ComboBoxModel<LocalDate> model = getModel();
173         Object selectedItem = model.getSelectedItem();
174         if (selectedItem == null) {
175             return null;
176         }
177         return LocalDate.parse(selectedItem.toString());//value;
178     }
179 
180     /**
181      * Sets the current value, adjusted to be within any specified min/max range.
182      *
183      * @param value The value to set
184      */
185     public void setValue(LocalDate value) {
186         if (getSelectedItem() == null) {
187             setModel(comboModel);
188         } else {
189             if (getSelectedItem().equals(value)) {
190                 return;
191             }
192         }
193 
194         if (min != null && value.isBefore(min)) {
195             value = min;
196         }
197         if (max != null && value.isAfter(max)) {
198             value = max;
199         }
200         comboModel.removeAllElements();
201         comboModel.addElement(value);
202         if (DptMonthView.getValue() != null) {
203             if (!DptMonthView.getValue().equals(value)) {
204                 DptMonthView.setValue(value);
205             }
206         }
207     }
208 
209     /**
210      * Returns the minimum value (earliest date), or <CODE>null</CODE> if no limit is set.
211      *
212      * @return The earliest date that can be selected.
213      */
214     public LocalDate getMin() {
215         return min;
216     }
217 
218     /**
219      * Sets the minimum value (earliest date). Call this method with a <CODE>null</CODE> value for no
220      * limit.
221      * <p>
222      * This class does not attempt to verify that min <= value <= max. It is the responsibility of
223      * client code to supply a sane value.
224      *
225      * @param min The earliest date that can be selected, or <CODE>null</CODE> for no limit.
226      */
227     public void setMin(LocalDate min) {
228         this.min = min;
229         DptMonthView.setMin(min);
230     }
231 
232     /**
233      * Returns the maximum value (latest date), or <CODE>null</CODE> if no limit is set.
234      *
235      * @return The latest date that can be selected.
236      */
237     public LocalDate getMax() {
238         return max;
239     }
240 
241     /**
242      * Sets the maximum value (latest date). Call this method with a <CODE>null</CODE> value for no
243      * limit.
244      * <p>
245      * This class does not attempt to verify that min <= value <= max. It is the responsibility of
246      * client code to supply a sane value.
247      *
248      * @param max The latest date that can be selected, or <CODE>null</CODE> for no limit.
249      */
250     public void setMax(LocalDate max) {
251         this.max = max;
252         DptMonthView.setMax(max);
253     }
254 
255     @Override
256     public void updateUI() {
257         super.updateUI();
258         setUI(new MyFlatComboBoxUI());
259         if (popupMenu != null) {
260             SwingUtilities.updateComponentTreeUI(popupMenu);
261         }
262     }
263 
264     private class MyFlatComboBoxUI extends FlatComboBoxUI {
265         public MyFlatComboBoxUI() {
266             super();
267         }
268 
269 
270         @Override
271         protected JButton createArrowButton() {
272             JButton button = new JButton();
273             button.setOpaque(false);
274             button.setBorderPainted(false);
275             button.setContentAreaFilled(false);
276             button.setFocusPainted(false);
277             button.setBackground(new Color(1f, 1f, 1f, 0f));
278             button.setIcon(new FlatSVGIcon("icons/rili.svg", 16, 16));
279             return button;
280         }
281     }
282 }
  1 /**
  2  * @(#)MonthView.java 1.0 2015/02/18
  3  */
  4 package com.example.dpt.component;
  5 
  6 import com.example.dpt.component.swing.DptYearMonthSpinner;
  7 
  8 import javax.swing.*;
  9 import javax.swing.event.ChangeEvent;
 10 import javax.swing.event.ListSelectionListener;
 11 import javax.swing.table.*;
 12 import java.awt.*;
 13 import java.awt.event.*;
 14 import java.awt.font.TextAttribute;
 15 import java.time.DayOfWeek;
 16 import java.time.LocalDate;
 17 import java.time.YearMonth;
 18 import java.time.format.DateTimeFormatter;
 19 import java.time.format.FormatStyle;
 20 import java.time.format.TextStyle;
 21 import java.time.temporal.ChronoUnit;
 22 import java.time.temporal.TemporalAdjusters;
 23 import java.util.Locale;
 24 import java.util.Map;
 25 import java.util.function.Function;
 26 
 27 /**
 28  * @see LocalDate
 29  */
 30 public class DptMonthView extends JPanel {
 31 
 32     private static final DateTimeFormatter LABEL_FORMATTER
 33             = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
 34     private final JPanel north;
 35     private TodayLinkLabel todayLinkLabel;
 36     private volatile boolean programmaticChange;
 37     private YearMonth yearMonth;
 38     private LocalDate startDate;
 39     private LocalDate value;
 40     private LocalDate min;
 41     private LocalDate max;
 42     private final DptYearMonthSpinner yearMonthSpinner;
 43     private final JTable table = new JTable(new MonthViewTableModel());
 44 
 45     /**
 46      * Constructs a MonthView with the value set to the current date, with no upper or lower limits.
 47      */
 48     public DptMonthView() {
 49         this(LocalDate.now());
 50     }
 51 
 52     /**
 53      * Constructs a MonthView with the value set to the supplied date, with no lower or upper limits.
 54      *
 55      * @param initialDate The date to set
 56      */
 57     public DptMonthView(LocalDate initialDate) {
 58         this(initialDate, null, null);
 59     }
 60 
 61     /**
 62      * Constructs a MonthView with the value set to the supplied date, with the supplied lower
 63      * (earliest) and upper (latest) dates.
 64      * <p>
 65      * Dates outside the specified range are not displayed.
 66      * <p>
 67      * This class does not attempt to verify that minDate <= value <= maxDate. It is the
 68      * responsibility of client code to supply sane values.
 69      *
 70      * @param initialDate The date to set
 71      * @param minDate     The minimum value (earliest date); <CODE>null</CODE> for no limit.
 72      * @param maxDate     The maximum value (latest date); <CODE>null</CODE> for no limit.
 73      */
 74     public DptMonthView(LocalDate initialDate, LocalDate minDate, LocalDate maxDate) {
 75         this(initialDate, minDate, maxDate, false);
 76     }
 77 
 78     /**
 79      * Constructs a MonthView with the value set to the supplied date, with the supplied lower
 80      * (earliest) and upper (latest) dates, and, optionally, a link to set the value to the current
 81      * date.
 82      * <p>
 83      * Dates outside the specified range are not displayed.
 84      * <p>
 85      * This class does not attempt to verify that minDate <= value <= maxDate. It is the
 86      * responsibility of client code to supply sane values.
 87      *
 88      * @param initialDate The date to set
 89      * @param minDate     The minimum value (earliest date); <CODE>null</CODE> for no limit.
 90      * @param maxDate     The maximum value (latest date); <CODE>null</CODE> for no limit.
 91      * @param showtoday   true to show a link to the current date, false otherwise
 92      */
 93     public DptMonthView(LocalDate initialDate, LocalDate minDate, LocalDate maxDate, boolean showtoday) {
 94         super(new BorderLayout(7, 0));
 95         min = minDate;
 96         max = maxDate;
 97         yearMonth = YearMonth.from(initialDate);
 98         startDate = yearMonth.atDay(1).minusDays(yearMonth.atDay(1).getDayOfWeek().getValue());
 99 
100         YearMonth firstMonth = null;
101         if (minDate != null) {
102             firstMonth = YearMonth.from(minDate);
103         }
104         YearMonth lastMonth = null;
105         if ((maxDate != null)) {
106             lastMonth = YearMonth.from(maxDate);
107         }
108         yearMonthSpinner = new DptYearMonthSpinner(yearMonth, firstMonth, lastMonth);
109         yearMonthSpinner.addChangeListener((ChangeEvent ce) -> {
110             if (!programmaticChange) {
111                 setYearMonth((YearMonth) yearMonthSpinner.getValue());
112             }
113         });
114 
115         setValue(initialDate);
116 
117         table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
118         table.setShowGrid(false);
119 //        table.setBorder(BorderFactory.createLineBorder(table.getForeground()));
120         table.setCellSelectionEnabled(true);
121         table.setRowHeight(40);
122         table.setDefaultRenderer(LocalDate.class, new MonthViewTableCellRenderer());
123         TableColumnModel columnModel = table.getColumnModel();
124         for (int i = 0; i < columnModel.getColumnCount(); i++) {
125             columnModel.getColumn(i).setPreferredWidth(40);
126         }
127         JTableHeader header = table.getTableHeader();
128         header.setBorder(table.getBorder());
129         header.setReorderingAllowed(false);
130         header.setResizingAllowed(false);
131         header.setDefaultRenderer(new MonthViewTableHeaderCellRenderer(header.getDefaultRenderer()));
132 
133         Timer selectionTimer = new Timer(50, ae -> {
134             LocalDate newValue = (LocalDate) table.getValueAt(table.getSelectedRow(),
135                     table.getSelectedColumn());
136             if (newValue.equals(value)) {
137                 return;
138             }
139             boolean needSetTableSelection = false;
140             if (min != null && newValue.isBefore(min)) {
141                 newValue = min;
142                 needSetTableSelection = true;
143             }
144             if (max != null && newValue.isAfter(max)) {
145                 newValue = max;
146                 needSetTableSelection = true;
147             }
148             firePropertyChange("Value", value, newValue);
149             value = newValue;
150             if (needSetTableSelection) {
151                 setTableSelection();
152             }
153             setYearMonth(YearMonth.from(newValue));
154         });
155         selectionTimer.setRepeats(false);
156 
157         ListSelectionListener selectionListener = lse -> {
158             if (!programmaticChange && !lse.getValueIsAdjusting()) {
159                 if (selectionTimer.isRunning()) {
160                     selectionTimer.restart();
161                 } else {
162                     selectionTimer.start();
163                 }
164             }
165         };
166         ListSelectionModel rowSelectionModel = table.getSelectionModel();
167         rowSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
168         rowSelectionModel.addListSelectionListener(selectionListener);
169 
170         ListSelectionModel columnSelectionModel = columnModel.getSelectionModel();
171         columnSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
172         columnSelectionModel.addListSelectionListener(selectionListener);
173         table.addMouseWheelListener(new MouseAdapter() {
174 
175             @Override
176             public void mouseWheelMoved(MouseWheelEvent mwe) {
177                 if (mwe.getWheelRotation() < 0) {
178                     yearMonthSpinner.setValue(yearMonthSpinner.getValue().plusMonths(1));
179                 } else {
180                     yearMonthSpinner.setValue(yearMonthSpinner.getValue().minusMonths(1));
181                 }
182             }
183         });
184         setTableSelection();
185 
186         changeTableAction(KeyEvent.VK_LEFT, ae -> {
187             if (min != null && value.equals(min)) {
188                 return false;
189             }
190             if (table.getSelectedColumn() == 0) {
191                 LocalDate newValue = value.minusDays(1);
192                 DptMonthView.this.firePropertyChange("Value", value, newValue);
193                 setValue(newValue);
194                 return false;
195             }
196             return true;
197         });
198         changeTableAction(KeyEvent.VK_UP, ae -> {
199             if (min != null && value.isBefore(min.plusDays(7))) {
200                 DptMonthView.this.firePropertyChange("Value", value, min);
201                 setValue(min);
202                 return false;
203             }
204             if (table.getSelectedRow() == 0) {
205                 LocalDate newValue = value.minusDays(7);
206                 DptMonthView.this.firePropertyChange("Value", value, newValue);
207                 setValue(newValue);
208                 return false;
209             }
210             return true;
211         });
212         changeTableAction(KeyEvent.VK_RIGHT, ae -> {
213             if (max != null && value.equals(max)) {
214                 return false;
215             }
216             if (table.getSelectedColumn() == 6) {
217                 LocalDate newValue = value.plusDays(1);
218                 DptMonthView.this.firePropertyChange("Value", value, newValue);
219                 setValue(newValue);
220                 return false;
221             }
222             return true;
223         });
224         changeTableAction(KeyEvent.VK_DOWN, ae -> {
225             if (max != null && value.isAfter(max.minusDays(7))) {
226                 DptMonthView.this.firePropertyChange("Value", value, max);
227                 setValue(max);
228                 return false;
229             }
230             if (table.getSelectedRow() == 5) {
231                 LocalDate newValue = value.plusDays(7);
232                 DptMonthView.this.firePropertyChange("Value", value, newValue);
233                 setValue(newValue);
234                 return false;
235             }
236             return true;
237         });
238         changeTableAction(KeyEvent.VK_ENTER, ae -> {
239             DptMonthView.this.firePropertyChange("Confirm", null, value);
240             return false;
241         });
242         table.addMouseListener(new MouseAdapter() {
243 
244             @Override
245             public void mouseReleased(MouseEvent me) {
246                 int selectedRow = table.getSelectedRow();
247                 int selectedColumn = table.getSelectedColumn();
248                 Object valueAt = table.getValueAt(selectedRow, selectedColumn);
249                 if (DptMonthView.this.value == null && valueAt != null) {
250                     LocalDate parseDate = LocalDate.parse(valueAt.toString());
251                     DptMonthView.this.value = parseDate;
252                     setValue(parseDate);
253                 }
254                 DptMonthView.this.firePropertyChange("Confirm", null, DptMonthView.this.value);
255                 DptMonthView.this.firePropertyChange("Value", null, DptMonthView.this.value);
256             }
257         });
258 
259         setFocusable(true);
260 
261         north = new JPanel(new BorderLayout());
262         north.setPreferredSize(new Dimension(0, 45));
263         north.add(yearMonthSpinner, BorderLayout.CENTER);
264         north.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager.getColor("ColorPalette.border")));
265 
266         Box tableBox = Box.createVerticalBox();
267         tableBox.add(header);
268         tableBox.add(table);
269         add(north, BorderLayout.NORTH);
270         add(tableBox, BorderLayout.CENTER);
271         todayLinkLabel = getTodayLinkLabel();
272         if (showtoday) {
273             add(todayLinkLabel, BorderLayout.SOUTH);
274         }
275 
276     }
277 
278     @Override
279     public void updateUI() {
280         super.updateUI();
281 
282         if (north != null) {
283             north.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager.getColor("ColorPalette.border")));
284         }
285     }
286 
287     private void changeTableAction(int key, Function<ActionEvent, Boolean> function) {
288         InputMap ancestorMap = table.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
289         InputMap windowMap = table.getInputMap(WHEN_IN_FOCUSED_WINDOW);
290         ActionMap actionMap = table.getActionMap();
291         KeyStroke keyStroke = KeyStroke.getKeyStroke(key, 0);
292         Object actionKey = ancestorMap.get(keyStroke);
293         windowMap.put(keyStroke, actionKey);
294 
295         Action oldAction = actionMap.get(actionKey);
296         Action replacement = new AbstractAction() {
297 
298             @Override
299             public void actionPerformed(ActionEvent ae) {
300                 if (function.apply(ae)) {
301                     oldAction.actionPerformed(ae);
302                 }
303             }
304         };
305         actionMap.put(actionKey, replacement);
306     }
307 
308     private void setYearMonth(YearMonth yearMonth) {
309         if (min != null && yearMonth.atEndOfMonth().isBefore(min)) {
310             yearMonth = YearMonth.from(min);
311         }
312         if (max != null && yearMonth.atDay(1).isAfter(max)) {
313             yearMonth = YearMonth.from(max);
314         }
315         if (yearMonth.equals(this.yearMonth)) {
316             //return; // no, we still have to setTableSelection
317         }
318 
319         if (!yearMonth.equals(yearMonthSpinner.getValue())) {
320             programmaticChange = true;
321             yearMonthSpinner.setValue(yearMonth);
322             programmaticChange = false;
323         }
324         this.yearMonth = yearMonth;
325         startDate = yearMonth.atDay(1).with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));
326         setTableSelection();
327         repaint();
328     }
329 
330     /**
331      * Returns the current value
332      *
333      * @return the current value
334      */
335     public LocalDate getValue() {
336         return value;
337     }
338 
339     /**
340      * Sets the current value, adjusted to be within any specified min/max range.
341      *
342      * @param value The value to set
343      */
344     public void setValue(LocalDate value) {
345         if (min != null && value.isBefore(min)) {
346             value = min;
347         }
348         if (max != null && value.isAfter(max)) {
349             value = max;
350         }
351 
352         this.value = value;
353         if (value == null) {
354             return;
355         }
356         setYearMonth(YearMonth.from(value));
357         getTodayLinkLabel().setText("今天: " + LocalDate.now().format(LABEL_FORMATTER));
358     }
359 
360     private void setTableSelection() {
361         programmaticChange = true;
362         if (value == null || value.isBefore(startDate)
363                 || value.isAfter(startDate.plusDays(41))) {
364             table.clearSelection();
365         } else {
366             int days = (int) ChronoUnit.DAYS.between(startDate, value);
367             table.setRowSelectionInterval(days / 7, days / 7);
368             table.setColumnSelectionInterval(days % 7, days % 7);
369         }
370         programmaticChange = false;
371     }
372 
373     /**
374      * Returns the minimum value (earliest date), or <CODE>null</CODE> if no limit is set.
375      *
376      * @return The earliest date that can be selected.
377      */
378     public LocalDate getMin() {
379         return min;
380     }
381 
382     /**
383      * Sets the minimum value (earliest date). Call this method with a <CODE>null</CODE> value for no
384      * limit.
385      * <p>
386      * This method does not attempt to verify that min <= value <= max. It is the responsibility of
387      * client code to supply a sane value.
388      *
389      * @param min The earliest date that can be selected, or <CODE>null</CODE> for no limit.
390      */
391     public void setMin(LocalDate min) {
392         this.min = min;
393     }
394 
395     /**
396      * Returns the maximum value (latest date), or <CODE>null</CODE> if no limit is set.
397      *
398      * @return The latest date that can be selected.
399      */
400     public LocalDate getMax() {
401         return max;
402     }
403 
404     /**
405      * Sets the maximum value (latest date). Call this method with a <CODE>null</CODE> value for no
406      * limit.
407      * <p>
408      * This method does not attempt to verify that min <= value <= max. It is the responsibility of
409      * client code to supply a sane value.
410      *
411      * @param max The latest date that can be selected, or <CODE>null</CODE> for no limit.
412      */
413     public void setMax(LocalDate max) {
414         this.max = max;
415     }
416 
417     private class MonthViewTableModel<T extends LocalDate> extends AbstractTableModel {
418 
419         @Override
420         public Class<?> getColumnClass(int column) {
421             return LocalDate.class;
422         }
423 
424         @Override
425         public String getColumnName(int column) {
426             String week = DayOfWeek.of((column + 6) % 7 + 1)
427                     .getDisplayName(TextStyle.SHORT_STANDALONE, Locale.getDefault());
428             return week.substring(2);
429         }
430 
431         @Override
432         public int getRowCount() {
433             return 6;
434         }
435 
436         @Override
437         public int getColumnCount() {
438             return 7;
439         }
440 
441         @Override
442         public Object getValueAt(int row, int column) {
443             return startDate.plusDays(row * 7 + column);
444         }
445     }
446 
447     private class MonthViewTableHeaderCellRenderer implements TableCellRenderer {
448 
449         private final TableCellRenderer defaultRenderer;
450 
451         private MonthViewTableHeaderCellRenderer(TableCellRenderer defaultRenderer) {
452             this.defaultRenderer = defaultRenderer;
453 
454         }
455 
456         @Override
457         public Component getTableCellRendererComponent(JTable table, Object value,
458                                                        boolean isSelected, boolean hasFocus, int row, int column) {
459             JComponent c = (JComponent) defaultRenderer.getTableCellRendererComponent(table, value,
460                     hasFocus, hasFocus, row, row);
461             c.setPreferredSize(new Dimension(40, 40));
462             c.setBorder(BorderFactory.createEmptyBorder());
463             c.setForeground(column == 0 ? Colors.normalSunday
464                     : column == 6 ? Colors.normalSaturday
465                     : getForeground());
466             return c;
467         }
468 
469     }
470 
471 
472     private class MonthViewTableCellRenderer extends DefaultTableCellRenderer {
473 
474         private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd");
475 
476         private MonthViewTableCellRenderer() {
477             setHorizontalAlignment(CENTER);
478 
479         }
480 
481         @Override
482         public Component getTableCellRendererComponent(JTable table, Object value,
483                                                        boolean isSelected, boolean hasFocus, int row, int column) {
484 
485             if (value == null) {
486                 return this;
487             }
488             LocalDate dateValue = (LocalDate) value;
489 
490             boolean isSunday = dateValue.getDayOfWeek() == DayOfWeek.SUNDAY;
491             boolean isSaturday = dateValue.getDayOfWeek() == DayOfWeek.SATURDAY;
492             if (dateValue.getMonth() == yearMonth.getMonth()) {
493                 setForeground(isSunday ? Colors.normalSunday : isSaturday ? Colors.normalSaturday : table.getForeground());
494             } else {
495                 Color fadedc = UIManager.getColor("ColorPalette.dis") == null ? new Color(0x8b8b8c) : UIManager.getColor("ColorPalette.dis");
496                 setForeground(isSunday ? Colors.fadedSunday : isSaturday ? Colors.fadedSaturday : fadedc);
497             }
498             hasFocus = true;
499             if (min != null && dateValue.isBefore(min)
500                     || max != null && dateValue.isAfter(max)) {
501                 setForeground(Colors.blank);
502                 isSelected = false;
503                 hasFocus = false;
504             }
505             super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
506             setText(formatter.format(dateValue));
507             setBorder(BorderFactory.createEmptyBorder());
508             return this;
509         }
510     }
511 
512     public TodayLinkLabel getTodayLinkLabel() {
513         if (todayLinkLabel == null) {
514             todayLinkLabel = new TodayLinkLabel();
515         }
516         return todayLinkLabel;
517     }
518 
519     private static class Colors {
520 
521         public static final Color faded = UIManager.getColor("ColorPalette.dis") == null ? new Color(0x8b8b8c) : UIManager.getColor("ColorPalette.dis");
522         public static final Color normalSunday = new Color(0xF56C6C);
523         public static final Color fadedSunday = new Color(0xfbc4c4);
524         public static final Color normalSaturday = new Color(0x67C23A);
525         public static final Color fadedSaturday = new Color(0xc2e7b0);
526         public static final Color blank = new Color(0x8b8b8c);
527 
528 
529     }
530 
531     private class TodayLinkLabel extends JLabel {
532 
533         private final Font normalFont;
534         private final Font underlinedFont;
535 
536         private TodayLinkLabel() {
537             super("今天: " + LocalDate.now().format(LABEL_FORMATTER));
538             setHorizontalAlignment(CENTER);
539             setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
540 
541             normalFont = getFont();
542             Map attributes = normalFont.getAttributes();
543             attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
544             underlinedFont = normalFont.deriveFont(attributes);
545 //            setPreferredSize(new Dimension(0, 40));
546             addMouseListener(new MouseAdapter() {
547 
548                 @Override
549                 public void mouseEntered(MouseEvent e) {
550                     setFont(underlinedFont);
551                 }
552 
553                 @Override
554                 public void mouseExited(MouseEvent e) {
555                     setFont(normalFont);
556                 }
557 
558                 @Override
559                 public void mouseClicked(MouseEvent me) {
560                     LocalDate newValue = LocalDate.now();
561                     DptMonthView.this.firePropertyChange("Value", value, newValue);
562                     setValue(newValue);
563 //                    MonthView.this.firePropertyChange("Confirm", null, newValue);
564                 }
565             });
566         }
567     }
568 }
  1 package com.example.dpt.component.swing;
  2 
  3 import javax.swing.*;
  4 import javax.swing.event.ChangeListener;
  5 import java.awt.*;
  6 import java.time.YearMonth;
  7 import java.time.format.DateTimeFormatter;
  8 import java.time.temporal.ChronoUnit;
  9 
 10 /**
 11  * YearMonthSpinner is a composite of two spinners, one for the month and the other for the year,
 12  * used to select a java.time.MonthYear.
 13  * <P>
 14  * <B>Note that compiling or using this class requires Java 8</B>
 15  *
 16  * @see YearMonth
 17  *
 18  */
 19 public class DptYearMonthSpinner extends JPanel {
 20 
 21   private boolean programmaticChange;
 22   private SpinnerTemporalModel<YearMonth> monthModel;
 23   private SpinnerTemporalModel<YearMonth> yearModel;
 24   private JSpinner monthSpinner;
 25   private JSpinner yearSpinner;
 26 
 27   /**
 28    * Constructs a YearMonthSpinner with the current year and month, with no lower or upper limits.
 29    *
 30    */
 31   public DptYearMonthSpinner() {
 32     this(YearMonth.now());
 33   }
 34 
 35   /**
 36    * Constructs a YearMonthSpinner with the supplied year and month, with no lower or upper limits.
 37    *
 38    * @param value the initial value
 39    */
 40   public DptYearMonthSpinner(YearMonth value) {
 41     this(value, null, null);
 42   }
 43 
 44   /**
 45    * Constructs a YearMonthSpinner with the supplied year and month, lower (earliest) and upper
 46    * (latest) dates.
 47    *
 48    * @param value the initial value
 49    * @param min the minimum (earliest) value
 50    * @param max the maximum (latest) value
 51    */
 52   public DptYearMonthSpinner(YearMonth value, YearMonth min, YearMonth max) {
 53     super(new FlowLayout(FlowLayout.CENTER, 7, 7));
 54 
 55     if (value == null) {
 56       value = YearMonth.now();
 57     }
 58     monthModel = new SpinnerTemporalModel<>(value, min, max, ChronoUnit.MONTHS);
 59     monthSpinner = new JSpinner(monthModel) {
 60       @Override
 61       public ComponentOrientation getComponentOrientation() {
 62         return ComponentOrientation.LEFT_TO_RIGHT;
 63       }
 64     };
 65     SpinnerTemporalEditor monthEditor = new SpinnerTemporalEditor(monthSpinner,
 66         DateTimeFormatter.ofPattern("M 月"));
 67     monthEditor.getTextField().setColumns(5);
 68     monthSpinner.setEditor(monthEditor);
 69     monthEditor.getTextField().setHorizontalAlignment(JTextField.CENTER);
 70     monthEditor.getTextField().setFont( monthEditor.getTextField().getFont().deriveFont(18f));
 71     yearModel = new SpinnerTemporalModel<>(value, min, max, ChronoUnit.YEARS);
 72     yearSpinner = new JSpinner(yearModel) {
 73       @Override
 74       public ComponentOrientation getComponentOrientation() {
 75         return ComponentOrientation.LEFT_TO_RIGHT;
 76       }
 77     };
 78     SpinnerTemporalEditor yearEditor
 79         = new SpinnerTemporalEditor(yearSpinner, DateTimeFormatter.ofPattern("yyyy 年"));
 80     yearEditor.getTextField().setColumns(4);
 81     yearEditor.getTextField().setHorizontalAlignment(JTextField.CENTER);
 82     yearSpinner.setEditor(yearEditor);
 83 
 84     yearEditor.getTextField().setFont( yearEditor.getTextField().getFont().deriveFont(18f));
 85 
 86     monthSpinner.addChangeListener(ce -> {
 87       if (!programmaticChange) {
 88         programmaticChange = true;
 89         yearSpinner.setValue(monthSpinner.getValue());//value);
 90         programmaticChange = false;
 91       }
 92     });
 93     yearSpinner.addChangeListener(ce -> {
 94       if (!programmaticChange) {
 95         programmaticChange = true;
 96         monthSpinner.setValue(yearSpinner.getValue());//value);
 97         programmaticChange = false;
 98       }
 99     });
100 
101     add(monthSpinner);
102     add(yearSpinner);
103   }
104 
105   /**
106    * Adds a listener that is notified each time a change occurs. The source of the
107    * <code>ChangeEvent</code> will be the contained month spinner.
108    *
109    * @param listener the <code>ChangeListeners</code> to add
110    * @see #removeChangeListener
111    */
112   public void addChangeListener(ChangeListener listener) {
113     monthSpinner.addChangeListener(listener);
114   }
115 
116   /**
117    * Removes a <code>ChangeListener</code>.
118    *
119    * @param listener the <code>ChangeListener</code> to remove
120    */
121   public void removeChangeListener(ChangeListener listener) {
122     monthSpinner.removeChangeListener(listener);
123   }
124 
125   /**
126    * {@inheritDoc }
127    */
128   @Override
129   public void setEnabled(boolean enabled) {
130     super.setEnabled(enabled);
131     monthSpinner.setEnabled(enabled);
132     yearSpinner.setEnabled(enabled);
133   }
134 
135   /**
136    * Returns the current value
137    *
138    * @return the current value
139    */
140   public YearMonth getValue() {
141     return monthModel.getTemporalValue();
142   }
143 
144   /**
145    * Sets the current value
146    * <P>
147    * This class does not attempt to verify that min <= value <= max. It is the responsibility of
148    * client code to supply a sane value.
149    *
150    * @param value The value to set
151    */
152   public void setValue(YearMonth value) {
153     programmaticChange = true;
154     monthSpinner.setValue(value);
155     yearSpinner.setValue(value);
156     programmaticChange = false;
157   }
158 
159   /**
160    * Returns the minimum value (earliest year/month), or <CODE>null</CODE> if no limit is set.
161    *
162    * @return The earliest year/month that can be selected.
163    */
164   public YearMonth getMin() {
165     return monthModel.getMin();
166   }
167 
168   /**
169    * Sets the minimum value (earliest year/month). Call this method with a <CODE>null</CODE> value
170    * for no limit.
171    * <P>
172    * This class does not attempt to verify that min <= value <= max. It is the responsibility of
173    * client code to supply a sane value.
174    *
175    * @param min The earliest year/month that can be selected, or <CODE>null</CODE> for no limit.
176    */
177   public void setMin(YearMonth min) {
178     monthModel.setMin(min);
179     yearModel.setMin(min);
180   }
181 
182   /**
183    * Returns the maximum value (latest year/month), or <CODE>null</CODE> if no limit is set.
184    *
185    * @return The latest year/month that can be selected.
186    */
187   public YearMonth getMax() {
188     return monthModel.getMax();
189   }
190 
191   /**
192    * Sets the maximum value (latest year/month). Call this method with a <CODE>null</CODE> value for
193    * no limit.
194    * <P>
195    * This class does not attempt to verify that min <= value <= max. It is the responsibility of
196    * client code to supply a sane value.
197    *
198    * @param max The latest year/month that can be selected, or <CODE>null</CODE> for no limit.
199    */
200   public void setMax(YearMonth max) {
201     monthModel.setMax(max);
202     yearModel.setMax(max);
203   }
204 
205   @Override
206   public void updateUI() {
207     super.updateUI();
208     if (yearSpinner != null) {
209       yearSpinner.updateUI();
210       monthSpinner.updateUI();
211     }
212   }
213 }