Milchreis / UiBooster

🚀 Creates fast and easy dialogs for utility tools
GNU General Public License v3.0
117 stars 12 forks source link

Value Change / Binding change on Enter #141

Open JoJpeg opened 2 months ago

JoJpeg commented 2 months ago

Hey! I love this Library! It is very nice and easy to work with, Thank you! I was wondering if there is a way to make the value changes in the Form only take place when hitting the Enter key. I feel like this is the expected behavior for me and it leads to a lot of problems when making typos. Am I missing something or is there a bigger reason to not have this functionality?

Milchreis commented 2 months ago

Hi JoJpeg,

thank you very much 😊 Do you have a code snippet for me? I don't really get the problem. The ChangeListener and the data-binding are working with the key-release-event. That means the change will propagade after each key press. Which formelement do you use (text-input, date-picker ...)?

JoJpeg commented 2 months ago

It might just be a personal preference.. I was using the Form.addText() method and the changelListener to display and change numerical values. I was not used to vaules changing before hitting the Return key as i said. Also i needed to parse the values after each keystroke to remove all non-numerical chars. It just seemed like i was using the form for something it was not intended to to. I could solve my problem by building my own custom FormElement using a JSpinner. I think i will also write one that has the value change on Return only..

But still i am curious why you have so many niche form elements like a date or color picker but no numerical spinner element?

Milchreis commented 2 months ago

Ok, I get it. The "return"-key event was not necessary for me, until now. Every change in an input element was the more interesting event, I thought. In that way a validation is possible before the user hits enter.

Please share your JSpinner component in this issue or better in a pull-request. It would be a great addition for the library 😊 UiBooster was an idea years ago and I added necessary features from my real-life tools/projects. A spinner was simply not needed 😅 But it's a great feature-request.

JoJpeg commented 2 months ago

I understand :) was expecting something like that! My current component is not using any of the binding logic and is generally not really fitting your architecture. The "Value changes when pressed Return" functionality comes by default in the spinner.

But for anybody who is interested, this is the code:


public abstract class NumberFieldElement extends FormElement<Double> {

    JSpinner spinner;
    float initialValue;
    float stepSize;
    String innerLabel;

    public NumberFieldElement(String label, float initialValue, float stepSize) {
        //sending no label to the super class since i dont like how much space it takes
        super("");
        this.initialValue = initialValue;
        this.stepSize = stepSize;
        this.innerLabel = label;
    }

    @Override
    public JComponent createComponent(FormElementChangeListener onChange) {

        // create a Horizontal pane that eavenly distributes the elements
        JPanel box = new JPanel();

        // Set the box to span the entire width
        box.setLayout(new BorderLayout());

        spinner = new JSpinner();

        // set the decimal places
        spinner.setModel(new SpinnerNumberModel(initialValue, -9999999999.9, 99999999.9, this.stepSize));

        // set the initial value
        spinner.setValue(initialValue);

        // call the onValueChanged method when the value changes
        spinner.addChangeListener(e -> {
            onValueChanged();
        });

        JLabel label = new JLabel(innerLabel);

        label.setPreferredSize(new Dimension(80, 30));
        label.setSize(new Dimension(80, 30));
        label.setMaximumSize(new Dimension(80, 30));

        // add the label to the left
        box.add(label, BorderLayout.WEST);

        // spacer
        box.add(Box.createHorizontalStrut(10));

        // add the spinner to the right
        box.add(spinner, BorderLayout.EAST);
        return box;
    }

    @Override
    public void setEnabled(boolean enable) {
        spinner.setEnabled(enable);
    }

    @Override
    public Double getValue() {
        Object value = spinner.getValue();
        Double result = null;
        if (value instanceof Float) {
            result = ((Float) value).doubleValue();
            return result;
        } else if (value instanceof Integer) {
            result = ((Integer) value).doubleValue();
            return result;
        } else {
            return (Double) spinner.getValue();
        }
    }

    @Override
    public void setValue(Double value) {
        spinner.setValue(value);
    }

    // abstract method to force the user to implement the onValueChanged method when
    // implementing this class
    public abstract void onValueChanged();

}

The Element is Initialized like this:

   builder.addCustomElement(
                        new NumberFieldElement(fieldName, initialValue, isInteger ? 1f : 0.01f) {
                            @Override
                            public void onValueChanged() {
                                System.out.println("Value changed to: " + getValue());
                                myMethodToChangeData(object, fieldName, getValue().toString(), fieldType);
                            }
                        }).setID(fieldName);