开发者

WPF TextBox Validation

开发者 https://www.devze.com 2022-12-16 20:24 出处:网络
I have validation hooked up to a model that is bound to the TextBox container.When the window is first opened validati开发者_运维百科on errors appear as the model is empty, I do not want to see valida

I have validation hooked up to a model that is bound to the TextBox container. When the window is first opened validati开发者_运维百科on errors appear as the model is empty, I do not want to see validation errors until submit of the window or the text in the TextBox has changed or on lost focus.

Here is the TextBox:

<TextBox Text="{Binding 
                   Path=Firstname, 
                   UpdateSourceTrigger=PropertyChanged, 
                   ValidatesOnDataErrors=True}"
         Width="124"
         Height="24"/>

How can this be achieved?


This really depends on your implementation of IDataErrorInfo. If you base it around a Dictionary of error messages you can control when validation runs that adds to that list. You would normally want to do that from your property setters (like whenever you call PropertyChange), here calling CheckValidationState:

    public string this[string columnName]
    {
        get
        {
            return ValidateProperty(columnName);
        }
    }

    public Dictionary<string, string> Errors { get; private set; }

    protected void SetError(string propertyName, string errorMessage)
    {
        Debug.Assert(!String.IsNullOrEmpty(propertyName), "propertyName is null or empty.");
        if (String.IsNullOrEmpty(propertyName))
            return;

        if (!String.IsNullOrEmpty(errorMessage))
        {
            if (Errors.ContainsKey(propertyName))
                Errors[propertyName] = errorMessage;
            else
                Errors.Add(propertyName, errorMessage);
        }
        else if (Errors.ContainsKey(propertyName))
            Errors.Remove(propertyName);

        NotifyPropertyChanged("Errors");
        NotifyPropertyChanged("Error");
        NotifyPropertyChanged("Item[]");
    }

    protected virtual string ValidateProperty(string propertyName)
    {
        return Errors.ContainsKey(propertyName) ? Errors[propertyName] : null;
    }

    protected virtual bool CheckValidationState<T>(string propertyName, T proposedValue)
    {
        // your validation logic here
    }

You can then also include a method that validates all of your properties (like during a save):

    protected bool Validate()
    {
        if (Errors.Count > 0)
            return false;

        bool result = true;
        foreach (PropertyInfo propertyInfo in GetType().GetProperties())
        {
            if (!CheckValidationState(propertyInfo.Name, propertyInfo.GetValue(this, null)))
                result = false;
            NotifyPropertyChanged(propertyInfo.Name);
        }
        return result;
    }

UPDATE:

I would recommend putting the above code into a base ViewModel class so you can reuse it. You could then create a derived class like this:

public class SampleViewModel : ViewModelBase
{
    private string _firstName;

    public SampleViewModel()
    {
        Save = new DelegateCommand<object>(SaveExecuted);
    }

    public DelegateCommand<object> Save { get; private set; }

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            if (_firstName == value)
                return;

            CheckValidationState("FirstName", value);

            _firstName = value;
            NotifyPropertyChanged("FirstName");
        }
    }

    public void SaveExecuted(object obj)
    {
        bool isValid = Validate();
        MessageBox.Show(isValid ? "Saved" : "Validation Error. Save canceled"); // TODO: do something appropriate to your app here
    }

    protected override bool CheckValidationState<T>(string propertyName, T proposedValue)
    {
        // your validation logic here
        if (propertyName == "FirstName")
        {
            if (String.IsNullOrEmpty(proposedValue as String))
            {
                SetError(propertyName, "First Name is required.");
                return false;
            }
            else if (proposedValue.Equals("John"))
            {
                SetError(propertyName, "\"John\" is not an allowed name.");
                return false;
            }
            else
            {
                SetError(propertyName, String.Empty); // clear the error
                return true;
            }
        }
        return true;
    }
}

In this case I'm using a DelegateCommand to trigger the save operation but it could be anything that makes a method call to do the saving. This setup allows for the initial empty state to show up as valid in the UI but either a change or a call to Save updates the validation state. You can also get a lot more general and more complicated in the way you actually do the validation so it doesn't all end up in one method (here with some assumptions about the type) but this is simplified to make it easier to start with.


If you are implementing IDataErrorInfo, I've achieved this by checking for null values in the implementation of the validation logic. When creating a new window, checking for null will prevent your validation logic from firing. For example:

public partial class Product : IDataErrorInfo
{
    #region IDataErrorInfo Members

    public string Error
    {
        get { return null; }
    }

    public string this[string columnName]
    {
        get
        {
            if (columnName == "ProductName")
            {
                // Only apply validation if there is actually a value
                if (this.ProductName != null)
                {
                    if (this.ProductName.Length <= 0 || this.ProductName.Length > 25)
                        return "Product Name must be between 1 and 25 characters";
                }
            }

            return null;
        }
    }

    #endregion
}

Also, if you want to fire the validation on TextBox.LostFocus, change your binding to LostFocus, like follows:

<TextBox Text="{Binding              
               Path=Firstname,              
               UpdateSourceTrigger=LostFocus,              
               ValidatesOnDataErrors=True}"             
     Width="124"             
     Height="24"/>


in your app.xaml file, you need to use custom textbox style for textbox validation without any third party component.

WPF TextBox Validation

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type TextBox}">
            <Grid Name="test">
                <Border Background="{StaticResource TextBackColor}"
                        BorderBrush="#FF888888"
                        x:Name="Bd"
                        CornerRadius="1"
                        BorderThickness="1">
                    <ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
                </Border>
                <Image  Name="ErrorImage"
                        Width="15"
                        Height="15"
                        Margin="0,0,4,0"
                        Source="Images/validate.png"
                        HorizontalAlignment="Right">
                </Image>
            </Grid>
        </ControlTemplate>
    </Setter.Value>
</Setter>


What I do, I donno if this is the correct way (I would be glad to learn, now is a chance), but in the initializer of the entity or the model I run all the validators.

0

精彩评论

暂无评论...
验证码 换一张
取 消