Me5
Me5

Reputation: 1895

CanExecute does not disable button when dataTemplate validation mvvm wpf

I have a dynamically generated window with some textboxes that have validation rule. The textbox validation is working, I can see the new style I have added, but I linked the IsValid function to CanExecute on save button and the button is always enabled, even if textbox validation is error.
Can anyone give me a hint what I`m missing?
Here is my window:

<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource = "{Binding Path = List}" >
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation = "Horizontal">
                            <Grid>    
                                <TextBox >
                                    <TextBox.Text>
                                        <Binding Path="Path" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True" Mode="TwoWay" ValidatesOnNotifyDataErrors="True">
                                            <Binding.ValidationRules>
                                                <validationrules:FolderValidationRule/>
                                            </Binding.ValidationRules>
                                        </Binding>
                                    </TextBox.Text>
                                    <TextBox.Style>
                                        <Style TargetType="TextBox">
                                            <Style.Triggers>
                                                <Trigger Property="Validation.HasError" Value="True">
                                                    <Setter Property="Background" Value="Pink"/>
                                                    <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                                                </Trigger>
                                            </Style.Triggers>
                                        </Style>
                                    </TextBox.Style>
                                </TextBox>
                            </Grid>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>      
        <Button Grid.Row="1" Content="Save" Command="{Binding ApplyChanges, Mode=TwoWay}" CommandParameter="{Binding ElementName=EditNewForm}" VerticalAlignment="Bottom" Width="114" Height="23" Margin="246,0,34,7"/>

the command:

 public RelayCommand<Window> ApplyChanges { get; private set; }
 ApplyChanges = new RelayCommand<Window>(ApplyChangesCommand, CanSaveExecute);

and the code:

 private bool IsValid(DependencyObject obj)
    {
      if (obj != null)
      {
        return !Validation.GetHasError(obj) && LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>().All(IsValid);
      }
      return true;

    }
    private bool CanSaveExecute(Window sender)
    {
      bool valid = IsValid(sender as DependencyObject);
      return valid;
    }

Upvotes: 0

Views: 483

Answers (1)

Nik
Nik

Reputation: 1870

I am assuming that your RelayCommand implementation has something like this:

public event EventHandler CanExecuteChanged
{
  add { CommandManager.RequerySuggested += value; }
  remove { CommandManager.RequerySuggested -= value; }
}

This hands the control over to the UI to query the CanExecute condition when it feels like it is appropriate, and it would not be an easy task to tell you exactly when that is. For the most part it works quite well, but not always. At times, you have to force a Requery if it is not working as expected. Simply call the static method below:

CommandManager.InvalidateRequerySuggested();

This will invalidate the command bindings and force the UI to query the CanExecute condition for all the UI elements that utilize it. It's hard to say where you would call that as there is no code in the question, but I imagine your DataContext knows when the text changes in the TextBox in question. Try calling it whenever the object bound to the TextBox changes. Not the prettiest approach, but it always works.

Upvotes: 1

Related Questions