Using validation rules to disable a button

Fanhua Kong 241 Reputation points
2020-02-29T07:50:28.677+00:00

Hi,
I know that in WPF, I could bind a button's IsEnabled property with some TextBox to receive the Validation.HasError result as its IsEnabled value. This does work for simple TextBox.

But in my case, I have a DataGrid for user to do the input. And for the TextBox column of the DataGrid, I binding a validation rule to it as followings:

 <DataGridTemplateColumn Header=TransformerNumber Width=* HeaderStyle={StaticResource NormalHeader}>  
                        <DataGridTemplateColumn.CellTemplate>  
                            <DataTemplate>  
                                <Grid>  
                                    <TextBox Name=txtNumber Style={StaticResource RegularTxtBox}>  
                                        <TextBox.Text>  
                                            <Binding Path =Number Mode=TwoWay UpdateSourceTrigger=PropertyChanged NotifyOnValidationError=True ValidatesOnDataErrors=True ValidatesOnExceptions=True ValidatesOnNotifyDataErrors=True>  
                                                <Binding.ValidationRules>  
                                                    <local:TransformerNumberValidationRule ValidationStep=UpdatedValue/>  
                                                </Binding.ValidationRules>  
                                            </Binding>  
                                        </TextBox.Text>  
                                    </TextBox>  
                                </Grid>  
                            </DataTemplate>  
                        </DataGridTemplateColumn.CellTemplate>  
                    </DataGridTemplateColumn>  

The validation works fine in this case(When the input is incorrect, the TextBox cell is shown with a red border). However, the button I want to disable is totally lost control. Even if the input is incorrect, I can still click the button.
Below is the trigger code for disabling the button

 <Style.Triggers>  
            <MultiDataTrigger>  
                <MultiDataTrigger.Conditions>  
                    <Condition Binding={Binding ElementName=txtNumber, Path=(Validation.HasError)} Value=true />  
                </MultiDataTrigger.Conditions>  
                <Setter Property=IsEnabled Value=False />  
            </MultiDataTrigger>  
        </Style.Triggers>  

So, anyone can tell me how could I do this in the situation. Thanks a lot.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,669 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-03-03T15:12:35.813+00:00

    Hi,
    you can use data object with INotifyPropertyChanged to inform about changes and IDataErrorInfo for detection of errors in data objects. Inheriting Collection from ObservableCollection you can inform the UI about changes and switch enable / disable of command button using RelayCommand class.

    Try following demo:

    XAML:

    <Window x:Class="WpfApp1.Window01"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp01"  
            mc:Ignorable="d"  
            Title="Validating in DataGrid" Height="450" Width="800">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
      <Grid>  
        <Grid.ColumnDefinitions>  
          <ColumnDefinition/>  
          <ColumnDefinition/>  
        </Grid.ColumnDefinitions>  
        <DataGrid ItemsSource="{Binding View}" AutoGenerateColumns="False">  
          <DataGrid.Columns>  
            <DataGridTemplateColumn Header="TransformerNumber">  
              <DataGridTemplateColumn.CellTemplate>  
                <DataTemplate>  
                  <Grid>  
                    <TextBox Name="txtNumber">  
                      <TextBox.Text>  
                        <Binding Path ="Number" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"   
                                 NotifyOnValidationError="True" ValidatesOnDataErrors="True" ValidatesOnExceptions="True"  
                                 ValidatesOnNotifyDataErrors="True">  
                          <Binding.ValidationRules>  
                            <local:TransformerNumberValidationRule ValidationStep="UpdatedValue"/>  
                          </Binding.ValidationRules>  
                        </Binding>  
                      </TextBox.Text>  
                    </TextBox>  
                  </Grid>  
                </DataTemplate>  
              </DataGridTemplateColumn.CellTemplate>  
            </DataGridTemplateColumn>  
          </DataGrid.Columns>  
        </DataGrid>  
        <StackPanel Grid.Column="1">  
          <Button Content="Save" Command="{Binding Cmd}" Margin="5"/>  
        </StackPanel>  
      </Grid>  
    </Window>  
    

    And classes:

    using System;  
    using System.Collections.Generic;  
    using System.Collections.ObjectModel;  
    using System.Collections.Specialized;  
    using System.ComponentModel;  
    using System.Globalization;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Input;  
    
    namespace WpfApp01  
    {  
      public partial class ViewModel : INotifyPropertyChanged  
      {  
        public ViewModel()  
        {  
          TrulyObservableCollection<Data> col = new TrulyObservableCollection<Data>();  
          for (int i = 1; i < 10; i++) col.Add(new Data() { ID = i, Number = 0 });  
          col.CollectionChanged += (sender, e) => { OnPropertyChanged(nameof(Cmd)); };  
          cvs.Source = col;  
        }  
        private CollectionViewSource cvs = new CollectionViewSource();  
        public ICollectionView View { get => cvs.View; }  
      
        public ICommand Cmd { get => new RelayCommand(CmdExec, CanCmdExec); }  
        private void CmdExec(Object obj)  
        {  
          //   
        }  
        private bool CanCmdExec(object obj)  
        {  
          foreach (var item in View)  
          {  
            var d = item as Data;  
            if (d != null && !string.IsNullOrEmpty(d.Error)) return false;  
          }  
          return true;  
        }  
        public event PropertyChangedEventHandler PropertyChanged;  
        private void OnPropertyChanged([CallerMemberName] string propName = "") =>  
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
      
      public class TransformerNumberValidationRule : ValidationRule  
      {  
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)  
        {  
          var be = value as BindingExpression;  
          if (be == null) return new ValidationResult(false, "Invalid use of validation");  
          var d = be.DataItem as Data;  
          if (d == null) return new ValidationResult(false, "Invalid use of validation");  
          if (!string.IsNullOrEmpty(d.Error)) return new ValidationResult(false, "Please enter a value in the range: 10 - 50");  
          return new ValidationResult(true, null);  
        }  
      }  
      
      public sealed class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged  
      {  
        public TrulyObservableCollection() => CollectionChanged += FullObservableCollectionCollectionChanged;  
        public TrulyObservableCollection(IEnumerable<T> pItems) : this()  
        {  
          foreach (var item in pItems) this.Add(item);  
        }  
        private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)  
        {  
          if (e.NewItems != null) foreach (Object item in e.NewItems) ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;  
          if (e.OldItems != null) foreach (Object item in e.OldItems) ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;  
        }  
        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender)));  
      }  
      
      public class RelayCommand : ICommand  
      {  
        private readonly Predicate<object> _canExecute;  
        private readonly Action<object> _action;  
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }  
        public void Execute(object o) => _action(o);  
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);  
        public event EventHandler CanExecuteChanged  
        {  
          add { CommandManager.RequerySuggested += value; }  
          remove { CommandManager.RequerySuggested -= value; }  
        }  
      }  
      
      public class Data : INotifyPropertyChanged, IDataErrorInfo  
      {  
        public int ID { get; set; }  
      
        private int _number;  
        public int Number  
        {  
          get => this._number;  
          set { this._number = value; OnPropertyChanged(); }  
        }  
        public string Error => (Number < 10 || Number > 50) ? "Please enter a value in the range: 10 - 50" : "";  
        public string this[string columnName] => (Number< 10 || Number> 50)? "Please enter a value in the range: 10 - 50":"";  
        public event PropertyChangedEventHandler PropertyChanged;  
        private void OnPropertyChanged([CallerMemberName] string propName = "") =>      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
    }  
    

1 additional answer

Sort by: Most helpful
  1. Alex Li-MSFT 1,096 Reputation points
    2020-03-02T07:36:02.357+00:00

    Hi,

    Welcome to our Microsoft Q&A platform!

    You can try my code.When the TextBox value is greater than 100, show an error.

      private void TxtNumber_TextChanged(object sender, TextChangedEventArgs e)  
            {  
                TextBox tb = sender as TextBox;  
                Button1.IsEnabled = Validation.GetHasError(tb) == true ? false : true;  
            }  
    

    3622-1.gif

    Thanks.