如何从支持常规文本的 JFXtras 中模拟 LocalDateTextField?

How to make an analog of LocalDateTextField from JFXtras that supports regular text?

我想为用户提供在数据库中搜索的能力。他们应该能够在一列上按文本搜索,在另一列上按日期搜索。但是我不想在 UI 中创建两个单独的字段,我想将它们组合在一个 TextField 中。 JFXtras 中的 LocalDateTextField 对此很有用,因为它允许使用方便的选择器选择日期,还允许输入文本。但是,它会自动解析输入,如果无法将其解析为 LocalDate,则会清除该字段(按照定义应该如此)。

我的问题是:我能否让它保留文本,以便在有 LocalDate 的情况下获取 LocalDate,或者如果 LocalDate 字段为空,那么我只获取文本?我已经尝试使用 setParseErrorCallback 进行试验(尝试在那里设置文本 属性),但似乎删除发生在某处。我可以在使用 LocalDatePicker 调用 PopOver 的常规 TextField 旁边放置一个按钮,但我不确定如何复制它的外观。我如何设置它的样式,使弹出窗口像按钮一样用渐变着色?我试过 picker.getStyleClass().add("button") 但它会保留悬停发光和填充。

LocalDateTextField 选择器的外观如下:

注意填充、渐变以及它如何直接位于文本字段下方。这是按下按钮时调用的 PopOver 中的 LocalDatePicker:

总而言之:我可以更改 LocalDateTextField 的行为吗?如果可以,那么如何更改?如果不能,那么我该如何设置自己的实现样式以使其看起来相同?我试过查看源文件,但我一直无法理解那里发生的神奇之处。

最终制作了我自己的 SearchTextField。得到一个 HBox,将 TextField 和 Button 放在那里,Button 使用 LocalDatePicker 调用 PopOver,选择器的样式为 JFXtras 中的以下 css:

.LocalDatePicker {
    -fx-background-color: -fx-body-color;
    -fx-background-insets: 0 0 -1 0,0,1,2;
    -fx-background-radius: 5,5,4,3;
    -fx-padding: 0.466667em 0.333333em 0.35em 0.333333em;
    -fx-text-fill: -fx-text-base-color;
}

然后添加一些侦听器以提供所需的逻辑并完成。

这是代码(抱歉)

package view.elements;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
import jfxtras.scene.control.LocalDatePicker;
import org.controlsfx.control.PopOver;
import org.controlsfx.glyphfont.FontAwesome;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class SearchTextField extends HBox {
    private final TextField field = new TextField();
    private final LocalDatePicker picker = new LocalDatePicker();
    private final PopOver over = new PopOver(picker);
    private final Button searchButton = new Button("Искать");
    private final StringProperty promptTextProperty = new SimpleStringProperty();
    private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");

    public SearchTextField() {
        super();
        field.promptTextProperty().bind(promptTextProperty);
        field.setPrefColumnCount(20);
        over.setArrowSize(0);
        over.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER);
        picker.getStylesheets().add(SearchTextField.class.getResource("datefield.css").toExternalForm());
        picker.getStyleClass().add("LocalDatePicker");
        picker.prefWidthProperty().bind(this.widthProperty());
        picker.setMode(LocalDatePicker.Mode.SINGLE);
        picker.setAllowNull(true);
        final Button showPicker = new Button();
        showPicker.setGraphic(MethodFX.getFontAwesome().create(FontAwesome.Glyph.CALENDAR_ALT));

        showPicker.setOnAction(event -> {
            if (over.isShowing())
                over.hide();
            else
                over.show(field, -2);
        });
        field.textProperty().addListener((obs, oldText, newText) -> {
            if(newText.matches("(^(((0[1-9]|1[0-9]|2[0-8])[.](0[1-9]|1[012]))|((29|30|31)[.](0[13578]|1[02]))|((29|30)[.](0[4,6,9]|11)))[.](19|[2-9][0-9])\d\d$)|(^29[.]02[.](19|[2-9][0-9])(00|04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96)$)"))
                picker.localDateProperty().setValue(LocalDate.parse(newText, dateFormatter));
            else
                picker.localDateProperty().setValue(null);
        });
        picker.localDates().addListener((ListChangeListener<LocalDate>) c -> {
            while(c.next() && c.wasAdded()) {
                field.setText(dateFormatter.format(c.getAddedSubList().get(0)));
            }
            over.hide();
        });
        field.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue && over.isShowing())
                over.hide();
        });

        this.setSpacing(4);
        this.getChildren().addAll(field, showPicker, searchButton);
    }

    public String getText() { return field.getText(); }

    public LocalDate getLocalDate() { return picker.getLocalDate(); }

    public void hidePicker(Duration duration) { if (over.isShowing()) over.hide(duration); }

    public void setOnSearch(EventHandler<ActionEvent> eventHandler) { searchButton.setOnAction(eventHandler); }

    public StringProperty promptTextProperty() {
        return promptTextProperty;
    }

    public void setPromptText(String value) { promptTextProperty.setValue(value); }

    public String getPromptText() { return promptTextProperty.getValue(); }
}