Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Establish a consistent mask/formatter/validation system for SwingSet components in formatting subpackage #18

Open
bpangburn opened this issue Dec 11, 2020 · 9 comments
Assignees

Comments

@bpangburn
Copy link
Owner

bpangburn commented Dec 11, 2020

The current InputVerifiers in SSFormattedTextField() and its child classes could be improved to better support masks, formatters, and overall validation.

Review:

Once there is better mask support, SSTextField mask options should be deprecated in favour of SSFormattedTextField and its child classes.

@bpangburn bpangburn self-assigned this Dec 11, 2020
@bpangburn
Copy link
Owner Author

This is the main documentation that I've been reviewing and using to update some of the JavaDoc in SSFormattedTextField:
https://docs.oracle.com/en/java/javase/17/docs/api/java.desktop/javax/swing/JFormattedTextField.html

Generally a JFormattedTextField is going to have an AbstractFormatter which takes care of translating an object into text and vice versa and can vary based on whether a value is null, has the focus, or does not have the focus.

The 'problem' is that the formatter doesn't do range or other validation to make sure that the object is not only formatted correctly, but also valid for the use case. I think a custom formatter could probably be written to do some validation, but is not the default. Therefore SSFormattedTextField adds an InputVerifier that determines if the object contains valid values for the application and can lock the user in the field until a valid value is provided.

The implementation of InputVerifier is based on the sample provided with link above, but also calls validateField() which can be overridden by the developer to do range checking, etc. I think the idea here was to eliminate the need for a full implementation of InputVerifier, only requiring a custom validateField() if needed.

Also per the link above:

Warning: As the AbstractFormatter will typically install a DocumentFilter on the Document, and a NavigationFilter on the JFormattedTextField you should not install your own. If you do, you are likely to see odd behavior in that the editing policy of the AbstractFormatter will not be enforced.

For this reason, I think we want to avoid adding back key/document listeners. It would be better to write a custom formatter (as I plan to do for SSDateField). JavaDoc also mentions that the AbstractFormatter determines when to send back new values (so an updated value may not be for every keystroke).

Finally, the way SSFormattedTextField is used in SwingSet, it would almost be better as an interface with some default implementations, but I don't think there's an easy way to do that. As written I don't see a developer using SwingSet adding base class SSFormattedTextFields to their screen. I think one would always want to use a child class extending SSFormattedTextField.

@errael errael mentioned this issue Dec 27, 2021
@errael
Copy link
Contributor

errael commented Dec 29, 2021

While exploring I played with the following to customize the UI (Or GUI if you prefer)

fmt.setAllowsInvalid(false); for the date field editor formatter. Seemed to work, putting in an invalid character caused an immediate bell and no change to the text field. And it's worth noting that fmt.setCommitsOnValidEdit(true) changes how often setValue is invoked.

Also of interest, the text field could dynamically show when the input text if valid/invalid with something like:

    ssFormattedTextField.addPropertyChangeListener("editValid", (e)->{
        decorateTextFieldValidity(ssFormattedTextField, (boolean) e.getNewValue());
    });
    ...
void decorateTextFieldValidity(SSFormattedTextField tf, boolean isValidValue) {
    if (!isValidValue) could adjust background color or show tiny error badge
    else clear error indications
}

@errael
Copy link
Contributor

errael commented Dec 30, 2021

It might be useful to provide some actions and optional KeyBindings for them. Might be

  • SetFieldNull. This occurred to me in the context of using a MaskFormatter, needs experimentation (might not make sense). Could be a toggle: if field is currently null, could bring back the editmask.
  • Undo field. Restore field to original database fetched from database. (not whole row)

@errael
Copy link
Contributor

errael commented Feb 23, 2022

Woke up this morning with an idea of why I was having background color capture problems with the mask formatter. I'm guessing that when dynamic error indication, a field can start out with the error color (note with the new demo how the non-null field starts out red) and this is causing the problem.

But rather than trying to fix this problem, I'm suggesting that the non-focused default background color be picked up from swing's UIDefaults, possibly with an override SwingSet provides (or possible a pair of overrides, global and per component), then this whole capture technique can be taken out.

@bpangburn
Copy link
Owner Author

Your solution regarding the background colors sounds like a good approach.

@bpangburn
Copy link
Owner Author

Copying some correspondence with @errael from outside of GitHub:

I don't have strong opinions on the Formatted fields other than:

  1. I wanted to redo any mask fields, especially the date field (you've done this)
  2. I want to add an option to SSPercentField to control how the number is stored in the database:
    A. 10% is stored as 0.1
    B. 10% is stored as 10.0

We typically store 10% as 10.0 and handle any division by 100 in the code (e.g. option B). The SSPercentField seems to divide by 100 (e.g. option A: [SS_PERCENT_FIELD]: Object to be passed to database is 0.78). For Numeric, Percent, and possibly Currency, it would be nice to specify the # of decimal places to display and default all to 2 decimal places displayed? I think I've mentioned before that Diego wrote the formatted fields and we've not used them much. We're using them in two spots that I can think of, and we use SSNumericField in lieu of SSPercentField. Also, the way the SSPercentField works, the user has to type "%". The "%" looks nice on the screen I guess, but not a fan of typing it.

@errael
Copy link
Contributor

errael commented Dec 3, 2024

SSPercentField now has the following. (from DecimalFormat)

	/**
	 * Get the multiplier used in percent.
	 * @return multiplier
	 */
	public int getMultiplier() {
		return getNumberFormatParam((nf) -> nf.getMultiplier());
	}

	/**
	 * Sets the multiplier for use in percent. With multiplier 100,
	 * 1.23 is formatted as "123", and "123" is parsed into 1.23.
	 * @param multiplier the new multiplier
	 */
	public void setMultiplier(int multiplier) {
		setFormatParam((nf) -> nf.setMultiplier(multiplier));
	}

@errael
Copy link
Contributor

errael commented Dec 3, 2024

The implementation of InputVerifier is based on the sample provided with link above, but also calls validateField() which can be overridden by the developer to do range checking, etc. I think the idea here was to eliminate the need for a full implementation of InputVerifier, only requiring a custom validateField() if needed.

Mentioning for completeness. Subclasses of SSFormattedTextField can override componentValidate. Application validation can be done through SSCommon's setValidator().

About the name change of validateField() to componentValidate(). I added SSComponentInterface.componentValidate(), then later discovered validateField().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

2 participants