62

I have 3 TextBoxes (Id1,Name and Salary). Id and Salary should contain integers and Name should only contain characters. I need validations for my TextBox, it should show errors as I enter wrong characters or integers. Also can this be done only in Xaml without codebehind? Please help me with the required code

This is Xaml code:

<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"/>
ASh
  • 34,632
  • 9
  • 60
  • 82
user2889489
  • 667
  • 2
  • 9
  • 11
  • 2
    I found [this question](http://stackoverflow.com/questions/1268552/how-do-i-get-a-textbox-to-only-accept-numeric-input-in-wpf) on SO which seems to relate to what you require and points to [this](http://www.codeproject.com/Articles/15239/Validation-in-Windows-Presentation-Foundation) codeproject article – Squirrel5853 Oct 23 '13 at 10:50
  • Are you using an MVVM format? My guess is no. – Philip Gullick Oct 23 '13 at 10:53
  • @philip Gullick I'm not using MVVM Format – user2889489 Oct 23 '13 at 11:09

7 Answers7

115

There a 3 ways to implement validation:

  1. Validation Rule
  2. Implementation of INotifyDataErrorInfo
  3. Implementation of IDataErrorInfo

Validation rule example:

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");
        }
    }
}

XAML:

Very important: don't forget to set ValidatesOnTargetUpdated="True" it won't work without this definition.

 <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>

INotifyDataErrorInfo example:

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

}

View Model Implementation:

public class MainFeedViewModel : BaseViewModel//, IDataErrorInfo
{
    private ObservableCollection<FeedItemViewModel> _feedItems;
    [XmlIgnore]
    public ObservableCollection<FeedItemViewModel> FeedItems
    {
        get
        {
            return _feedItems;
        }
        set
        {
            _feedItems = value;
            OnPropertyChanged("FeedItems");
        }
    }
    [XmlIgnore]
    public ObservableCollection<FeedItemViewModel> FilteredFeedItems
    {
        get
        {
            if (SearchText == null) return _feedItems;

            return new ObservableCollection<FeedItemViewModel>(_feedItems.Where(x => x.Title.ToUpper().Contains(SearchText.ToUpper())));
        }
    }

    private string _title;
    [Required]
    [StringLength(20)]
    //[CustomNameValidationRegularExpression(5, 20)]
    [CustomNameValidationAttribute(3, 20)]
    public string Title
    {
        get { return _title; }
        set
        {
            _title = value;
            OnPropertyChanged("Title");
        }
    }
    private string _url;
    [Required]
    [StringLength(200)]
    [Url]
    //[CustomValidation(typeof(MainFeedViewModel), "UrlValidation")]
    /// <summary>
    /// Validation of URL should be with custom method like the one that implemented below, or with 
    /// </summary>
    public string Url
    {
        get { return _url; }
        set
        {
            _url = value;
            OnPropertyChanged("Url");
        }
    }

    public MainFeedViewModel(string url, string title)
    {
        Title = title;
        Url = url;
    }
    /// <summary>
    /// 
    /// </summary>
    public MainFeedViewModel()
    {

    }
    public MainFeedViewModel(ObservableCollection<FeedItemViewModel> feeds)
    {
        _feedItems = feeds;
    }


    private string _searchText;
    [XmlIgnore]
    public string SearchText
    {
        get { return _searchText; }
        set
        {
            _searchText = value;

            OnPropertyChanged("SearchText");
            OnPropertyChanged("FilteredFeedItems");
        }
    }

    #region Data validation local
    /// <summary>
    /// Custom URL validation method
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    public static ValidationResult UrlValidation(object obj, ValidationContext context)
    {
        var vm = (MainFeedViewModel)context.ObjectInstance;
        if (!Uri.IsWellFormedUriString(vm.Url, UriKind.Absolute))
        {
            return new ValidationResult("URL should be in valid format", new List<string> { "Url" });
        }
        return ValidationResult.Success;
    }

    #endregion
}

XAML:

<UserControl x:Class="RssReaderTool.Views.AddNewFeedDialogView"
             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"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="300">
    <FrameworkElement.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate x:Name="TextErrorTemplate">
                        <DockPanel LastChildFill="True">
                            <AdornedElementPlaceholder>
                                <Border BorderBrush="Red"
                                        BorderThickness="2" />
                            </AdornedElementPlaceholder>
                            <TextBlock FontSize="20"
                                       Foreground="Red">*?*</TextBlock>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError"
                         Value="True">
                    <Setter Property="ToolTip"
                            Value="{Binding RelativeSource=
            {x:Static RelativeSource.Self},
            Path=(Validation.Errors)[0].ErrorContent}"></Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
        <!--<Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError"
                         Value="true">
                    <Setter Property="ToolTip"
                            Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>-->
    </FrameworkElement.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="5" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="5" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="5" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock Text="Feed Name"
                   ToolTip="Display" />
        <TextBox Text="{Binding MainFeedViewModel.Title,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
                 Grid.Column="2" />

        <TextBlock Text="Feed Url"
                   Grid.Row="2" />
        <TextBox Text="{Binding MainFeedViewModel.Url,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
                 Grid.Column="2"
                 Grid.Row="2" />
    </Grid>
</UserControl>

IDataErrorInfo:

View Model:

public class OperationViewModel : ViewModelBase, IDataErrorInfo
{
    private const int ConstCodeMinValue = 1;
    private readonly IEventAggregator _eventAggregator;
    private OperationInfoDefinition _operation;
    private readonly IEntityFilterer _contextFilterer;
    private OperationDescriptionViewModel _description;

    public long Code
    {
        get { return _operation.Code; }
        set
        {
            if (SetProperty(value, _operation.Code, o => _operation.Code = o))
            {
                UpdateDescription();
            }
        }
    }

    public string Description
    {
        get { return _operation.Description; }
        set
        {
            if (SetProperty(value, _operation.Description, o => _operation.Description = o))
            {
                UpdateDescription();
            }
        }
    }

    public string FriendlyName
    {
        get { return _operation.FriendlyName; }
        set
        {
            if (SetProperty(value, _operation.FriendlyName, o => _operation.FriendlyName = o))
            {
                UpdateDescription();
            }
        }
    }

    public int Timeout
    {
        get { return _operation.Timeout; }
        set
        {
            if (SetProperty(value, _operation.Timeout, o => _operation.Timeout = o))
            {
                UpdateDescription();
            }
        }
    }

    public string Category
    {
        get { return _operation.Category; }
        set
        {
            if (SetProperty(value, _operation.Category, o => _operation.Category = o))
            {
                UpdateDescription();
            }
        }
    }

    public bool IsManual
    {
        get { return _operation.IsManual; }
        set
        {
            if (SetProperty(value, _operation.IsManual, o => _operation.IsManual = o))
            {
                UpdateDescription();
            }
        }
    }


    void UpdateDescription()
    {
        //some code
    }




    #region Validation




    #region IDataErrorInfo

    public ValidationResult Validate()
    {
        return ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);
    }

    public string this[string columnName]
    {
        get
        {
            var validation = ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);

            return validation.IsValid ? null : validation.ErrorContent.ToString();
        }
    }

    public string Error
    {
        get
        {
            var result = Validate();
            return result.IsValid ? null : result.ErrorContent.ToString();
        }
    }
    #endregion

    #endregion
}

XAML:

<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>
Alex Essilfie
  • 12,339
  • 9
  • 70
  • 108
Mr.B
  • 3,484
  • 2
  • 26
  • 40
  • 7
    There's also ExceptionValidation. Just set `ValidatesOnExceptions="True"`. then throw exceptions from the Setter. – LuckyLikey Jun 13 '17 at 09:04
  • How can this work? validationRules only appears once in the code. – Paul McCarthy Feb 04 '20 at 10:15
  • 2
    Paul McCarthy, you need to choose ONLY 1 option. Depending on your framework version, but the preferable is INotifyDataErrorInfo - from the latest .NET version. – Mr.B Feb 09 '20 at 20:20
  • @Mr.B: on your NumericValidationRule example, you reference in the XAML " – NirMH May 11 '20 at 09:27
  • Somethere here add your namespace: Something like that – Mr.B May 11 '20 at 22:35
  • I think for new WPF users it would be useful to include information about the intuition between what these different methods do, and when to pick one approach over another: https://stackoverflow.com/questions/2980853/idataerrorinfo-vs-validationrule-vs-exception – ocean800 Jun 04 '20 at 17:58
  • @ocean800 , I believe curious people will do it on their own, the answer is pretty comprehensive, and I don't want to make it bigger. – Mr.B Jun 05 '20 at 01:25
  • Which is the easiest way? – user1034912 Oct 16 '20 at 03:18
33

You can additionally implement IDataErrorInfo as follows in the view model. If you implement IDataErrorInfo, you can do the validation in that instead of the setter of a particular property, then whenever there is a error, return an error message so that the text box which has the error gets a red box around it, indicating an error.

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;
    }
}

And you have to set ValidatesOnDataErrors=True in the XAML in order to invoke the methods of IDataErrorInfo as follows:

<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
Dmytro
  • 16,668
  • 27
  • 80
  • 130
Kumareshan
  • 1,311
  • 8
  • 8
  • This works for me, as it adds a red border around textboxes. Pretty cool. The only problem is that it validates it every time the property has changed. This is surprisingly slow. There is just a slight delay when i'm typing. but +1 for doing what I need – Nlinscott Jul 19 '15 at 14:57
  • 6
    @Nlinscott, I know it's been nearly two years but if you changed the `UpdateSourceTrigger` so it only updates when focus is lost this will be faster. – Brownish Monster Jan 10 '17 at 09:11
13

To get it done only with XAML you need to add Validation Rules for individual properties. But i would recommend you to go with code behind approach. In your code, define your specifications in properties setters and throw exceptions when ever it doesn't compliance to your specifications. And use error template to display your errors to user in UI. Your XAML will look like this

<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>

Code Behind:

public partial class MainWindow : Window
{
    private ExampleViewModel m_ViewModel;
    public MainWindow()
    {
        InitializeComponent();
        m_ViewModel = new ExampleViewModel();
        DataContext = m_ViewModel;
    }
}


public class ExampleViewModel : INotifyPropertyChanged
{
    private string m_Name = "Type Here";
    public ExampleViewModel()
    {

    }

    public string Name
    {
        get
        {
            return m_Name;
        }
        set
        {
            if (String.IsNullOrEmpty(value))
            {
                throw new Exception("Name can not be empty.");
            }
            if (value.Length > 12)
            {
                throw new Exception("name can not be longer than 12 charectors");
            }
            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));
        }

    }
}
saurabh.mridul
  • 177
  • 1
  • 11
  • 7
    -1 Using Exceptions for validation can be way too slow. Not a good practise – Rasto Mar 31 '14 at 21:46
  • 18
    Way to slow? We are dealing with a user interface here. I do not think that the little extra work of instantiating an exception would impose a performance hit here. – Mattias Nordqvist Apr 14 '14 at 13:51
  • 5
    I know this is a year old, but I agree w Mattias. Exceptions are one of the only ways to catch some types of input invalidations and are explicitly used it MS docs. Also this is UI, single input validation, not entire objects. – meteorainer Mar 13 '15 at 11:41
  • 1
    I know this is another year older - but I agree with drasto. Using exceptions for validation is a bad habit and should be avoided wherever possible. – Richard Moore May 03 '16 at 20:50
  • I know this is another year older - but I agree with Mattias. Could someone post a quote from a official MS-Source about which style is to prefer? – wotanii Sep 26 '16 at 12:56
  • I know this is even still another year older, but -- no just kidding it's only two months older. @RichardMoore -- I've never heard that nor do I believe it to be correct at all. Can you back that up with anything? – rory.ap Nov 16 '16 at 12:33
  • @rory.ap it's just a general good practice that I've learned the hard way over the past 30 years. Recently I had a bit of code that validated whether a string was an integer by attempting to cast it and catching an exception. Because catching an exception is computationally very slow, the code was adding 20mS to the execution time. Because this code was used in a loop I was getting huge latency caused by using exceptions for validation. Changing to a TryCatch was a better (faster) approach. Rule of thumb, use exceptions to catch errors, otherwise you're just building technical debt. – Richard Moore Dec 04 '16 at 11:42
  • I know it's another year older but exceptions do have impact on performance. Here is chapter from Microsoft Framework design guidelines dedicated specifically to this topic https://msdn.microsoft.com/en-us/library/ms229009(v=vs.110).aspx – Denis Palnitsky Apr 04 '17 at 21:02
  • i know this is over a year older, but still could someone please give conclusion to this issue and once and for all clarify the best way to handle errors on textboxes? – baye dbest Jun 13 '18 at 08:10
  • 1
    I know it's over a year older, where's the love? – Ben Williams Jan 14 '22 at 17:46
4

I have implemented this validation. But you would be used code behind. It is too much easy and simplest way.

XAML: For name Validtion only enter character from A-Z and a-z.

<TextBox x:Name="first_name_texbox" PreviewTextInput="first_name_texbox_PreviewTextInput" >  </TextBox>

Code Behind.

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 !");
    }
}

For Salary and ID validation, replace regex constructor passed value with [0-9]+. It means you can only enter number from 1 to infinite.

You can also define length with [0-9]{1,4}. It means you can only enter less then or equal to 4 digit number. This baracket means {at least,How many number}. By doing this you can define range of numbers in textbox.

May it help to others.

XAML:

Code Behind.

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 !");
    }
}
pacholik
  • 8,607
  • 9
  • 43
  • 55
Muhammad Mehdi
  • 432
  • 4
  • 12
3

When it comes to Muhammad Mehdi's answer, it is better to do:

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

Because when comparing with the TextCompositionEventArgs it gets also the last character, while with the textbox.Text it does not. With textbox, the error will show after next inserted character.

DiSaSteR
  • 608
  • 6
  • 11
  • I noticed that e.Text doesn't contain the complete text as you state, but only the character that the user just entered. See my answer for what I had to do that it really worked. Now I wonder what I would have to do that your solution would work for me, which is simpler. – Peter Huber Nov 29 '18 at 17:12
0

When I needed to do this, I followed Microsoft's example using Binding.ValidationRules and it worked first time.

See their article, How to: Implement Binding Validation: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-binding-validation?view=netframeworkdesktop-4.8

Phil Rogers
  • 545
  • 4
  • 11
  • 1
    A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](/help/deleted-answers) – Sabito stands with Ukraine Nov 21 '20 at 04:49
-1

When it comes to DiSaSteR's answer, I noticed a different behavior. textBox.Text shows the text in the TextBox as it was before the user entered a new character, while e.Text shows the single character the user just entered. One challenge is that this character might not get appended to the end, but it will be inserted at the carret position:

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");
  }
}
Peter Huber
  • 3,052
  • 2
  • 30
  • 42