如何在JavaFX 8的DatePicker上修改月份和年份控件?

时间:2014-10-13 13:39:54

标签: java css user-interface datepicker javafx

对于我正在开发的应用程序,许多手指较大且视力较差的老客户需要在DatePicker弹出窗口中选择箭头和日期/年份标签更大。

在DatePicker类的文档中,描述了用于修改日历本身内的DateCells的方法,但没有进一步描述或详细说明弹出窗口的构造。 (至少,代码不能自我记录,因为我的Java开发入门级知识可以掌握任何提示)

My implementation ends up looking like this

有时当我打开和关闭日历时,月/年控件中字体和按钮的大小会有波动,但我不知道如何或为什么。我假设一些CSS可能会篡改它,但我不知道为什么它有时可以工作,有时不工作。 CSS的真实本质一直让我失望。

我知道它[DatePicker]基于一个ComboBox,但我绝对不知道它是如何构建和完全实现的,更不用说如何修改它中的特定元素了。

是否有我可以调用的方法/覆盖,或者我可以在线修改参数以正确设置这些样式?另外,我可以在CSS中调用类和元素的组合来设置这些样式,以避免必须如上所述直接修改DatePicker本身吗?

这是我的实施:

@FXML private DatePicker dPicker;

final Callback<DatePicker, DateCell> dayCellFactory =
            new Callback<DatePicker, DateCell>() {
                @Override
                public DateCell call(final DatePicker datePicker) {
                    return new DateCell() {
                        @Override
                        public void updateItem(LocalDate item, boolean empty) {
                            super.updateItem(item, empty);

                            setMinSize(100,100);
                            setStyle("-fx-font-size:50px;-fx-font-weight:bold;");
                        }
                    };
                }
            };

dPicker.setDayCellFactory(dayCellFactory);

...这里是DataPicker.java:

/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javafx.scene.control;

// editor and converter code in sync with ComboBox 4858:e60e9a5396e6

import java.time.LocalDate;
import java.time.DateTimeException;
import java.time.chrono.Chronology;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DecimalStyle;
import java.time.format.FormatStyle;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableProperty;
import javafx.util.Callback;
import javafx.util.StringConverter;

import com.sun.javafx.css.converters.BooleanConverter;
import com.sun.javafx.scene.control.skin.DatePickerSkin;
import com.sun.javafx.scene.control.skin.resources.ControlResources;


/**
 * The DatePicker control allows the user to enter a date as text or
 * to select a date from a calendar popup. The calendar is based on
 * either the standard ISO-8601 chronology or any of the other
 * chronology classes defined in the java.time.chrono package.
 *
 * <p>The {@link #valueProperty() value} property represents the
 * currently selected {@link java.time.LocalDate}.  An initial date can
 * be set via the {@link #DatePicker(java.time.LocalDate) constructor}
 * or by calling {@link #setValue(java.time.LocalDate) setValue()}.  The
 * default value is null.
 *
 * <pre><code>
 * final DatePicker datePicker = new DatePicker();
 * datePicker.setOnAction(new EventHandler() {
 *     public void handle(Event t) {
 *         LocalDate date = datePicker.getValue();
 *         System.err.println("Selected date: " + date);
 *     }
 * });
 * </code></pre>
 *
 * The {@link #chronologyProperty() chronology} property specifies a
 * calendar system to be used for parsing, displaying, and choosing
 * dates.
 * The {@link #valueProperty() value} property is always defined in
 * the ISO calendar system, however, so applications based on a
 * different chronology may use the conversion methods provided in the
 * {@link java.time.chrono.Chronology} API to get or set the
 * corresponding {@link java.time.chrono.ChronoLocalDate} value. For
 * example:
 *
 * <pre><code>
 * LocalDate isoDate = datePicker.getValue();
 * ChronoLocalDate chronoDate =
 *     ((isoDate != null) ? datePicker.getChronology().date(isoDate) : null);
 * System.err.println("Selected date: " + chronoDate);
 * </code></pre>
 *
 *
 * @since JavaFX 8.0
 */
public class DatePicker extends ComboBoxBase<LocalDate> {

private LocalDate lastValidDate = null;
private Chronology lastValidChronology = IsoChronology.INSTANCE;

/**
 * Creates a default DatePicker instance with a <code>null</code> date value set.
 */
public DatePicker() {
    this(null);

    valueProperty().addListener(new InvalidationListener() {
        @Override public void invalidated(Observable observable) {
            LocalDate date = getValue();
            Chronology chrono = getChronology();

            if (validateDate(chrono, date)) {
                lastValidDate = date;
            } else {
                System.err.println("Restoring value to " +
                            ((lastValidDate == null) ? "null" : getConverter().toString(lastValidDate)));
                setValue(lastValidDate);
            }
        }
    });

    chronologyProperty().addListener(new InvalidationListener() {
        @Override public void invalidated(Observable observable) {
            LocalDate date = getValue();
            Chronology chrono = getChronology();

            if (validateDate(chrono, date)) {
                lastValidChronology = chrono;
            } else {
                System.err.println("Restoring value to " + lastValidChronology);
                setChronology(lastValidChronology);
            }
        }
    });
}

private boolean validateDate(Chronology chrono, LocalDate date) {
    try {
        if (date != null) {
            chrono.date(date);
        }
        return true;
    } catch (DateTimeException ex) {
        System.err.println(ex);
        return false;
    }
}

/**
 * Creates a DatePicker instance and sets the
 * {@link #valueProperty() value} to the given date.
 *
 * @param localDate to be set as the currently selected date in the DatePicker. Can be null.
 */
public DatePicker(LocalDate localDate) {
    setValue(localDate);
    getStyleClass().add(DEFAULT_STYLE_CLASS);
    setEditable(true);
}


/***************************************************************************
 *                                                                         *
 * Properties                                                                 *
 *                                                                         *
 **************************************************************************/


/**
 * A custom cell factory can be provided to customize individual
 * day cells in the DatePicker popup. Refer to {@link DateCell}
 * and {@link Cell} for more information on cell factories.
 * Example:
 *
 * <pre><code>
 * final Callback&lt;DatePicker, DateCell&gt; dayCellFactory = new Callback&lt;DatePicker, DateCell&gt;() {
 *     public DateCell call(final DatePicker datePicker) {
 *         return new DateCell() {
 *             &#064;Override public void updateItem(LocalDate item, boolean empty) {
 *                 super.updateItem(item, empty);
 *
 *                 if (MonthDay.from(item).equals(MonthDay.of(9, 25))) {
 *                     setTooltip(new Tooltip("Happy Birthday!"));
 *                     setStyle("-fx-background-color: #ff4444;");
 *                 }
 *                 if (item.equals(LocalDate.now().plusDays(1))) {
 *                     // Tomorrow is too soon.
 *                     setDisable(true);
 *                 }
 *             }
 *         };
 *     }
 * };
 * datePicker.setDayCellFactory(dayCellFactory);
 * </code></pre>
 *
 * @defaultValue null
 */
private ObjectProperty<Callback<DatePicker, DateCell>> dayCellFactory;
public final void setDayCellFactory(Callback<DatePicker, DateCell> value) {
    dayCellFactoryProperty().set(value);
}
public final Callback<DatePicker, DateCell> getDayCellFactory() {
    return (dayCellFactory != null) ? dayCellFactory.get() : null;
}
public final ObjectProperty<Callback<DatePicker, DateCell>> dayCellFactoryProperty() {
    if (dayCellFactory == null) {
        dayCellFactory = new SimpleObjectProperty<Callback<DatePicker, DateCell>>(this, "dayCellFactory");
    }
    return dayCellFactory;
}



/**
 * The calendar system used for parsing, displaying, and choosing
 * dates in the DatePicker control.
 *
 * <p>The default value is returned from a call to
 * {@code Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT))}.
 * The default is usually {@link java.time.chrono.IsoChronology} unless
 * provided explicitly in the {@link java.util.Locale} by use of a
 * Locale calendar extension.
 *
 * Setting the value to <code>null</code> will restore the default
 * chronology.
 */
public final ObjectProperty<Chronology> chronologyProperty() {
    return chronology;
}
private ObjectProperty<Chronology> chronology =
    new SimpleObjectProperty<Chronology>(this, "chronology", null);
public final Chronology getChronology() {
    Chronology chrono = chronology.get();
    if (chrono == null) {
        try {
            chrono = Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT));
        } catch (Exception ex) {
            System.err.println(ex);
        }
        if (chrono == null) {
            chrono = IsoChronology.INSTANCE;
        }
        //System.err.println(chrono);
    }
    return chrono;
}
public final void setChronology(Chronology value) {
    chronology.setValue(value);
}


/**
 * Whether the DatePicker popup should display a column showing
 * week numbers.
 *
 * <p>The default value is specified in a resource bundle, and
 * depends on the country of the current locale.
 */
public final BooleanProperty showWeekNumbersProperty() {
    if (showWeekNumbers == null) {
        String country = Locale.getDefault(Locale.Category.FORMAT).getCountry();
        boolean localizedDefault =
            (!country.isEmpty() &&
             ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country));
        showWeekNumbers = new StyleableBooleanProperty(localizedDefault) {
            @Override public CssMetaData<DatePicker,Boolean> getCssMetaData() {
                return StyleableProperties.SHOW_WEEK_NUMBERS;
            }

            @Override public Object getBean() {
                return DatePicker.this;
            }

            @Override public String getName() {
                return "showWeekNumbers";
            }
        };
    }
    return showWeekNumbers;
}
private BooleanProperty showWeekNumbers;
public final void setShowWeekNumbers(boolean value) {
    showWeekNumbersProperty().setValue(value);
}
public final boolean isShowWeekNumbers() {
    return showWeekNumbersProperty().getValue();
}


// --- string converter
/**
 * Converts the input text to an object of type LocalDate and vice
 * versa.
 *
 * <p>If not set by the application, the DatePicker skin class will
 * set a converter based on a {@link java.time.DateTimeFormatter}
 * for the current {@link java.util.Locale} and
 * {@link #chronologyProperty() chronology}. This formatter is
 * then used to parse and display the current date value.
 *
 * Setting the value to <code>null</code> will restore the default
 * converter.
 *
 * <p>Example using an explicit formatter:
 * <pre><code>
 * datePicker.setConverter(new StringConverter&lt;LocalDate&gt;() {
 *     String pattern = "yyyy-MM-dd";
 *     DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
 *
 *     {
 *         datePicker.setPromptText(pattern.toLowerCase());
 *     }
 *
 *     &#064;Override public String toString(LocalDate date) {
 *         if (date != null) {
 *             return dateFormatter.format(date);
 *         } else {
 *             return "";
 *         }
 *     }
 *
 *     &#064;Override public LocalDate fromString(String string) {
 *         if (string != null && !string.isEmpty()) {
 *             return LocalDate.parse(string, dateFormatter);
 *         } else {
 *             return null;
 *         }
 *     }
 * });
 * </code></pre>
 * <p>Example that wraps the default formatter and catches parse exceptions:
 * <pre><code>
 *   final StringConverter&lt;LocalDate&gt; defaultConverter = datePicker.getConverter();
 *   datePicker.setConverter(new StringConverter&lt;LocalDate&gt;() {
 *       &#064;Override public String toString(LocalDate value) {
 *           return defaultConverter.toString(value);
 *       }
 *
 *       &#064;Override public LocalDate fromString(String text) {
 *           try {
 *               return defaultConverter.fromString(text);
 *           } catch (DateTimeParseException ex) {
 *               System.err.println("HelloDatePicker: "+ex.getMessage());
 *               throw ex;
 *           }
 *       }
 *   });
 * </code></pre>
 *
 * @see javafx.scene.control.ComboBox#converterProperty
 */
public final ObjectProperty<StringConverter<LocalDate>> converterProperty() { return converter; }
private ObjectProperty<StringConverter<LocalDate>> converter =
        new SimpleObjectProperty<StringConverter<LocalDate>>(this, "converter", null);
public final void setConverter(StringConverter<LocalDate> value) { converterProperty().set(value); }
public final StringConverter<LocalDate> getConverter() {
    StringConverter<LocalDate> converter = converterProperty().get();
    if (converter != null) {
        return converter;
    } else {
        return defaultConverter;
    }
}

private StringConverter<LocalDate> defaultConverter = new StringConverter<LocalDate>() {
    @Override public String toString(LocalDate value) {
        if (value != null) {
            Locale locale = Locale.getDefault(Locale.Category.FORMAT);
            Chronology chrono = getChronology();
            ChronoLocalDate cDate;
            try {
                cDate = chrono.date(value);
            } catch (DateTimeException ex) {
                System.err.println(ex);
                chrono = IsoChronology.INSTANCE;
                cDate = value;
            }
            DateTimeFormatter dateFormatter =
                DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
                                 .withLocale(locale)
                                 .withChronology(chrono)
                                 .withDecimalStyle(DecimalStyle.of(locale));

            String pattern =
                DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT,
                                                                     null, chrono, locale);

            if (pattern.contains("yy") && !pattern.contains("yyy")) {
                // Modify pattern to show four-digit year, including leading zeros.
                String newPattern = pattern.replace("yy", "yyyy");
                //System.err.println("Fixing pattern ("+forParsing+"): "+pattern+" -> "+newPattern);
                dateFormatter = DateTimeFormatter.ofPattern(newPattern)
                                                 .withDecimalStyle(DecimalStyle.of(locale));
            }

            return dateFormatter.format(cDate);
        } else {
            return "";
        }
    }

    @Override public LocalDate fromString(String text) {
        if (text != null && !text.isEmpty()) {
            Locale locale = Locale.getDefault(Locale.Category.FORMAT);
            Chronology chrono = getChronology();

            String pattern =
                DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT,
                                                                     null, chrono, locale);
            DateTimeFormatter df =
                new DateTimeFormatterBuilder().parseLenient()
                                              .appendPattern(pattern)
                                              .toFormatter()
                                              .withChronology(chrono)
                                              .withDecimalStyle(DecimalStyle.of(locale));
            TemporalAccessor temporal = df.parse(text);
            ChronoLocalDate cDate = chrono.date(temporal);
            return LocalDate.from(cDate);
        }
        return null;
    }
};


// --- Editor
/**
 * The editor for the DatePicker.
 *
 * @see javafx.scene.control.ComboBox#editorProperty
 */
private ReadOnlyObjectWrapper<TextField> editor;
public final TextField getEditor() {
    return editorProperty().get();
}
public final ReadOnlyObjectProperty<TextField> editorProperty() {
    if (editor == null) {
        editor = new ReadOnlyObjectWrapper<TextField>(this, "editor");
        editor.set(new ComboBoxListViewSkin.FakeFocusTextField());
    }
    return editor.getReadOnlyProperty();
}

/** {@inheritDoc} */
@Override protected Skin<?> createDefaultSkin() {
    return new DatePickerSkin(this);
}


/***************************************************************************
 *                                                                         *
 * Stylesheet Handling                                                     *
 *                                                                         *
 **************************************************************************/

private static final String DEFAULT_STYLE_CLASS = "date-picker";

 /**
  * @treatAsPrivate implementation detail
  */
private static class StyleableProperties {
    private static final String country =
        Locale.getDefault(Locale.Category.FORMAT).getCountry();
    private static final CssMetaData<DatePicker, Boolean> SHOW_WEEK_NUMBERS =
          new CssMetaData<DatePicker, Boolean>("-fx-show-week-numbers",
               BooleanConverter.getInstance(),
               (!country.isEmpty() &&
                    ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country))) {
        @Override public boolean isSettable(DatePicker n) {
            return n.showWeekNumbers == null || !n.showWeekNumbers.isBound();
        }

        @Override public StyleableProperty<Boolean> getStyleableProperty(DatePicker n) {
            return (StyleableProperty)n.showWeekNumbersProperty();
        }
    };

    private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;

    static {
        final List<CssMetaData<? extends Styleable, ?>> styleables =
            new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
        Collections.addAll(styleables,
            SHOW_WEEK_NUMBERS
        );
        STYLEABLES = Collections.unmodifiableList(styleables);
    }
}

/**
 * @return The CssMetaData associated with this class, which may include the
 * CssMetaData of its super classes.
 */
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
    return StyleableProperties.STYLEABLES;
}

/**
 * {@inheritDoc}
 */
@Override
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
    return getClassCssMetaData();
}
}

...并提前感谢您的耐心和帮助我。

1 个答案:

答案 0 :(得分:0)

更改外观的最佳方法是使用外部CSS。您可以参考default stylesheet的源代码来查看默认值的定义方式:DatePicker的有趣部分位于底部附近(编写本文时的第2932行)。

只需执行

即可更改整个控件的字体大小
.date-picker {
    -fx-font-size: 18pt ;
}

最终看起来有点混乱,所以在一天中添加一些填充名称会有所帮助。箭头将自动缩放以适合,因此它们会随着月份和年份标签上字体大小的增加而增加尺寸。

这是一个完整的例子:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.DatePicker;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;


public class DatePickerExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        DatePicker datePicker = new DatePicker();
        VBox root = new VBox(datePicker);

        Scene scene = new Scene(root, 250, 150);

        scene.getStylesheets().add("date-picker-readable.css");

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

日期选择-readable.css:

.date-picker {
    -fx-font-size: 18pt; 
    -fx-font-weight: bold ;
}
.date-picker .day-name-cell {
    -fx-padding: 10px ;
}