如何从支持常规文本的 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(); }
}
我想为用户提供在数据库中搜索的能力。他们应该能够在一列上按文本搜索,在另一列上按日期搜索。但是我不想在 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(); }
}