org.controlsfx.validation.ValidationSupport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of controlsfx Show documentation
Show all versions of controlsfx Show documentation
High quality UI controls and other tools to complement the core JavaFX distribution
/**
* Copyright (c) 2014, 2015, ControlsFX
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.controlsfx.validation;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.scene.control.Control;
import javafx.util.Callback;
import org.controlsfx.tools.ValueExtractor;
import org.controlsfx.validation.decoration.GraphicValidationDecoration;
import org.controlsfx.validation.decoration.ValidationDecoration;
/**
* Provides validation support for UI components. The idea is create an instance of this class the component group, usually a panel.
* Once created, {@link Validator}s can be registered for components, to provide the validation:
*
*
* ValidationSupport validationSupport = new ValidationSupport();
* validationSupport.registerValidator(textField, Validator.createEmptyValidator("Text is required"));
* validationSupport.registerValidator(combobox, Validator.createEmptyValidator( "ComboBox Selection required"));
* validationSupport.registerValidator(checkBox, (Control c, Boolean newValue) ->
* ValidationResult.fromErrorIf( c, "Checkbox should be checked", !newValue)
* );
*
*
* validationResultProperty provides an ability to react on overall validation result changes:
*
* validationSupport.validationResultProperty().addListener( (o, oldValue, newValue) ->
messageList.getItems().setAll(newValue.getMessages()));
*
*
* Standard JavaFX UI controls are supported out of the box. There is also an ability to add support for custom controls.
* To do that "observable value extractor" should be added for specific controls. Such "extractor" consists of two functional interfaces:
* a {@link Predicate} to check the applicability of the control and a {@link Callback} to extract control's observable value.
* Here is an sample of internal registration of such "extractor" for a few controls :
*
* ValueExtractor.addObservableValueExtractor( c -> c instanceof TextInputControl, c -> ((TextInputControl)c).textProperty());
* ValueExtractor.addObservableValueExtractor( c -> c instanceof ComboBox, c -> ((ComboBox<?>)c).getValue());
*
*
*/
public class ValidationSupport {
private static final String CTRL_REQUIRED_FLAG = "$org.controlsfx.validation.required$"; //$NON-NLS-1$
/**
* Set control's required flag
* @param c control
* @param required flag
*/
public static void setRequired( Control c, boolean required ) {
c.getProperties().put(CTRL_REQUIRED_FLAG, required);
}
/**
* Check control's required flag
* @param c control
* @return true if required
*/
public static boolean isRequired( Control c ) {
Object value = c.getProperties().get(CTRL_REQUIRED_FLAG);
return value instanceof Boolean? (Boolean)value: false;
}
private ObservableSet controls = FXCollections.observableSet();
private ObservableMap validationResults =
FXCollections.observableMap(new WeakHashMap<>());
private AtomicBoolean dataChanged = new AtomicBoolean(false);
/**
* Creates validation support instance.
* If initial decoration is desired invoke {@link #initInitialDecoration()}.
*/
public ValidationSupport() {
validationResultProperty().addListener( (o, oldValue, validationResult) -> {
invalidProperty.set(!validationResult.getErrors().isEmpty());
redecorate();
});
// notify validation result observers
validationResults.addListener( (MapChangeListener.Change extends Control, ? extends ValidationResult> change) ->
validationResultProperty.set(ValidationResult.fromResults(validationResults.values()))
);
}
/**
* Activates the initial decoration of validated controls.
* By default the decoration will only be applied after the first change of one validated controls value.
*/
public void initInitialDecoration() {
dataChanged.set(true);
redecorate();
}
/**
* Redecorates all known components
* Only decorations related to validation are affected
*/
// TODO needs optimization
public void redecorate() {
Optional odecorator = Optional.ofNullable(getValidationDecorator());
for (Control target : getRegisteredControls()) {
odecorator.ifPresent( decorator -> {
decorator.removeDecorations(target);
decorator.applyRequiredDecoration(target);
if ( dataChanged.get() && isErrorDecorationEnabled()) {
getHighestMessage(target).ifPresent(msg -> decorator.applyValidationDecoration(msg));
}
});
}
}
private BooleanProperty errorDecorationEnabledProperty = new SimpleBooleanProperty(true) {
protected void invalidated() {
redecorate();
};
};
public BooleanProperty errorDecorationEnabledProperty() {
return errorDecorationEnabledProperty;
}
public void setErrorDecorationEnabled(boolean enabled) {
errorDecorationEnabledProperty.set(enabled);
}
private boolean isErrorDecorationEnabled() {
return errorDecorationEnabledProperty.get();
}
private ReadOnlyObjectWrapper validationResultProperty =
new ReadOnlyObjectWrapper<>();
/**
* Retrieves current validation result
* @return validation result
*/
public ValidationResult getValidationResult() {
return validationResultProperty.get();
}
/**
* Can be used to track validation result changes
* @return The Validation result property.
*/
public ReadOnlyObjectProperty validationResultProperty() {
return validationResultProperty.getReadOnlyProperty();
}
private BooleanProperty invalidProperty = new SimpleBooleanProperty();
/**
* Returns current validation state.
* @return true if there is at least one error
*/
public Boolean isInvalid() {
return invalidProperty.get();
}
/**
* Validation state property
* @return validation state property
*/
public ReadOnlyBooleanProperty invalidProperty() {
return invalidProperty;
}
private ObjectProperty validationDecoratorProperty =
new SimpleObjectProperty(this, "validationDecorator", new GraphicValidationDecoration()) { //$NON-NLS-1$
@Override protected void invalidated() {
// when the decorator changes, rerun the decoration to update the visuals immediately.
redecorate();
}
};
/**
* @return The Validation decorator property
*/
public ObjectProperty validationDecoratorProperty() {
return validationDecoratorProperty;
}
/**
* Returns current validation decorator
* @return current validation decorator or null if none
*/
public ValidationDecoration getValidationDecorator() {
return validationDecoratorProperty.get();
}
/**
* Sets new validation decorator
* @param decorator new validation decorator. Null value is valid - no decoration will occur
*/
public void setValidationDecorator( ValidationDecoration decorator ) {
validationDecoratorProperty.set(decorator);
}
/**
* Registers {@link Validator} for specified control with additional possiblity to mark control as required or not.
* @param c control to validate
* @param required true if controls should be required
* @param validator {@link Validator} to be used
* @return true if registration is successful
*/
@SuppressWarnings("unchecked")
public boolean registerValidator( final Control c, boolean required, final Validator validator ) {
Optional.ofNullable(c).ifPresent( ctrl -> {
ctrl.getProperties().addListener( new MapChangeListener