C# 实现WPF文本框的验证

C# 实现WPF文本框的验证,c#,wpf,validation,xaml,data-binding,C#,Wpf,Validation,Xaml,Data Binding,我有3个文本框(Id1、Name和Salary)Id和Salary应包含整数,Name应仅包含字符。我需要验证我的文本框,它应该显示错误,因为我输入了错误的字符或整数。也可以只在Xaml中完成,而不使用codebehind吗?请帮我输入所需的代码 这是Xaml代码: <TextBox Name="tb1" HorizontalAlignment="Left" Height="20" Margin="60,10,0,0" TextWrapping="NoWrap" Text="{Bindin

我有3个文本框(
Id1
Name
Salary
Id
Salary
应包含整数,
Name
应仅包含字符。我需要验证我的文本框,它应该显示错误,因为我输入了错误的字符或整数。也可以只在Xaml中完成,而不使用codebehind吗?请帮我输入所需的代码

这是Xaml代码:

<TextBox Name="tb1" HorizontalAlignment="Left" Height="20" Margin="60,10,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Id,ElementName=dgsample}" VerticalAlignment="Top" Width="100" />
<TextBox Name="tb2" HorizontalAlignment="Left" Height="20" Margin="60,60,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Name, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>
<TextBox Name="tb3" HorizontalAlignment="Left" Height="20" Margin="60,110,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Salary, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>

要仅使用XAML完成,您需要为各个属性添加验证规则。但我建议您使用代码隐藏方法。 在代码中,在属性设置器中定义规范,并在不符合规范时抛出异常。 并使用错误模板在UI中向用户显示错误。 您的XAML将如下所示

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <Style x:Key="CustomTextBoxTextStyle" TargetType="TextBox">
        <Setter Property="Foreground" Value="Green" />
        <Setter Property="MaxLength" Value="40" />
        <Setter Property="Width" Value="392" />
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Trigger.Setters>
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
                    <Setter Property="Background" Value="Red"/>
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
    <TextBox Name="tb2" Height="30" Width="400"
             Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" 
             Style="{StaticResource CustomTextBoxTextStyle}"/>
</Grid>

您还可以在视图模型中实现
IDataErrorInfo
,如下所示。如果实现了
IDataErrorInfo
,则可以在其中执行验证,而不是特定属性的setter,然后每当出现错误时,返回错误消息,以便出现错误的文本框周围会出现一个红色框,指示错误

class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    private string m_Name = "Type Here";
    public ViewModel()
    {
    }

    public string Name
    {
        get
        {
            return m_Name;
        }
        set
        {
            if (m_Name != value)
            {
                m_Name = value;
                OnPropertyChanged("Name");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public string Error
    {
        get { return "...."; }
    }

    /// <summary>
    /// Will be called for each and every property when ever its value is changed
    /// </summary>
    /// <param name="columnName">Name of the property whose value is changed</param>
    /// <returns></returns>
    public string this[string columnName]
    {
        get 
        {
            return Validate(columnName);
        }
    }

    private string Validate(string propertyName)
    {
        // Return error message if there is error on else return empty or null string
        string validationMessage = string.Empty;
        switch (propertyName)
        {
            case "Name": // property name
                // TODO: Check validiation condition
                validationMessage = "Error";
                break;
        }

        return validationMessage;
    }
}

有三种方法可以实现验证:

  • 验证规则
  • INotifyDataErrorInfo的实现
  • IDataErrorInfo的实现
  • 验证规则示例

    public class NumericValidationRule : ValidationRule
    {
        public Type ValidationType { get; set; }
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            string strValue = Convert.ToString(value);
    
            if (string.IsNullOrEmpty(strValue))
                return new ValidationResult(false, $"Value cannot be coverted to string.");
            bool canConvert = false;
            switch (ValidationType.Name)
            {
    
                case "Boolean":
                    bool boolVal = false;
                    canConvert = bool.TryParse(strValue, out boolVal);
                    return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of boolean");
                case "Int32":
                    int intVal = 0;
                    canConvert = int.TryParse(strValue, out intVal);
                    return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int32");
                case "Double":
                    double doubleVal = 0;
                    canConvert = double.TryParse(strValue, out doubleVal);
                    return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Double");
                case "Int64":
                    long longVal = 0;
                    canConvert = long.TryParse(strValue, out longVal);
                    return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int64");
                default:
                    throw new InvalidCastException($"{ValidationType.Name} is not supported");
            }
        }
    }
    
    public abstract class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
    {
    
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
            ValidateAsync();
        }
        #endregion
    
    
        public virtual void OnLoaded()
        {
        }
    
        #region INotifyDataErrorInfo
        private ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();
    
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    
        public void OnErrorsChanged(string propertyName)
        {
            var handler = ErrorsChanged;
            if (handler != null)
                handler(this, new DataErrorsChangedEventArgs(propertyName));
        }
    
        public IEnumerable GetErrors(string propertyName)
        {
            List<string> errorsForName;
            _errors.TryGetValue(propertyName, out errorsForName);
            return errorsForName;
        }
    
        public bool HasErrors
        {
            get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
        }
    
        public Task ValidateAsync()
        {
            return Task.Run(() => Validate());
        }
    
        private object _lock = new object();
        public void Validate()
        {
            lock (_lock)
            {
                var validationContext = new ValidationContext(this, null, null);
                var validationResults = new List<ValidationResult>();
                Validator.TryValidateObject(this, validationContext, validationResults, true);
    
                foreach (var kv in _errors.ToList())
                {
                    if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
                    {
                        List<string> outLi;
                        _errors.TryRemove(kv.Key, out outLi);
                        OnErrorsChanged(kv.Key);
                    }
                }
    
                var q = from r in validationResults
                        from m in r.MemberNames
                        group r by m into g
                        select g;
    
                foreach (var prop in q)
                {
                    var messages = prop.Select(r => r.ErrorMessage).ToList();
    
                    if (_errors.ContainsKey(prop.Key))
                    {
                        List<string> outLi;
                        _errors.TryRemove(prop.Key, out outLi);
                    }
                    _errors.TryAdd(prop.Key, messages);
                    OnErrorsChanged(prop.Key);
                }
            }
        }
        #endregion
    
    }
    
    XAML:

    非常重要:不要忘记设置
    validateOnTargetUpdated=“True”
    如果没有此定义,它将无法工作

     <TextBox x:Name="Int32Holder"
             IsReadOnly="{Binding IsChecked,ElementName=CheckBoxEditModeController,Converter={converters:BooleanInvertConverter}}"
             Style="{StaticResource ValidationAwareTextBoxStyle}"
             VerticalAlignment="Center">
        <!--Text="{Binding Converter={cnv:TypeConverter}, ConverterParameter='Int32', Path=ValueToEdit.Value, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"-->
        <TextBox.Text>
            <Binding Path="Name"
                     Mode="TwoWay"
                     UpdateSourceTrigger="PropertyChanged"
                     Converter="{cnv:TypeConverter}"
                     ConverterParameter="Int32"
                     ValidatesOnNotifyDataErrors="True"
                     ValidatesOnDataErrors="True"
                     NotifyOnValidationError="True">
                <Binding.ValidationRules>
                    <validationRules:NumericValidationRule ValidationType="{x:Type system:Int32}"
                                                           ValidatesOnTargetUpdated="True" />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
        <!--NumericValidationRule-->
    </TextBox>
    
    XAML:

    
    代码:
    说明:
    类别:
    友好名称:
    超时:
    是手册:
    
    我已经实现了此验证。但是你会被代码隐藏使用。这太简单了

    XAML: 对于名称有效性,仅输入A-Z和A-Z中的字符

    <TextBox x:Name="first_name_texbox" PreviewTextInput="first_name_texbox_PreviewTextInput" >  </TextBox>
    
    对于薪资和ID验证,将regex构造函数传递的值替换为
    [0-9]+
    。这意味着您只能输入从1到无限的数字

    您还可以使用
    [0-9]{1,4}
    定义长度。这意味着您只能输入小于或等于4位数的数字。这个巴拉克的意思是{至少,多少}。通过这样做,您可以在文本框中定义数字的范围

    愿它对其他人有所帮助

    XAML:

    代码隐藏。

    private void first_name_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
    {
        Regex regex = new Regex ( "[^a-zA-Z]+" );
        if ( regex.IsMatch ( first_name_texbox.Text ) )
        {
            MessageBox.Show("Invalid Input !");
        }
    }
    
    private void salary_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
    {
        Regex regex = new Regex ( "[^0-9]+" );
        if ( regex.IsMatch ( salary_texbox.Text ) )
        {
            MessageBox.Show("Invalid Input !");
        }
    }
    

    当谈到穆罕默德·迈赫迪的答案时,最好是:

    private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
         Regex regex = new Regex ( "[^0-9]+" );
         if(regex.IsMatch(e.Text))
         {
             MessageBox.Show("Error");
         }
    }
    

    因为与TextCompositionEventArgs比较时,它也会得到最后一个字符,而与textbox比较时,它不会得到最后一个字符。对于文本框,错误将在下一个插入字符后显示。

    当涉及灾难的答案时,我注意到了一种不同的行为。Text显示用户输入新字符之前文本框中的文本,而e.Text显示用户刚刚输入的单个字符。一个挑战是该字符可能不会附加到末尾,但会插入到carret位置:

    private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e){
      Regex regex = new Regex ( "[^0-9]+" );
    
      string text;
      if (textBox.CaretIndex==textBox.Text.Length) {
        text = textBox.Text + e.Text;
      } else {
        text = textBox.Text.Substring(0, textBox.CaretIndex)  + e.Text + textBox.Text.Substring(textBox.CaretIndex);
      }
    
      if(regex.IsMatch(text)){
          MessageBox.Show("Error");
      }
    }
    

    当我需要这样做时,我使用Binding.ValidationRules跟随微软的例子,它第一次起作用

    请参阅他们的文章,如何:实现绑定验证:

    我在上找到了与您的需求相关的SO,并指向codeproject Article您是否使用MVVM格式?我的猜测是否定的。@philip Gullick我没有使用MVVM格式接受答案,TS?-1使用异常进行验证可能太慢了。不是一个好的练习慢下来的方法?我们在这里处理的是用户界面。我不认为实例化异常的额外工作会对这里的性能造成影响。我知道这已经有一年了,但我同意w Mattias。异常是捕获某些类型的输入无效的唯一方法之一,并在MS文档中显式使用。另外,这是UI,单输入验证,而不是整个对象。我知道这比以前老了一年——但我同意drasto。使用异常进行验证是一个坏习惯,应该尽可能避免。我知道这比验证早了一年,但我同意Mattias的观点。有没有人可以引用微软官方消息来源的一句话,告诉我更喜欢哪种风格?这对我来说很有用,因为它在文本框周围添加了一个红色边框。很酷。唯一的问题是,每次属性发生更改时,它都会对其进行验证。这是惊人的缓慢。我打字时有点耽搁。但是+1代表我所做的need@Nlinscott,我知道已经快两年了,但是如果您更改了
    UpdateSourceTrigger
    ,使其仅在焦点丢失时更新,这会更快。还有例外验证。只需设置
    ValidatesOnExceptions=“True”
    。然后从Setter抛出异常。这是如何工作的?validationRules在代码中只出现一次。Paul McCarthy,您只需选择1个选项。取决于您的框架版本,但最好是INotifyDataErrorInfo-来自最新的.NET版本。@先生B:在您的NumericValidationRule示例中,您在XAML中引用“这里有些地方添加了您的名称空间:类似于此,我注意到e.Text不包含您所说的完整文本,只包含用户刚刚输入的字符。看看我的答案,我必须做什么,它真的有效。现在,我想知道我必须做些什么才能使您的解决方案对我有效,这更简单。欢迎链接到解决方案,但请确保您的答案在没有它的情况下是有用的:这样您的其他用户就会知道它是什么以及它为什么存在,然后引用你链接到的页面最相关的部分,以防目标页面不可用。
    <controls:NewDefinitionControl x:Class="DiagnosticsDashboard.EntityData.Operations.Views.NewOperationView"
                                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                                   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                                   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                                   xmlns:views="clr-namespace:DiagnosticsDashboard.EntityData.Operations.Views"
                                   xmlns:controls="clr-namespace:DiagnosticsDashboard.Core.Controls;assembly=DiagnosticsDashboard.Core"
                                   xmlns:c="clr-namespace:DiagnosticsDashboard.Core.Validation;assembly=DiagnosticsDashboard.Core"
                                   mc:Ignorable="d">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="40" />
                <RowDefinition Height="40" />
                <RowDefinition Height="40" />
                <RowDefinition Height="40" />
                <RowDefinition Height="40" />
                <RowDefinition Height="40" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Label Grid.Column="0"
                   Grid.Row="0"
                   Margin="5">Code:</Label>
            <Label Grid.Column="0"
                   Grid.Row="1"
                   Margin="5">Description:</Label>
            <Label Grid.Column="0"
                   Grid.Row="2"
                   Margin="5">Category:</Label>
            <Label Grid.Column="0"
                   Grid.Row="3"
                   Margin="5">Friendly Name:</Label>
            <Label Grid.Column="0"
                   Grid.Row="4"
                   Margin="5">Timeout:</Label>
            <Label Grid.Column="0"
                   Grid.Row="5"
                   Margin="5">Is Manual:</Label>
            <TextBox Grid.Column="1"
                     Text="{Binding Code,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
                     Grid.Row="0"
                     Margin="5"/>
            <TextBox Grid.Column="1"
                     Grid.Row="1"
                     Margin="5"
                     Text="{Binding Description}" />
            <TextBox Grid.Column="1"
                     Grid.Row="2"
                     Margin="5"
                     Text="{Binding Category}" />
            <TextBox Grid.Column="1"
                     Grid.Row="3"
                     Margin="5"
                     Text="{Binding FriendlyName}" />
            <TextBox Grid.Column="1"
                     Grid.Row="4"
                     Margin="5"
                     Text="{Binding Timeout}" />
            <CheckBox Grid.Column="1"
                      Grid.Row="5"
                      Margin="5"
                      IsChecked="{Binding IsManual}"
                      VerticalAlignment="Center" />
    
    
        </Grid>
    </controls:NewDefinitionControl>
    
    <TextBox x:Name="first_name_texbox" PreviewTextInput="first_name_texbox_PreviewTextInput" >  </TextBox>
    
    private void first_name_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
    {
        Regex regex = new Regex ( "[^a-zA-Z]+" );
        if ( regex.IsMatch ( first_name_texbox.Text ) )
        {
            MessageBox.Show("Invalid Input !");
        }
    }
    
    private void salary_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
    {
        Regex regex = new Regex ( "[^0-9]+" );
        if ( regex.IsMatch ( salary_texbox.Text ) )
        {
            MessageBox.Show("Invalid Input !");
        }
    }
    
    private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
         Regex regex = new Regex ( "[^0-9]+" );
         if(regex.IsMatch(e.Text))
         {
             MessageBox.Show("Error");
         }
    }
    
    private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e){
      Regex regex = new Regex ( "[^0-9]+" );
    
      string text;
      if (textBox.CaretIndex==textBox.Text.Length) {
        text = textBox.Text + e.Text;
      } else {
        text = textBox.Text.Substring(0, textBox.CaretIndex)  + e.Text + textBox.Text.Substring(textBox.CaretIndex);
      }
    
      if(regex.IsMatch(text)){
          MessageBox.Show("Error");
      }
    }