J'ai 3 TextBoxes (Id1
, Name
et Salary
). Id
et Salary
devraient contenir des entiers et Name
ne devrait contenir que des caractères. J'ai besoin de validations pour ma zone de texte, elle devrait afficher des erreurs lorsque je saisis des caractères ou des entiers incorrects Cela peut-il aussi être fait uniquement dans Xaml sans codebehind? S'il vous plaît aidez-moi avec le code requis
C'est le code 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"/>
Il y a 3 façons d'implémenter la validation:
Exemple de règle de validation :
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:
Très important : n'oubliez pas de définir ValidatesOnTargetUpdated="True"
, cela ne fonctionnera pas sans cette définition.
<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>
Exemple INotifyDataErrorInfo :
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
}
Voir l'implémentation du modèle:
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 :
Voir le modèle:
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>
Vous pouvez également implémenter IDataErrorInfo
comme suit dans le modèle de vue. Si vous implémentez IDataErrorInfo
, vous pouvez effectuer la validation en ce sens que, au lieu de définir une propriété particulière, alors, en cas d'erreur, renvoyez un message d'erreur afin que la zone de texte contenant l'erreur contienne un encadré rouge indiquant un Erreur.
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;
}
}
Et vous devez définir ValidatesOnDataErrors=True
dans le code XAML pour appeler les méthodes de IDataErrorInfo
comme suit:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
Pour le faire uniquement avec XAML, vous devez ajouter des règles de validation pour des propriétés individuelles. Mais je vous recommanderais d’appliquer le code derrière l’approche. Dans votre code, définissez vos spécifications dans les paramètres de propriétés et émettez des exceptions lorsque cela n’est pas conforme à vos spécifications. Et utilisez le modèle d'erreur pour afficher vos erreurs à l'utilisateur dans l'interface utilisateur. Votre XAML ressemblera à ceci
<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 Derrière:
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));
}
}
}
J'ai implémenté cette validation. Mais vous seriez utilisé du code derrière. C'est trop facile et le plus simple.
XAML: Pour le nom Validtion, entrez uniquement les caractères de A à Z et a-z.
<TextBox x:Name="first_name_texbox" PreviewTextInput="first_name_texbox_PreviewTextInput" > </TextBox>
Code derrière.
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 !");
}
}
Pour la validation des salaires et des identifiants, remplacez la valeur passée par le constructeur de regex par [0-9]+
. Cela signifie que vous ne pouvez entrer qu'un nombre compris entre 1 et l'infini.
Vous pouvez également définir la longueur avec [0-9]{1,4}
. Cela signifie que vous ne pouvez entrer qu'un nombre inférieur ou égal à 4 chiffres. Ce baracket signifie {au moins, combien de nombre}. En faisant cela, vous pouvez définir une plage de nombres dans la zone de texte.
Puisse-t-il aider les autres.
XAML:
Code derrière.
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 !");
}
}
Quand il s'agit de la réponse de Muhammad Mehdi, il vaut mieux faire:
private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex ( "[^0-9]+" );
if(regex.IsMatch(e.Text))
{
MessageBox.Show("Error");
}
}
Parce que lors de la comparaison avec TextCompositionEventArgs, il obtient également le dernier caractère, alors qu'avec textbox.Text, ce n'est pas le cas. Avec zone de texte, l'erreur montrera après le caractère inséré suivant.
En ce qui concerne la réponse de DiSaSteR, j'ai remarqué un comportement différent. textBox.Text affiche le texte dans la zone de texte tel qu'il était avant que l'utilisateur n'entre un nouveau caractère, tandis que e.Text affiche le seul caractère que l'utilisateur vient d'entrer. Un défi est que ce personnage ne soit pas ajouté à la fin, mais il sera inséré à la position du 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");
}
}