Validation when binding data in WPF

2019-08-29 20:55发布

问题:

I've builded an application in WPF (MVVM) and adding validation. Here's my result:

You'll notice the red squires around the input textboxes. The thing is I don't want to confront the client with validation error when starting the form. When entering data or pressing Submit would be best.

There are some similar questions on SO but haven't found a suitable solution. (Resetting every control on initialize is no solution)

So the question is how startup the form and:

Option A: (easily) Reset the validation

Option B: Don't call validation until after binding

Option C: Other nice solution

Download code:

here

View code:

<Window x:Class="Validation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="150" Width="250">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="80"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <!-- Name -->
        <Label Grid.Column="0" Grid.Row="0">Name</Label>
        <TextBox Grid.Column="1" Grid.Row="0"
                 Text="{Binding Model.Name, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"></TextBox>
        <!-- Age -->
        <Label Grid.Column="0" Grid.Row="1">Age</Label>
        <TextBox Grid.Column="1" Grid.Row="1"
                 Text="{Binding Model.Age, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"></TextBox>
        <!-- Submit/Cancel -->
        <StackPanel Grid.Column="1" Grid.Row="2" FlowDirection="LeftToRight">
            <Button>Cancel</Button>
            <Button>Submit</Button>
        </StackPanel>
    </Grid>
</Window>

ViewModel code:

public class FormViewModel
{
    #region constructors
    public FormViewModel()
    {
        Model = new FormModel();
    }
    #endregion

    #region properties
    public FormModel Model { get; set; }
    #endregion

}

Model code:

   public class FormModel : INotifyPropertyChanged, IDataErrorInfo
    {
        #region notifypropertychanged
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
        #endregion

        #region dataerrorinfo
        public string Error
        {
            get { return null; }
        }

        public string this[string columnName]
        {
            get
            {
                switch (columnName)
                {
                    case "Name":
                        if (string.IsNullOrEmpty(Name))
                        {
                            return "Name is required";
                        }
                        break;
                    case "Age":
                        if (Age < 18 || Age > 50)
                        {
                            return "Are you kidding?";
                        }
                        break;
                }
                return null;
            }
        }
        #endregion

        #region properties
        private string name;
        public string Name { get { return name; } set { name = value; OnPropertyChanged("Name"); } }

        private int age;
        public int Age { get { return age; } set { age = value; OnPropertyChanged("Age"); } }
        #endregion
    }

Solution (at the moment)

<Style x:Key="InputControlDefault" TargetType="{x:Type TextBox}">
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel LastChildFill="True">
                    <TextBlock DockPanel.Dock="Right" 
                    Foreground="black"
                    FontSize="12pt">
                    ?
                    </TextBlock>
                    <AdornedElementPlaceholder/>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="Black"/>
        </Trigger>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" Value="test"/>
            <Setter Property="BorderBrush" Value="Red" />
        </Trigger>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="Validation.HasError" Value="true"/>
                <Condition Property="Text" Value=""/>
            </MultiTrigger.Conditions>
            <Setter Property="BorderBrush" Value="Orange" />
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel LastChildFill="True">
                            <TextBlock DockPanel.Dock="Right" Foreground="Black" FontSize="12pt">
                                *
                            </TextBlock>
                            <AdornedElementPlaceholder/>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="ToolTip" Value="test"/>
        </MultiTrigger>
    </Style.Triggers>
</Style>

回答1:

i answer a similar question a few days ago:

first if your rule say that first and lastname should not be empty - its right that the user see the validation error.

what i have done is to use a ValidationTemplate for empty/initial values, so that the user just see a "*" for requiered field.

here is the complete answer