如何在 JavaFX 中制作 TimeSpinner? [英] How to make a TimeSpinner in JavaFX?

查看:25
本文介绍了如何在 JavaFX 中制作 TimeSpinner?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想制作一个微调器来输入时间,就像这样:

谢谢

解决方案

我认为选择编辑器各个部分的最佳方法是查看 caretPosition 并根据需要增加/减少适当的部分.您还可以设置 TextFormatter 在编辑器上控制允许的输入等

这是一个快速尝试:不打算提高生产质量,但应该是一个好的开始:

import java.time.LocalTime;导入 java.time.format.DateTimeFormatter;导入 javafx.beans.property.ObjectProperty;导入 javafx.beans.property.SimpleObjectProperty;导入 javafx.scene.control.Spinner;导入 javafx.scene.control.SpinnerValueFactory;导入 javafx.scene.control.TextFormatter;导入 javafx.scene.input.InputEvent;导入 javafx.util.StringConverter;公共类 TimeSpinner 扩展了 Spinner{//Mode 代表当前正在编辑的单位.//为方便起见,公开递增和递减的方法//单位,用于在微调器的编辑器中选择适当的部分枚举模式{小时 {@覆盖本地时间增量(本地时间时间,整数步){返回 time.plusHours(steps);}@覆盖无效选择(TimeSpinner微调器){int index = spinner.getEditor().getText().indexOf(':');spinner.getEditor().selectRange(0, index);}},分钟 {@覆盖本地时间增量(本地时间时间,整数步){返回 time.plusMinutes(steps);}@覆盖无效选择(TimeSpinner微调器){int hrIndex = spinner.getEditor().getText().indexOf(':');int minIndex = spinner.getEditor().getText().indexOf(':', hrIndex + 1);spinner.getEditor().selectRange(hrIndex+1, minIndex);}},秒{@覆盖本地时间增量(本地时间时间,整数步){返回 time.plusSeconds(steps);}@覆盖无效选择(TimeSpinner微调器){int index = spinner.getEditor().getText().lastIndexOf(':');spinner.getEditor().selectRange(index+1, spinner.getEditor().getText().length());}};抽象本地时间增量(本地时间时间,整数步);抽象无效选择(TimeSpinner微调器);本地时间递减(本地时间时间,整数步){返回增量(时间,-步骤);}}//包含当前编辑模式的属性:私有最终 ObjectPropertymode = new SimpleObjectProperty<>(Mode.HOURS);公共对象属性<模式>模式属性(){返回模式;}公共最终模式 getMode() {返回 modeProperty().get();}public final void setMode(模式模式){modeProperty().set(mode);}公共 TimeSpinner(本地时间){设置可编辑(真);//创建一个 StringConverter 用于在文本之间进行转换//编辑器和实际值:DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");StringConverterlocalTimeConverter = new StringConverter() {@覆盖公共字符串 toString(本地时间){返回 formatter.format(time);}@覆盖public LocalTime fromString(String string) {String[] tokens = string.split(":");int hours = getIntField(tokens, 0);int 分钟 = getIntField(tokens, 1) ;int seconds = getIntField(tokens, 2);int totalSeconds = (小时 * 60 + 分钟) * 60 + 秒;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()) {返回 0 ;}返回 Integer.parseInt(tokens[index]);}};//textFormatter 都管理文本 <->本地时间转换,//并否决任何无效的编辑.我们只是确保我们有//两个冒号,中间只有数字:TextFormattertextFormatter = new TextFormatter(localTimeConverter, LocalTime.now(), c -> {String newText = c.getControlNewText();if (newText.matches("[0-9]{0,2}:[0-9]{0,2}:[0-9]{0,2}")) {返回 c ;}返回空;});//微调器值工厂定义增量和减量//委派到当前编辑模式:SpinnerValueFactoryvalueFactory = new SpinnerValueFactory() {{setConverter(localTimeConverter);设置值(时间);}@覆盖公共无效减量(整数步){setValue(mode.get().decrement(getValue(), steps));mode.get().select(TimeSpinner.this);}@覆盖公共无效增量(整数步){setValue(mode.get().increment(getValue(), steps));mode.get().select(TimeSpinner.this);}};this.setValueFactory(valueFactory);this.getEditor().setTextFormatter(textFormatter);//当用户与编辑器交互时更新模式.//这有点像 hack,例如调用 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);如果(caretPos <= hrIndex){mode.set(Mode.HOURS);} else if (caretPos <= minIndex) {mode.set( Mode.MINUTES );} 别的 {模式.设置(模式.秒);}});//当模式改变时,选择新的部分:mode.addListener((obs, oldMode, newMode) -> newMode.select(this));}公共 TimeSpinner() {this(LocalTime.now());}}

这是一个使用它的快速示例:

import java.time.format.DateTimeFormatter;导入 javafx.application.Application;导入 javafx.scene.Scene;导入 javafx.scene.layout.StackPane;导入 javafx.stage.Stage;公共类 TimeSpinnerExample 扩展应用程序 {@覆盖公共无效开始(阶段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(场景);primaryStage.show();}公共静态无效主(字符串 [] 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆