Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Scout/HowTo/4.0/Validation and Formatting"

< Scout‎ | HowTo‎ | 4.0
(Created page with "{{ScoutPage|cat=HowTo 4.0}} This how-to describes how to validate and format FormFields and Forms = Field Validation = In order to validate field contents, overwrite th...")
 
(Replaced content with "The Scout documentation has been moved to https://eclipsescout.github.io/.")
 
Line 1: Line 1:
{{ScoutPage|cat=HowTo 4.0}}
+
The Scout documentation has been moved to https://eclipsescout.github.io/.
 
+
This how-to describes how to validate and format FormFields and Forms
+
 
+
 
+
= Field Validation  =
+
 
+
In order to validate field contents, overwrite the '''execValidateValue()''' method of a field:
+
 
+
[[Image:ValidationExecValidate.png]]
+
 
+
In that method, return the input parameter <code>rawValue</code> (or a modified value based on <code>rawValue</code>) if the value is accepted or throw a <code>VetoException()</code> with a string describing the reason for not accepting the input.
+
<pre>@Override
+
protected Long execValidateValue(Long rawValue) throws ProcessingException {
+
  if ((rawValue&nbsp;% 2)&nbsp;!= 0) {
+
    throw new VetoException("Value must be even.");
+
  }
+
  return rawValue;
+
}
+
</pre>
+
Alternatively, the '''<code>VetoException</code>''' can also be created with a title string, an error code and a severity (though it seems that the severity is always set to <code>IStatus.ERROR</code> (4) internally)
+
<pre>throw new VetoException("Invalid input", "Value must be even.", 267, IStatus.ERROR);</pre>
+
If a <code>VetoException</code> is thrown, the field is marked with a red icon, hovering the cursor over the icon will display the string passed to the exception as tooltip.
+
 
+
[[Image:ValidationMustBeEven.png]]
+
 
+
It is also possible to return a modified value from <code>execValidateValue</code> (e.g. to filter out spaces from numerical input etc.) without the need of full field formatting as described below.
+
 
+
Beside <code>IStatus.ERROR</code> there is also a <code>IStatus.WARNING</code> (2) available, which marks the field with a warning indicator.
+
 
+
<br>
+
 
+
= Formatting a field  =
+
 
+
The basic mechanism used for formatted input and output is described on the Scout Wiki about {{ScoutLink|Concepts|ValueField|ValueFields}}, specifically in the following drawing:
+
 
+
[[Image:Scout ValueField Validation.png]]
+
 
+
Basically, the <code>ParseValue()</code> method is used to remove any formatting, <code>ValidateValue()</code> is used to check for valid input and only if validation passes will <code>FormatValue()</code> be called to write the formatted output back to the field. Also, the <code>ChangedValue()</code> method or a <code>propertyChange()</code> listener will be called.
+
 
+
The following example will take a carriage number (''Wagennummer'') as input (either formatted or unformatted), check it for validity and format it if it is correct. It will then also check the number's checksum and display an error if it is not correct.
+
 
+
'''execParseValue()''' removes any formatting information (spaces, dashes):
+
<pre>@Override
+
protected String execParseValue(String text) throws ProcessingException {
+
  String result = "";
+
  if (text&nbsp;!= null) {
+
    result = text.replaceAll(" ", "").replaceAll("-", "");
+
  }
+
  return result;
+
}
+
</pre>
+
'''execFormatValue()''' will format the number using spaces and a dash:
+
<pre>@Override
+
protected String execFormatValue(String validValue) {
+
  String result = "";
+
  if (validValue&nbsp;!= null) {
+
    for (int i = 0; i &lt; validValue.length(); ++i) {
+
      result += validValue.charAt(i);
+
      if ((i == 1) || (i == 3) || (i == 7)) {
+
        result += " ";
+
      } else if (i == 10) {
+
        result += "-";
+
      }
+
    }
+
  }
+
  return result;
+
}
+
</pre>
+
'''execValidateValue()''' will check the input for valid characters and correct length only (but not the validity of the number). Note that we first call <code>clearErrorStatus()</code> as the <code>propertyChangeListener</code> may have set an error status explicitly. Also note, that we have the possibility to further remove characters we do not want to keep (and re-display) by not including them in the value that is being returned.
+
<pre>@Override
+
protected String execValidateValue(String rawValue) throws ProcessingException {
+
  clearErrorStatus();
+
  String result = "";
+
  if (rawValue&nbsp;!= null) {
+
    String text = rawValue.toLowerCase();
+
    for (int i = 0; i &lt; rawValue.length(); ++i) {
+
      if (text.charAt(i) &gt;= '0' &amp;&amp; text.charAt(i) &lt;= '9') {
+
        result += text.charAt(i);
+
      } else if (text.charAt(i) &gt;= 'a' &amp;&amp; text.charAt(i) &lt;= 'z') {
+
        throw new VetoException(TEXTS.get("ErrNoAlpha"));
+
      }
+
    }
+
    if (result.length()&nbsp;!= 12) {
+
      throw new VetoException(TEXTS.get("Err12Digits"));
+
    }
+
  } else {
+
    throw new VetoException(TEXTS.get("ErrNotEmpty"));
+
  }
+
  return result;
+
}
+
</pre>
+
We also add a '''propertyChangeListener''' to the field to check the number for validity after it has been formatted. Should it be invalid, we can still mark the field as invalid (despite <code>execValidateValue()</code> having succeeded) by our call to <code>setErrorStatus()</code>:  
+
<pre>private void registerPropertyChangeListener() {
+
  addPropertyChangeListener(IValueField.PROP_VALUE, new PropertyChangeListener() {
+
    @Override
+
    public void propertyChange(PropertyChangeEvent e) {
+
      if (!WagenUtil.checkWagennummer((String) e.getNewValue())) {
+
        setErrorStatus(TEXTS.get("ErrWgNr"));
+
      }
+
    }
+
  });
+
}
+
</pre>
+
<br> Find below a few possible inputs and the reaction of the system:
+
 
+
Empty input:
+
 
+
[[Image:ValidationCannotBeEmpty.png]]
+
 
+
Input too short (output unformatted)
+
 
+
[[Image:ValidationInputTooShort.png]]
+
 
+
Illegal characters in input (output unformatted)
+
 
+
[[Image:ValidationIllegalCharacters.png]]
+
 
+
Ignored characters in input (output unformatted due to other error)
+
 
+
[[Image:ValidationIgnoredCharacters.png]]
+
 
+
Number detected as such, but invalid (output formatted)
+
 
+
[[Image:ValidationInvalidNumber.png]]
+
 
+
Number detected and found to be valid (output formatted)
+
 
+
[[Image:ValidationValidAndFormatted.png]]
+
 
+
<br>
+
 
+
= Validating on any keypress  =
+
 
+
It is possible to force validation of input on any keypress. Currently this is only possible on <code>StringFields</code> (though a Bug has been opened to add support for this to numerical fields as well).  
+
 
+
This is done by setting the ValidateOnAnyKey property:
+
<pre>@Override
+
protected boolean getConfiguredValidateOnAnyKey() {
+
  return true;
+
}
+
</pre>
+
Currently the only benefit of doing this is the possibility to throw a veto exception on first input of illegal characters. Returning a modified value from <code>execValidateValue()</code> does not take effect until the field looses focus.
+
 
+
<br>
+
 
+
= Form Validation  =
+
 
+
In order to validate a form when the "OK" or "Save" button is pressed, overwrite its '''execValidate()''' method. This method will only be called if all the contained fields' <code>execValidateValue()</code> methods have succesfully passed. It allows checking dependencies between fields. If the form passes validation, return true. If the form fails validation, throw a <code>VetoException</code> with an appropriate error message (which will be shown in a separate error dialog). If you want to mark certain fields in addition, do so by calling their <code>setErrorStatus()</code> method (though in that case, that fields <code>execValidateValue()</code> method must call <code>clearErrorStatus()</code> to make sure this error is cleared when its value is changed.
+
 
+
[[Image:ValidationFormExecValidate.png]]
+
<pre>@Override
+
protected boolean execValidate() throws ProcessingException {
+
  if (!StringUtility.isNullOrEmpty(getPasswordField().getValue())) {
+
    if (StringUtility.isNullOrEmpty(getUsernameField().getValue())) {
+
      // if a password is provided, a username is mandatory
+
      getUsernameField().setErrorStatus(TEXTS.get("UsernameNeededWithPassword"));
+
      throw new VetoException(TEXTS.get("UsernameNeededWithPassword"));
+
    }
+
  }
+
  return true;
+
}
+
</pre>
+
[[Image:ValidationFormValidationResult.png]]<br>
+
 
+
 
+
 
+
= Showing validation results in the window header  =
+
 
+
A form needs to have the '''SubTitle '''property set for this to work:
+
<pre>@Override
+
protected String getConfiguredSubTitle() {
+
  return TEXTS.get("PersonDetails");
+
}
+
</pre>
+
In addition, the '''SwtScoutDialog '''must be extended as follows:
+
<pre>import java.beans.PropertyChangeEvent;
+
import java.beans.PropertyChangeListener;
+
 
+
import org.eclipse.scout.commons.exception.IProcessingStatus;
+
import org.eclipse.scout.rt.client.ui.form.IForm;
+
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
+
import org.eclipse.scout.rt.ui.swt.ISwtEnvironment;
+
import org.eclipse.scout.rt.ui.swt.window.dialog.SwtScoutDialog;
+
import org.eclipse.swt.widgets.Shell;
+
import org.eclipse.ui.forms.IMessage;
+
 
+
public class ValidationSwtScoutDialog extends SwtScoutDialog {
+
  private P_FieldValidationPropertyChangeListener m_validationListener;
+
 
+
  public ValidationSwtScoutDialog(Shell parentShell, ISwtEnvironment environment, int style) {
+
    super(parentShell, environment, style);
+
  }
+
 
+
  @Override
+
  protected void attachScout(IForm form) {
+
    super.attachScout(form);
+
 
+
    if (m_validationListener == null) {
+
      m_validationListener = new P_FieldValidationPropertyChangeListener();
+
    }
+
    for (IFormField f&nbsp;: form.getAllFields()) {
+
      f.addPropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
+
    }
+
  }
+
 
+
  @Override
+
  protected void detachScout(IForm form) {
+
    super.detachScout(form);
+
 
+
    for (IFormField f&nbsp;: form.getAllFields()) {
+
      f.removePropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
+
    }
+
  }
+
 
+
  private void handleFieldErrorStatusChanged(IFormField formField, IProcessingStatus errorStatus) {
+
    if (errorStatus == null) {
+
      getSwtForm().getMessageManager().removeMessage(formField);
+
    }
+
    else {
+
      getSwtForm().getMessageManager().addMessage(formField, errorStatus.getMessage(), null, IMessage.ERROR);
+
    }
+
  }
+
 
+
  private class P_FieldValidationPropertyChangeListener implements PropertyChangeListener {
+
    @Override
+
    public void propertyChange(final PropertyChangeEvent e) {
+
      Runnable t = new Runnable() {
+
 
+
        @Override
+
        public void run() {
+
          if (IFormField.PROP_ERROR_STATUS.equals(e.getPropertyName())) {
+
            handleFieldErrorStatusChanged((IFormField) e.getSource(), (IProcessingStatus) e.getNewValue());
+
          }
+
 
+
        }
+
      };
+
      getEnvironment().invokeSwtLater(t);
+
    }
+
  }// end private class
+
}
+
</pre>
+
This new class then needs to be returned by overwriting '''SwtEnvironment.createSwtScoutDialog''':
+
<pre>@Override
+
protected SwtScoutDialog createSwtScoutDialog(Shell shell, int dialogStyle) {
+
  return new ValidationSwtScoutDialog(shell, this, dialogStyle);
+
}
+
</pre>
+
Next, '''AbstractScoutView''' must be extended as follows:
+
<pre>import java.beans.PropertyChangeEvent;
+
import java.beans.PropertyChangeListener;
+
 
+
import org.eclipse.minicrm.ui.swt.Activator;
+
import org.eclipse.scout.commons.exception.IProcessingStatus;
+
import org.eclipse.scout.rt.client.ui.form.IForm;
+
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
+
import org.eclipse.scout.rt.ui.swt.window.desktop.view.AbstractScoutView;
+
import org.eclipse.ui.forms.IMessage;
+
 
+
/**
+
*
+
*/
+
public abstract class ValidationAbstractScoutView extends AbstractScoutView {
+
  private P_FieldValidationPropertyChangeListener m_validationListener;
+
 
+
  public ValidationAbstractScoutView() {
+
    super();
+
  }
+
 
+
  @Override
+
  protected void attachScout(IForm form) {
+
    super.attachScout(form);
+
 
+
    if (m_validationListener == null) {
+
      m_validationListener = new P_FieldValidationPropertyChangeListener();
+
    }
+
    for (IFormField f&nbsp;: form.getAllFields()) {
+
      f.addPropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
+
    }
+
  }
+
 
+
  @Override
+
  protected void detachScout(IForm form) {
+
    super.detachScout(form);
+
 
+
    for (IFormField f&nbsp;: form.getAllFields()) {
+
      f.removePropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
+
    }
+
  }
+
 
+
  private void handleFieldErrorStatusChanged(IFormField formField, IProcessingStatus errorStatus) {
+
    if (errorStatus == null) {
+
      getSwtForm().getMessageManager().removeMessage(formField);
+
    }
+
    else {
+
      getSwtForm().getMessageManager().addMessage(formField, errorStatus.getMessage(), null, IMessage.ERROR);
+
    }
+
  }
+
 
+
  private class P_FieldValidationPropertyChangeListener implements PropertyChangeListener {
+
    @Override
+
    public void propertyChange(final PropertyChangeEvent e) {
+
      Runnable t = new Runnable() {
+
 
+
        @Override
+
        public void run() {
+
          if (IFormField.PROP_ERROR_STATUS.equals(e.getPropertyName())) {
+
            handleFieldErrorStatusChanged((IFormField) e.getSource(), (IProcessingStatus) e.getNewValue());
+
          }
+
 
+
        }
+
      };
+
      Activator.getDefault().getEnvironment().invokeSwtLater(t);
+
    }
+
  }// end private class
+
}
+
</pre>
+
Next, '''AbstractScoutEditorPart''' must be extended as follows:
+
<pre>import java.beans.PropertyChangeEvent;
+
import java.beans.PropertyChangeListener;
+
 
+
import org.eclipse.scout.commons.exception.IProcessingStatus;
+
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
+
import org.eclipse.scout.rt.ui.swt.window.desktop.editor.AbstractScoutEditorPart;
+
import org.eclipse.ui.forms.IMessage;
+
 
+
public abstract class ValidationAbstractScoutEditorPart extends AbstractScoutEditorPart {
+
  private P_FieldValidationPropertyChangeListener m_validationListener;
+
 
+
  public ValidationAbstractScoutEditorPart() {
+
    super();
+
  }
+
 
+
  @Override
+
  protected void attachScout() {
+
    super.attachScout();
+
 
+
    if (m_validationListener == null) {
+
      m_validationListener = new P_FieldValidationPropertyChangeListener();
+
    }
+
    for (IFormField f&nbsp;: getForm().getAllFields()) {
+
      f.addPropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
+
    }
+
  }
+
 
+
  @Override
+
  protected void detachScout() {
+
    super.detachScout();
+
 
+
    for (IFormField f&nbsp;: getForm().getAllFields()) {
+
      f.removePropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
+
    }
+
  }
+
 
+
  private void handleFieldErrorStatusChanged(IFormField formField, IProcessingStatus errorStatus) {
+
    if (errorStatus == null) {
+
      getSwtForm().getMessageManager().removeMessage(formField);
+
    }
+
    else {
+
      getSwtForm().getMessageManager().addMessage(formField, errorStatus.getMessage(), null, IMessage.ERROR);
+
    }
+
  }
+
 
+
  private class P_FieldValidationPropertyChangeListener implements PropertyChangeListener {
+
    @Override
+
    public void propertyChange(final PropertyChangeEvent e) {
+
      Runnable t = new Runnable() {
+
 
+
        @Override
+
        public void run() {
+
          if (IFormField.PROP_ERROR_STATUS.equals(e.getPropertyName())) {
+
            handleFieldErrorStatusChanged((IFormField) e.getSource(), (IProcessingStatus) e.getNewValue());
+
          }
+
 
+
        }
+
      };
+
      getSwtEnvironment().invokeSwtLater(t);
+
    }
+
  }// end private class
+
}
+
</pre>
+
Now, the extended classes must be used instead of the original classes. In package '''org.eclipse.minicrm.ui.swt.views''' replace each class definition for '''XxxView''' to use <code>ValidationAbstractScoutView</code>:
+
<pre>public class CenterView extends ValidationAbstractScoutView {</pre>
+
Finally, we need to use '''ValidationAbstractScoutEditorPart '''by changing the class definition for <code>ScoutEditorPart</code>:
+
<pre>public class ScoutEditorPart extends ValidationAbstractScoutEditorPart {</pre>
+
With these changes, validation messages are not only shown in the tooltip of a field in error status but also in the header of the form
+
 
+
[[Image:ValidationResultDisplaySubtitle.png]]
+

Latest revision as of 07:37, 18 March 2024

The Scout documentation has been moved to https://eclipsescout.github.io/.

Back to the top