如何在JavaFX中创建TimeSpinner? [英] How to make a TimeSpinner in JavaFX?
问题描述
我想让旋转器像这样输入时间:
谢谢
我认为选择编辑器各个部分的最佳方法是检查 caretPosition
,并根据需要递增/递减相应的部分。您还可以设置 <$ c $编辑器中的c> TextFormatter 用于控制允许的输入等。
这是一个快速尝试:不打算生产质量,但应该是一个良好的开端:
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.InputEvent;
import javafx.util.StringConverter;
公共类TimeSpinner扩展Spinner< LocalTime> {
// Mode表示当前正在编辑的单位。
//为方便起见,公开递增和递减
//单位的方法,以及在微调器编辑器中选择适当部分的方法
enum模式{
HOURS {
@Override
LocalTime increment(LocalTime time,int steps){
return time.plusHours(steps);
}
@Override
void select(TimeSpinner spinner){
int index = spinner.getEditor()。getText()。indexOf(':');
spinner.getEditor()。selectRange(0,index);
}
},
MINUTES {
@Override
LocalTime increment(LocalTime time,int steps){
return time.plusMinutes(steps);
}
@Override
void select(TimeSpinner spinner){
int hrIndex = spinner.getEditor()。getText()。indexOf(':');
int minIndex = spinner.getEditor()。getText()。indexOf(':',hrIndex + 1);
spinner.getEditor()。selectRange(hrIndex + 1,minIndex);
}
},
SECONDS {
@Override
LocalTime increment(LocalTime time,int steps){
return time.plusSeconds(steps);
}
@Override
void select(TimeSpinner spinner){
int index = spinner.getEditor()。getText()。lastIndexOf(':');
spinner.getEditor()。selectRange(index + 1,spinner.getEditor()。getText()。length());
}
};
abstract LocalTime increment(LocalTime time,int steps);
abstract void select(TimeSpinner spinner);
LocalTime减量(LocalTime time,int steps){
return increment(time,-steps);
}
}
//包含当前编辑模式的属性:
private final ObjectProperty< Mode> mode = new SimpleObjectProperty<>(Mode.HOURS);
public ObjectProperty< Mode> modeProperty(){
返回模式;
}
public final模式getMode(){
return modeProperty()。get();
}
public final void setMode(模式模式){
modeProperty()。set(mode);
}
public TimeSpinner(LocalTime time){
setEditable(true);
//创建一个StringConverter,用于在
//编辑器中的文本和实际值之间进行转换:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(HH: MM:SS);
StringConverter< LocalTime> localTimeConverter = new StringConverter< LocalTime>(){
@Override
public String toString(LocalTime time){
return formatter.format(time);
}
@Override
public LocalTime fromString(String string){
String [] tokens = string.split(:);
int hours = getIntField(tokens,0);
int minutes = getIntField(tokens,1);
int seconds = getIntField(tokens,2);
int totalSeconds =(小时* 60 +分钟)* 60 +秒;
返回LocalTime.of((totalSeconds / 3600)%24,(totalSeconds / 60)%60,秒%60);
}
private int getIntField(String [] tokens,int index){
if(tokens.length< = index || tokens [index] .isEmpty()) {
返回0;
}
返回Integer.parseInt(tokens [index]);
}
};
// textFormatter管理文本< - > LocalTime转换,
//并否决任何无效的编辑。我们确保我们有
//两个冒号且只有两位数字:
TextFormatter< LocalTime> textFormatter = new TextFormatter< LocalTime>(localTimeConverter,LocalTime.now(),c - > {
String newText = c.getControlNewText();
if(newText.matches([0-9] {0,2}:[0-9] {0,2}:[0-9] {0,2})){
返回c;
}
返回null;
});
//微调值工厂定义递增和递减
//委托给当前编辑模式:
SpinnerValueFactory< LocalTime> valueFactory = new SpinnerValueFactory< LocalTime>(){
{
setConverter(localTimeConverter);
setValue(time);
}
@Override
public void decrement(int steps){
setValue(mode.get()。decrement(getValue(),steps));
mode.get()。select(TimeSpinner.this);
}
@Override
public void increment(int steps){
setValue(mode.get()。increment(getValue(),steps));
mode.get()。select(TimeSpinner.this);
}
};
this.setValueFactory(valueFactory);
this.getEditor()。setTextFormatter(textFormatter);
//当用户与编辑器交互时更新模式。
//这有点像黑客,例如调用spinner.getEditor()。positionCaret()
//可能导致错误的状态。直接观察caretPostion
//虽然效果不佳;让它正常工作可能是
//从长远来看更好的方法。
this.getEditor()。addEventHandler(InputEvent.ANY,e - > {
int caretPos = this.getEditor()。getCaretPosition();
int hrIndex = this.getEditor() .getText()。indexOf(':');
int minIndex = this.getEditor()。getText()。indexOf(':',hrIndex + 1);
if(caretPos< = hrIndex){
mode.set(Mode.HOURS);
} else if(caretPos< = minIndex){
mode.set(Mode.MINUTES);
} else {
mode.set(Mode.SECONDS);
}
});
//当模式改变时,选择新部分:
mode.addListener((obs,oldMode,newMode) - > newMode.select(this));
}
public TimeSpinner(){
this(LocalTime.now());
}
}
以下是使用它的快速示例:
import java.time.format.DateTimeFormatter;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
公共类TimeSpinnerExample extends Application {
@Override
public void start(Stage primaryStage){
TimeSpinner spinner = new TimeSpinner( );
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(hh:mm:ss a);
spinner.valueProperty()。addListener((obs,oldTime,newTime) - >
System.out.println(formatter.format(newTime)));
StackPane root = new StackPane(spinner);
场景场景=新场景(root,350,120);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String [] args){
launch(args);
}
}
I would like to make a spinner to enter the time just like this : Youtube Vidéo
If someone knows where the source code is hiding, it would be perfect. But if not, I would like to try and implement it myself, but how do I even make 3 different (focusable ?) textarea like this ?
Edit : Here is what I got, but I would like to able to select the hours and the increment the hours not only the minutes (and same for the seconds ofc)
Spinner<LocalTime> spinner = new Spinner(new SpinnerValueFactory() {
{
setConverter(new LocalTimeStringConverter(FormatStyle.MEDIUM));
}
@Override
public void decrement(int steps) {
if (getValue() == null)
setValue(LocalTime.now());
else {
LocalTime time = (LocalTime) getValue();
setValue(time.minusMinutes(steps));
}
}
@Override
public void increment(int steps) {
if (this.getValue() == null)
setValue(LocalTime.now());
else {
LocalTime time = (LocalTime) getValue();
setValue(time.plusMinutes(steps));
}
}
});
spinner.setEditable(true);
This is the result I get :
Thanks
I think the best approach for selecting the individual parts of the editor is to check the caretPosition
in the editor and increment/decrement the appropriate portion as required. You can also set a TextFormatter
on the editor to control the allowed input, etc.
Here's a quick attempt: not intended to be production quality but should be a good start:
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.InputEvent;
import javafx.util.StringConverter;
public class TimeSpinner extends Spinner<LocalTime> {
// Mode represents the unit that is currently being edited.
// For convenience expose methods for incrementing and decrementing that
// unit, and for selecting the appropriate portion in a spinner's editor
enum Mode {
HOURS {
@Override
LocalTime increment(LocalTime time, int steps) {
return time.plusHours(steps);
}
@Override
void select(TimeSpinner spinner) {
int index = spinner.getEditor().getText().indexOf(':');
spinner.getEditor().selectRange(0, index);
}
},
MINUTES {
@Override
LocalTime increment(LocalTime time, int steps) {
return time.plusMinutes(steps);
}
@Override
void select(TimeSpinner spinner) {
int hrIndex = spinner.getEditor().getText().indexOf(':');
int minIndex = spinner.getEditor().getText().indexOf(':', hrIndex + 1);
spinner.getEditor().selectRange(hrIndex+1, minIndex);
}
},
SECONDS {
@Override
LocalTime increment(LocalTime time, int steps) {
return time.plusSeconds(steps);
}
@Override
void select(TimeSpinner spinner) {
int index = spinner.getEditor().getText().lastIndexOf(':');
spinner.getEditor().selectRange(index+1, spinner.getEditor().getText().length());
}
};
abstract LocalTime increment(LocalTime time, int steps);
abstract void select(TimeSpinner spinner);
LocalTime decrement(LocalTime time, int steps) {
return increment(time, -steps);
}
}
// Property containing the current editing mode:
private final ObjectProperty<Mode> mode = new SimpleObjectProperty<>(Mode.HOURS) ;
public ObjectProperty<Mode> modeProperty() {
return mode;
}
public final Mode getMode() {
return modeProperty().get();
}
public final void setMode(Mode mode) {
modeProperty().set(mode);
}
public TimeSpinner(LocalTime time) {
setEditable(true);
// Create a StringConverter for converting between the text in the
// editor and the actual value:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
StringConverter<LocalTime> localTimeConverter = new StringConverter<LocalTime>() {
@Override
public String toString(LocalTime time) {
return formatter.format(time);
}
@Override
public LocalTime fromString(String string) {
String[] tokens = string.split(":");
int hours = getIntField(tokens, 0);
int minutes = getIntField(tokens, 1) ;
int seconds = getIntField(tokens, 2);
int totalSeconds = (hours * 60 + minutes) * 60 + seconds ;
return LocalTime.of((totalSeconds / 3600) % 24, (totalSeconds / 60) % 60, seconds % 60);
}
private int getIntField(String[] tokens, int index) {
if (tokens.length <= index || tokens[index].isEmpty()) {
return 0 ;
}
return Integer.parseInt(tokens[index]);
}
};
// The textFormatter both manages the text <-> LocalTime conversion,
// and vetoes any edits that are not valid. We just make sure we have
// two colons and only digits in between:
TextFormatter<LocalTime> textFormatter = new TextFormatter<LocalTime>(localTimeConverter, LocalTime.now(), c -> {
String newText = c.getControlNewText();
if (newText.matches("[0-9]{0,2}:[0-9]{0,2}:[0-9]{0,2}")) {
return c ;
}
return null ;
});
// The spinner value factory defines increment and decrement by
// delegating to the current editing mode:
SpinnerValueFactory<LocalTime> valueFactory = new SpinnerValueFactory<LocalTime>() {
{
setConverter(localTimeConverter);
setValue(time);
}
@Override
public void decrement(int steps) {
setValue(mode.get().decrement(getValue(), steps));
mode.get().select(TimeSpinner.this);
}
@Override
public void increment(int steps) {
setValue(mode.get().increment(getValue(), steps));
mode.get().select(TimeSpinner.this);
}
};
this.setValueFactory(valueFactory);
this.getEditor().setTextFormatter(textFormatter);
// Update the mode when the user interacts with the editor.
// This is a bit of a hack, e.g. calling spinner.getEditor().positionCaret()
// could result in incorrect state. Directly observing the caretPostion
// didn't work well though; getting that to work properly might be
// a better approach in the long run.
this.getEditor().addEventHandler(InputEvent.ANY, e -> {
int caretPos = this.getEditor().getCaretPosition();
int hrIndex = this.getEditor().getText().indexOf(':');
int minIndex = this.getEditor().getText().indexOf(':', hrIndex + 1);
if (caretPos <= hrIndex) {
mode.set( Mode.HOURS );
} else if (caretPos <= minIndex) {
mode.set( Mode.MINUTES );
} else {
mode.set( Mode.SECONDS );
}
});
// When the mode changes, select the new portion:
mode.addListener((obs, oldMode, newMode) -> newMode.select(this));
}
public TimeSpinner() {
this(LocalTime.now());
}
}
And here's a quick example of using it:
import java.time.format.DateTimeFormatter;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TimeSpinnerExample extends Application {
@Override
public void start(Stage primaryStage) {
TimeSpinner spinner = new TimeSpinner();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm:ss a");
spinner.valueProperty().addListener((obs, oldTime, newTime) ->
System.out.println(formatter.format(newTime)));
StackPane root = new StackPane(spinner);
Scene scene = new Scene(root, 350, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
这篇关于如何在JavaFX中创建TimeSpinner?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!