I had such a problem: I need the button to become inactive if there is an error in the TextBox. The problem is that there are several such TextBoxes. I tried to do the usual validation on exceptions and a few flags for each of the fields denoting their validity and to bind a button. But it is very dreary. And if you add fields? All new flags to create? Terribly uncomfortable and clumsy. Please tell me some more beautiful way.
- Look at this question. - Vlad
- @Vlad I perfectly understand the validation mechanisms. I do not need these obvious things to poke. And specifically that topic in particular, I have already seen before. She does not solve my problem. - PECHAPTER
- Then it is not clear what the problem is? You put all the properties you need to validate in the VM, hang the corresponding attributes and bind them to the TextBox. The availability of the command is associated with the presence of validation errors. When adding new fields, no additional gestures will be required. - Vlad
- @Vlad button problem. Exclusively in it. It must be inactive if there are input errors in the TextBox. - PECHAIR
- So bind the CanExecute command, which is tied to the button, to the presence of validation errors. - Vlad
2 answers
If you use MVVM, then everything is quite simple. Doing validation at the data source. Properties with validation errors are listed. If the list is empty, then there are no errors, which means you can click on the button. Here is an example of a VM:
public sealed class MainVm : INotifyPropertyChanged, IDataErrorInfo { // ΠΏΠ΅ΡΠ΅ΡΠ΅Π½Ρ ΡΠ²ΠΎΠΉΡΡΠ² Ρ ΠΎΡΠΈΠ±ΠΊΠ°ΠΌΠΈ private readonly HashSet<string> propertiesWithErrors = new HashSet<string>(); // ΠΌΠ΅ΡΠΎΠ΄ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠΈ ΡΠ²ΠΎΠΉΡΡΠ²Π° private string Validate(string propertyName) { var value = GetType() .GetProperty(propertyName) .GetValue(this, null); var results = new List<ValidationResult>(); var context = new ValidationContext(this, null, null) { MemberName = propertyName }; if (!Validator.TryValidateProperty(value, context, results)) { // Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΎ ΡΠΎΠΌ, ΡΡΠΎ ΡΡΠΎ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ ΠΎΡΠΈΠ±ΠΊΡ propertiesWithErrors.Add(propertyName); return results.First().ErrorMessage; } // ΡΠ΄Π°Π»ΡΠ΅ΠΌ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ΅ Π² ΡΠ²ΠΎΠΉΡΡΠ²Π΅ propertiesWithErrors.Remove(propertyName); return string.Empty; } // ΡΠ°ΠΌΠΈΠ»ΠΈΡ (Π±ΡΠΎΡΠ°Π΅Ρ PropertyChanged) [Required(AllowEmptyStrings = false)] public string Surname { get; set; } // ΠΈΠΌΡ (Π±ΡΠΎΡΠ°Π΅Ρ PropertyChanged) [Required(AllowEmptyStrings = false)] public string Name { get; set; } // ΠΊΠΎΠΌΠ°Π½Π΄Π°, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΏΡΠΈΠ²ΡΠ·ΡΠ²Π΅ΡΡΡ ΠΊ ΠΊΠ½ΠΎΠΏΠΊΠ΅. ΠΠΎΠΏΡΡΡΠΈΠΌ, ΠΏΡΠΈΠΌΠ΅Π½ΠΈΡΡ ΠΊΠ°ΠΊΠΈΠ΅-Π»ΠΈΠ±ΠΎ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ public ICommand ApplyCommand { get; } // ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ IDataErrorInfo public string Error { get { throw new NotImplementedException(); } } string IDataErrorInfo.this[string propertyName] { get { return Validate(propertyName); } } // ------ public MainVm() { // ΡΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠΎΠΌΠ°Π½Π΄Ρ // ΠΏΠ΅ΡΠ²ΡΠΉ Π°ΡΠ³ΡΠΌΠ΅Π½Ρ Execute (Π½ΠΈΡΠ΅Π³ΠΎ Π½Π΅ Π΄Π΅Π»Π°Π΅ΠΌ) // Π²ΡΠΎΡΠΎΠΉ Π°ΡΠ³ΡΠΌΠ΅Π½Ρ CanExecute (true, Π΅ΡΠ»ΠΈ Π½Π΅Ρ ΡΠ²ΠΎΠΉΡΡΠ² Ρ ΠΎΡΠΈΠ±ΠΊΠ°ΠΌΠΈ) ApplyCommand = new RelayCommand(o => { }, o => !propertiesWithErrors.Any()); } RelayCommand can be pulled from here . The presentation is still easier. Bind properties to the TextBox, and the button to the command.
<Window ...> <Window.DataContext> <vm:MainVm/> </Window.DataContext> <StackPanel> <TextBox Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> <TextBox Text="{Binding Surname, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> <Button Content="Button" Command="{Binding ApplyCommand}"/> </StackPanel> </Window> UPD
No, the Required attribute has nothing to do with INotifyPropertyChanged. The property, in fact, should look like this:
public string Name { get { return name; } set { if (name != value) { name = value; OnPropertyChanged(nameof(Name)); } } } private string name; private void OnPropertyChanged(string propertyName) { var tmp = PropertyChanged; if (tmp != null) { tmp(this, new PropertyChangedEventArgs(propertyName)); } } The Required attribute says that this property should be set to a value other than null and string.Empty.
- What is the attribute Required? - PECHAPTER
- From System.ComponentModel.DataAnnotations - Vlad
- I did not understand, is it causing PropertyChanged itself ??? Those. is it like Fody.PropertyChanged analog from .NET? - PECHAPTER
- @DarkByte, what is it? - Vlad
- Well, what does this attribute itself call PropertyChanged when a property changes? - PECHAPTER
You can add a Count field for the button, if the textbox changed its status to wrong, then +1 to count, if it changed from wrong to correct, then -1 count. (Here we must carefully make the field add \ cancel only when the status is changed to the opposite, that is, the count value interval is from 0 to the textbox number.) Accordingly, as soon as count turned to 0, the button works.
- here I already thought of it myself. Well, if no one offers a more elegant solution, I will probably do so. - PECHAPTER
- can be done with events - R0manych
- in terms of events? - PECHAIRTER
- There is still a problem in that it is always necessary to declare a binding to the text field. Because it is necessary to increment the counter even in case of incorrect conversion. Horror, of course, how uncomfortable anyway ... - PECHAPPER
- For example, if I have a double field, but I still have to declare a textual for binding, in order to check the conversion to double in the setter. This, too, is a mistake ... - PECHAPTER