Last active
November 16, 2021 19:49
-
-
Save james-d/ee8a5c216fb3c6e027ea to your computer and use it in GitHub Desktop.
Simple example of a calendar display. Supports some basic CSS styling and direct localization (in practice you would almost never need localization, just use the default locale; but it was fun to play with). Includes a simple example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.calendar-grid { | |
-fx-hgap: 4 ; | |
-fx-vgap: 4 ; | |
-fx-padding: 4 ; | |
} | |
.calendar-header { | |
-fx-alignment: center ; | |
-fx-font-size: 18pt ; | |
-fx-font-weight: bold ; | |
} | |
.calendar-cell { | |
-fx-padding: 4 ; | |
-fx-alignment: center ; | |
} | |
.calendar-cell:before-display-month, .calendar-cell:after-display-month { | |
-fx-opacity: 0.4 ; | |
} | |
.calendar-header-cell { | |
-fx-padding: 4 ; | |
-fx-alignment: center ; | |
-fx-font-weight: bold ; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.DayOfWeek; | |
import java.time.LocalDate; | |
import java.time.YearMonth; | |
import java.time.format.DateTimeFormatter; | |
import java.time.format.TextStyle; | |
import java.time.temporal.ChronoUnit; | |
import java.time.temporal.WeekFields; | |
import java.util.Locale; | |
import javafx.beans.binding.Bindings; | |
import javafx.beans.property.ObjectProperty; | |
import javafx.beans.property.SimpleObjectProperty; | |
import javafx.css.PseudoClass; | |
import javafx.geometry.HPos; | |
import javafx.scene.Node; | |
import javafx.scene.control.Label; | |
import javafx.scene.layout.BorderPane; | |
import javafx.scene.layout.GridPane; | |
public class CalendarView { | |
private final ObjectProperty<YearMonth> month = new SimpleObjectProperty<>(); | |
private final ObjectProperty<Locale> locale = new SimpleObjectProperty<>(Locale.getDefault()); | |
private final BorderPane view ; | |
private final GridPane calendar ; | |
public CalendarView(YearMonth month) { | |
view = new BorderPane(); | |
view.getStyleClass().add("calendar"); | |
calendar = new GridPane(); | |
calendar.getStyleClass().add("calendar-grid"); | |
Label header = new Label(); | |
header.setMaxWidth(Double.MAX_VALUE); | |
header.getStyleClass().add("calendar-header"); | |
this.month.addListener((obs, oldMonth, newMonth) -> | |
rebuildCalendar()); | |
this.locale.addListener((obs, oldLocale, newLocale) -> | |
rebuildCalendar()); | |
view.setTop(header); | |
view.setCenter(calendar); | |
view.getStylesheets().add(getClass().getResource("calendar.css").toExternalForm()); | |
setMonth(month); | |
header.textProperty().bind(Bindings.createStringBinding(() -> | |
this.month.get().format(DateTimeFormatter.ofPattern("MMMM yyyy", locale.get())), | |
this.month, this.locale)); | |
} | |
public CalendarView() { | |
this(YearMonth.now()) ; | |
} | |
public void nextMonth() { | |
month.set(month.get().plusMonths(1)); | |
} | |
public void previousMonth() { | |
month.set(month.get().minusMonths(1)); | |
} | |
private void rebuildCalendar() { | |
calendar.getChildren().clear(); | |
WeekFields weekFields = WeekFields.of(locale.get()); | |
LocalDate first = month.get().atDay(1); | |
int dayOfWeekOfFirst = first.get(weekFields.dayOfWeek()) ; | |
// column headers: | |
for (int dayOfWeek = 1 ; dayOfWeek <= 7 ; dayOfWeek++) { | |
LocalDate date = first.minusDays(dayOfWeekOfFirst - dayOfWeek); | |
DayOfWeek day = date.getDayOfWeek() ; | |
Label label = new Label(day.getDisplayName(TextStyle.SHORT_STANDALONE, locale.get())); | |
label.getStyleClass().add("calendar-day-header"); | |
GridPane.setHalignment(label, HPos.CENTER); | |
calendar.add(label, dayOfWeek - 1, 0); | |
} | |
LocalDate firstDisplayedDate = first.minusDays(dayOfWeekOfFirst - 1); | |
LocalDate last = month.get().atEndOfMonth() ; | |
int dayOfWeekOfLast = last.get(weekFields.dayOfWeek()); | |
LocalDate lastDisplayedDate = last.plusDays(7 - dayOfWeekOfLast); | |
PseudoClass beforeMonth = PseudoClass.getPseudoClass("before-display-month"); | |
PseudoClass afterMonth = PseudoClass.getPseudoClass("after-display-month"); | |
for (LocalDate date = firstDisplayedDate ; ! date.isAfter(lastDisplayedDate) ; date = date.plusDays(1)) { | |
Label label = new Label(String.valueOf(date.getDayOfMonth())); | |
label.getStyleClass().add("calendar-cell"); | |
label.pseudoClassStateChanged(beforeMonth, date.isBefore(first)); | |
label.pseudoClassStateChanged(afterMonth, date.isAfter(last)); | |
GridPane.setHalignment(label, HPos.CENTER); | |
int dayOfWeek = date.get(weekFields.dayOfWeek()) ; | |
int daysSinceFirstDisplayed = (int) firstDisplayedDate.until(date, ChronoUnit.DAYS); | |
int weeksSinceFirstDisplayed = daysSinceFirstDisplayed / 7 ; | |
calendar.add(label, dayOfWeek - 1, weeksSinceFirstDisplayed + 1); | |
} | |
} | |
public Node getView() { | |
return view ; | |
} | |
public final ObjectProperty<YearMonth> monthProperty() { | |
return this.month; | |
} | |
public final YearMonth getMonth() { | |
return this.monthProperty().get(); | |
} | |
public final void setMonth(final YearMonth month) { | |
this.monthProperty().set(month); | |
} | |
public final ObjectProperty<Locale> localeProperty() { | |
return this.locale; | |
} | |
public final java.util.Locale getLocale() { | |
return this.localeProperty().get(); | |
} | |
public final void setLocale(final java.util.Locale locale) { | |
this.localeProperty().set(locale); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Locale; | |
import javafx.application.Application; | |
import javafx.geometry.Insets; | |
import javafx.geometry.Pos; | |
import javafx.scene.Scene; | |
import javafx.scene.control.Button; | |
import javafx.scene.control.ComboBox; | |
import javafx.scene.control.ListCell; | |
import javafx.scene.layout.BorderPane; | |
import javafx.stage.Stage; | |
public class CalendarViewTest extends Application { | |
@Override | |
public void start(Stage primaryStage) { | |
CalendarView calendarView = new CalendarView() ; | |
Button next = new Button(">"); | |
next.setOnAction(e -> calendarView.nextMonth()); | |
Button previous = new Button("<"); | |
previous.setOnAction(e -> calendarView.previousMonth()); | |
ComboBox<Locale> localeCombo = new ComboBox<>(); | |
localeCombo.getItems().addAll(Locale.getAvailableLocales()); | |
localeCombo.setValue(Locale.getDefault()); | |
localeCombo.setCellFactory(lv -> new LocaleCell()); | |
localeCombo.setButtonCell(new LocaleCell()); | |
calendarView.localeProperty().bind(localeCombo.valueProperty()); | |
BorderPane.setAlignment(localeCombo, Pos.CENTER); | |
BorderPane.setMargin(localeCombo, new Insets(10)); | |
BorderPane root = new BorderPane(calendarView.getView(), localeCombo, next, null, previous); | |
Scene scene = new Scene(root); | |
primaryStage.setScene(scene); | |
primaryStage.show(); | |
} | |
public static class LocaleCell extends ListCell<Locale> { | |
@Override | |
public void updateItem(Locale locale, boolean empty) { | |
super.updateItem(locale, empty); | |
setText(locale == null ? null : locale.getDisplayName(locale)); | |
} | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi James,
Am hoping to try out your Java Calendar. There are 4 errors in Eclipse Markers as Java Problems.
Please communicate back.
Thank you.
Jon