• category: swing folder: CalendarViewTable title: JTableにLocaleを考慮したLocalDateを適用してカレンダーを表示する tags: [JTable, LocalDate, Locale, Calendar] author: aterai pubdate: 2018-01-29T14:57:32+09:00 description: JTableに週の最初の曜日がLocaleに応じて変化するカレンダーを表示します。 image: https://drive.google.com/uc?export=view&id=1jXZtiYFaA5ABWsdaRBnPUKqS2_VBDkFqQA hreflang:
       href: https://java-swing-tips.blogspot.com/2018/01/apply-localdate-considering-locale-to.html
       lang: en

概要

JTableに週の最初の曜日がLocaleに応じて変化するカレンダーを表示します。

サンプルコード

class CalendarViewTableModel<T extends LocalDate> extends DefaultTableModel {
  private final LocalDate startDate;
  private final WeekFields weekFields = WeekFields.of(Locale.getDefault());
  public CalendarViewTableModel(T date) {
    super();
    LocalDate firstDayOfMonth = YearMonth.from(date).atDay(1);
    int dowv = firstDayOfMonth.get(weekFields.dayOfWeek()) - 1;
    startDate = firstDayOfMonth.minusDays(dowv);
  }
  @Override public Class<?> getColumnClass(int column) {
    return LocalDate.class;
  }
  @Override public String getColumnName(int column) {
    return weekFields.getFirstDayOfWeek().plus(column)
      .getDisplayName(TextStyle.SHORT_STANDALONE, Locale.getDefault());
  }
  @Override public int getRowCount() {
    return 6;
  }
  @Override public int getColumnCount() {
    return 7;
  }
  @Override public Object getValueAt(int row, int column) {
    return startDate.plusDays(row * getColumnCount() + column);
  }
  @Override public boolean isCellEditable(int row, int column) {
    return false;
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、Java 8からjava.timeパッケージに追加されたLocalDateJTableのモデルとして使用し、月のカレンダーを表示しています。

  • WeekFields#getFirstDayOfWeek()メソッドでLocaleに応じた週の最初の曜日を取得して、JTable0列目を設定
    • 例: フランスとISO-8601標準では月曜日が週の最初の曜日になる
      • java -Duser.language=fr -jar example.jar

  • 土曜と日曜の位置も6列目と0列目固定ではないので、以下のようにLocalDate.getDayOfWeek()メソッドで曜日を確認してその背景色を変更する必要がある
private class CalendarTableRenderer extends DefaultTableCellRenderer {
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean selected, boolean focused,
      int row, int column) {
    super.getTableCellRendererComponent(table, value, selected, focused, row, column);
    setHorizontalAlignment(SwingConstants.CENTER);
    if (value instanceof LocalDate) {
      LocalDate d = (LocalDate) value;
      setText(String.valueOf(d.getDayOfMonth()));
      if (YearMonth.from(d).equals(YearMonth.from(currentLocalDate))) {
        setForeground(Color.BLACK);
      } else {
        setForeground(Color.GRAY);
      }
      DayOfWeek dow = d.getDayOfWeek();
      if (d.isEqual(realLocalDate)) {
        setBackground(new Color(220, 255, 220));
      } else if (dow == DayOfWeek.SUNDAY) {
        setBackground(new Color(255, 220, 220));
      } else if (dow == DayOfWeek.SATURDAY) {
        setBackground(new Color(220, 220, 255));
      } else {
        setBackground(Color.WHITE);
      }
    }
    return this;
  }
}

  • JTableではなく、JListを使用すると週を跨いだ日付の範囲選択(上下キー)が可能
import java.awt.*;
import java.awt.event.*;
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
import java.util.*;
import javax.swing.*;

public class CalendarViewListTest {
  private final JLabel monthLabel = new JLabel("", SwingConstants.CENTER);
  private final JList<LocalDate> monthList = new JList<>();
  private final LocalDate realLocalDate = LocalDate.now();
  private LocalDate currentLocalDate;

  public JComponent makeUI() {
    Dimension size = new Dimension(40, 20);

    monthList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
    monthList.setVisibleRowCount(6);
    monthList.setFixedCellWidth(size.width);
    monthList.setFixedCellHeight(size.height);
    monthList.setCellRenderer(new CalendarListRenderer<LocalDate>());
    monthList.getSelectionModel().setSelectionMode(
      ListSelectionModel.SINGLE_INTERVAL_SELECTION);

    InputMap imap = monthList.getInputMap(JComponent.WHEN_FOCUSED);
    imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "selectNextIndex");
    imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "selectPreviousIndex");

    monthList.getActionMap().put("selectPreviousIndex", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        int index = monthList.getLeadSelectionIndex();
        if (index >= 0 && index < monthList.getModel().getSize()) {
          monthList.setSelectedIndex(index - 1);
        }
      }
    });
    monthList.getActionMap().put("selectNextIndex", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        int index = monthList.getLeadSelectionIndex();
        if (index >= 0 && index < monthList.getModel().getSize()) {
          monthList.setSelectedIndex(index + 1);
        }
      }
    });

    Locale l = Locale.getDefault();
    DefaultListModel<DayOfWeek> weekModel = new DefaultListModel<>();
    DayOfWeek firstDayOfWeek = WeekFields.of(l).getFirstDayOfWeek();
    for (int i = 0; i < 7; i++) {
      weekModel.add(i, firstDayOfWeek.plus(i));
    }
    JList<DayOfWeek> header = new JList<>(weekModel);
    header.setLayoutOrientation(JList.HORIZONTAL_WRAP);
    header.setVisibleRowCount(0);
    header.setFixedCellWidth(size.width);
    header.setFixedCellHeight(size.height);
    header.setCellRenderer(new DefaultListCellRenderer() {
      @Override public Component getListCellRendererComponent(
          JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        super.getListCellRendererComponent(list, value, index, false, false);
        setHorizontalAlignment(SwingConstants.CENTER);
        if (value instanceof DayOfWeek) {
          DayOfWeek dow = (DayOfWeek) value;
          String s = dow.getDisplayName(TextStyle.SHORT_STANDALONE, l);
          setText(s.substring(0, Math.min(2, s.length())));
          setBackground(new Color(220, 220, 220));
        }
        return this;
      }
    });
    updateMonthView(realLocalDate);

    JButton prev = new JButton("<");
    prev.addActionListener(e -> updateMonthView(currentLocalDate.minusMonths(1)));

    JButton next = new JButton(">");
    next.addActionListener(e -> updateMonthView(currentLocalDate.plusMonths(1)));

    JPanel monthPanel = new JPanel(new BorderLayout());
    monthPanel.add(monthLabel);
    monthPanel.add(prev, BorderLayout.WEST);
    monthPanel.add(next, BorderLayout.EAST);

    JScrollPane scroll = new JScrollPane(monthList);
    scroll.setColumnHeaderView(header);
    scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
    scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

    Box box = Box.createVerticalBox();
    box.add(monthPanel);
    box.add(Box.createVerticalStrut(2));
    box.add(scroll);

    JPanel p = new JPanel();
    p.add(box);
    return p;
  }
  private void updateMonthView(LocalDate localDate) {
    currentLocalDate = localDate;
    monthLabel.setText(localDate.format(DateTimeFormatter.ofPattern("yyyy / MM")
      .withLocale(Locale.getDefault())));
    WeekFields weekFields = WeekFields.of(Locale.getDefault());
    LocalDate firstDayOfMonth = localDate.with(TemporalAdjusters.firstDayOfMonth());
    int dowv = firstDayOfMonth.get(weekFields.dayOfWeek()) - 1;
    int vlen = 7 * 6;
    LocalDate date = firstDayOfMonth.minusDays(dowv);
    DefaultListModel<LocalDate> calendarModel = new DefaultListModel<>();
    for (int i = 0; i < vlen; i++) {
      calendarModel.add(i, date.plusDays(i));
    }
    monthList.setModel(calendarModel);
  }
  class CalendarListRenderer<E extends LocalDate> implements ListCellRenderer<E> {
    private final JLabel renderer = new JLabel("", SwingConstants.CENTER);
    @Override public Component getListCellRendererComponent(
        JList<? extends E> list, E value, int index,
        boolean isSelected, boolean cellHasFocus) {
      renderer.setOpaque(true);
      renderer.setText(String.valueOf(value.getDayOfMonth()));
      if (YearMonth.from(value).equals(YearMonth.from(currentLocalDate))) {
        renderer.setForeground(Color.BLACK);
      } else {
        renderer.setForeground(Color.GRAY);
      }
      DayOfWeek dow = value.getDayOfWeek();
      if (value.isEqual(realLocalDate)) {
        renderer.setBackground(new Color(220, 255, 220));
      } else if (dow == DayOfWeek.SUNDAY) {
        renderer.setBackground(new Color(255, 220, 220));
      } else if (dow == DayOfWeek.SATURDAY) {
        renderer.setBackground(new Color(220, 220, 255));
      } else {
        renderer.setBackground(Color.WHITE);
      }
      if (isSelected) {
        renderer.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY));
      } else {
        renderer.setBorder(BorderFactory.createEmptyBorder(0, 0, 1, 0));
      }
      return renderer;
    }
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new CalendarViewListTest().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

参考リンク

コメント