0

I want to validate my DataGrid rows for errors, and if there are any errors present then the 'Save' button should be disabled or at least there should be some message saying there are errors.

After some research I stumbled upon the following post: Detecting WPF Validation Errors

The post has as solution to use this piece of code for validation a DataGrid on errors:

private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = IsValid(sender as DependencyObject);
}

private bool IsValid(DependencyObject obj)
{
    // The dependency object is valid if it has no errors and all
    // of its children (that are dependency objects) are error-free.
    return !Validation.GetHasError(obj) &&
    LogicalTreeHelper.GetChildren(obj)
    .OfType<DependencyObject>()
    .All(IsValid);
}

However, I am using the ICommand implementation for my button and I can't seem to figure out how to implement the IsValid function with it. I tried several things with registering the DependencyObject, using a Binding and some other stuff.

Could anyone point me in the right direction, it should be rather simple I guess but I just can't get my head around it.

Below the implementation of my button:

public class MyViewModel
{
    public ICommand MyCommandButton { get; set; }


    public MyViewModel()
    {
        MyCommandButton = new BaseCommand(MyCommandFunction);
        this.Initialize();
    }

    private void MyCommandFunction(object obj)
    {
        //... some (not yet implemented) logic
    }




    public class BaseCommand : ICommand
    {
        private Predicate<object> _canExecute;
        private Action<object> _method;
        public event EventHandler CanExecuteChanged;

        public BaseCommand(Action<object> method)
            : this(method, null)
        {
        }

        public BaseCommand(Action<object> method, Predicate<object> canExecute)
        {
            _method = method;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }

            return _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _method.Invoke(parameter);
        }
    }
}

XAML Button:

<Button
        Name="MyButton"
        Command="{Binding MyCommandButton}"
/>

UPDATE

After some more research (Object parameter of MyCommandCanExecuteFunction in @RajN example was returning null everytime), I stumbled upon the following post: object sender is always null in RelayCommand which states I should use the CommandParameter for it not to be null. Now the IsValid function works but keeps returning TRUE ( so it's Valid) even when there are errors on the datagrid.

I suspect there is something wrong with the datagrid / data itself so i'll post that below as well:

The DataGrid

<DataGrid x:Name="MainGrid" 
    ItemsSource="{Binding ItemList}"
    SelectedItem="{Binding SelectedItem,Converter={StaticResource ignoreNewItemPlaceHolderConverter}}" 
    AutoGenerateColumns="False"
    DataContextChanged="OnMainGridDataContextChanged" 
    CanUserAddRows="False" 
    >

    <DataGrid.Columns>

        <DataGridTextColumn>
        <DataGridTextColumn.Header>
            <Grid Margin="0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="16"/>
                </Grid.ColumnDefinitions>
                    <Button
                        Height="25"
                        Width="25"
                        Style="{StaticResource MaterialDesignFloatingActionMiniDarkButton}"
                        Command="{Binding Path=DataContext.AddRowCommand, 
                            RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
                        ToolTip="Voeg regel toe"
                    >
                    <materialDesign:PackIcon
                        Kind="Add"
                        Height="24"
                        Width="24" />
                    </Button>
                </Grid>
        </DataGridTextColumn.Header>
        </DataGridTextColumn>

        <DataGridTextColumn Binding="{Binding SequenceNumber}" 
                    Header="Line"
                    EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnEditingStyle}"
                    Width="63" 
                    IsReadOnly="true" />


        <DataGridTextColumn Header="Width" Width="100" IsReadOnly="false" EditingElementStyle="{StaticResource errTemplate}" >
            <DataGridTextColumn.Binding>
                <Binding Path="Width" ValidatesOnDataErrors="True">
                    <Binding.ValidationRules>
                        <validationbinding1:RequiredRule ValidatesOnTargetUpdated="True"/>
                        <validationbinding1:NumericRule ValidatesOnTargetUpdated="True" />
                    </Binding.ValidationRules>
                </Binding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>

        <DataGridTextColumn Header="Height" Width="100" IsReadOnly="false" EditingElementStyle="{StaticResource errTemplate}" >
            <DataGridTextColumn.Binding>
                <Binding Path="Height" ValidatesOnDataErrors="True" NotifyOnValidationError = "True">
                    <Binding.ValidationRules>
                        <validationbinding1:RequiredRule />
                        <validationbinding1:NumericRule />
                    </Binding.ValidationRules>
                </Binding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>


        ~~Some other columns which are left out     

    </DataGrid.Columns>
</DataGrid>

The DataGrid properties and bindings:

On the view itself

private void OnMainGridDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    m_MyViewModel = (m_MyViewModel)this.DataContext;
}

In the ViewModel

public class MyViewModel : MyModel
{

    // Property variables
    private ObservableCollection<ItemListDetails> p_ItemList;


    public ICommand MyCommandButton { get; set; }

    public MyViewModel()
    {
        MyCommandButton = new BaseCommand(MyCommandFunction, canExecute);
        this.Initialize();
    }


    private bool canExecute (object obj)
    {
        return IsValid(obj as DependencyObject);
    }


    private bool IsValid(DependencyObject obj)
    {
        // The dependency object is valid if it has no errors and all
        // of its children (that are dependency objects) are error-free.
        if (obj == null)
            return true;

        return !Validation.GetHasError(obj) &&
        LogicalTreeHelper.GetChildren(obj)
        .OfType<DependencyObject>()
        .All(IsValid);
    }


    private void MyCommandFunction(object obj)
    {
        //... some (not yet implemented) logic
    }

    private void AddRow(object obj)
    {
        ItemListDetails Item = new ItemListDetails
        {
            Width = 0,
            Height = 0,
        };

        p_ItemList.Add(Item);
    }


    public ObservableCollection<ItemListDetails> ItemList
    {
        get { return p_ItemList; }

        set
        {
            p_ItemList = value;
            this.MutateVerbose(ref p_ItemList, value, RaisePropertyChanged());
        }
    }

    /// <summary>
    /// The currently-selected item.
    /// </summary>
    public ItemListDetails SelectedItem { get; set; }



    /// <summary>
    /// Updates the ItemCount Property when the list collection changes.
    /// </summary>
    void OnListChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {

        // Update item count
        this.ItemCount = this.p_ItemList.Count;

        // Resequence list
        SequencingService.SetCollectionSequence(this.p_ItemList);
    }



    /// <summary>
    /// Initializes this application.
    /// </summary>
    private void Initialize()
    {
        // Create item list
        p_ItemList = new ObservableCollection<ItemListDetails>();

        // Subscribe to CollectionChanged event
        p_ItemList.CollectionChanged += OnListChanged;

        // Initialize list index
        this.p_ItemList = SequencingService.SetCollectionSequence(this.p_ItemList);

        // Update bindings
        //base.RaisePropertyChangedEvent("GroceryList");
        //this.MutateVerbose(ref _materiaal, value, RaisePropertyChanged());

    }

}

The ItemListDetails class of the DataGrid Items

public class ItemListDetails  : ObservableObject, ISequencedObject
{

    // Property variables
    private int p_SequenceNumber;
    private int p_Width;
    private int p_Height;

    /// <summary>
    /// Default constructor
    /// </summary>
    public ItemListDetails ()
    {
    }

    /// <summary>
    /// Paramterized constructor.
    /// </summary>
    public ItemListDetails (int width, int height, int itemIndex)
    {
        p_Width = width;
        p_Height = height;
        p_SequenceNumber = itemIndex;
    }

    /// <summary>
    /// The sequential position of this item in a list of items.
    /// </summary>
    public int SequenceNumber
    {
        get { return p_SequenceNumber; }

        set
        {
            p_SequenceNumber = value;
            base.RaisePropertyChangedEvent("SequenceNumber");
        }
    }


    /// <summary>
    /// The width
    /// </summary>
    public int Width
    {
        get { return p_Width; }

        set
        {
            p_Width = value;
            base.RaisePropertyChangedEvent("Int"); 
        }
    }

    /// <summary>
    /// The height
    /// </summary>
    public int Height
    {
        get { return p_Height; }

        set
        {
            p_Height = value;
            base.RaisePropertyChangedEvent("Int"); 
        }
    }
}

The MyModel class contains some other fields which are not contained in the DataGrid.

Nicolas
  • 2,277
  • 5
  • 36
  • 82

1 Answers1

0

Here you go

public class MyViewModel
{
    public ICommand MyCommandButton { get; set; }


    public MyViewModel()
    {
        MyCommandButton = new BaseCommand(MyCommandFunction, MyCommandCanExecuteFunction);
        this.Initialize();
    }

    private void MyCommandFunction(object obj)
    {
        //... some (not yet implemented) logic
    }
    private bool MyCommandCanExecuteFunction(object obj)
    {
        return IsValid(obj as DependencyObject);
    }

    private bool IsValid(DependencyObject obj)
    {
        // The dependency object is valid if it has no errors and all
        // of its children (that are dependency objects) are error-free.
        return !Validation.GetHasError(obj) &&
        LogicalTreeHelper.GetChildren(obj)
        .OfType<DependencyObject>()
        .All(IsValid);
    }
}
Cinchoo
  • 6,088
  • 2
  • 19
  • 34
  • Already tried this, for some reason the `object` in `MyCommandCanExecuteFunction` remains `null`. – Nicolas Dec 27 '18 at 07:53