question

FanhuaKong-4325 avatar image
0 Votes"
FanhuaKong-4325 asked ·

Using validation rules to disable a button

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.

wpf
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

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 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi, thanks for your reply. It worked! And I try to simplify your code a little. I found that if you use CanExecute to control the button's enable status, ObservableCollection works fine as well. And the use of [CallerMemberName] is quite a easier way to work with OnProertyChanged. Thanks agian! Originally, I was trying to use the binding to solve it on the UI level so I can stop passing wrong data to the ViewModel as following. But maybe because the DataGrid is dynamic generate its rows, so the element name binding won't work.

0 Votes 0 · ·
AlexLi-MSFT avatar image
0 Votes"
AlexLi-MSFT answered ·

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.


1.gif (55.1 KiB)
· 1 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi, thanks for your answer. But I'm using MVVM mode. I think it should be done by binding or something. Do you know how to do that in MVVM?

0 Votes 0 · ·