DeveloperLV
DeveloperLV

Reputation: 1781

How to validate DataGrid selected Item in WPF?

Project: https://github.com/98gzi/Validate-DataGrid-Selected-Item

Goal

My goal is to click on a datagrid row, get the selected item values and bind it to a textbox. So I can edit the values there, rather than directly in DataGrid.

DataGrid:

<DataGrid ItemsSource="{Binding List}" SelectedItem="{Binding SelectedRecord}" />

Selected record display's the record's property into a textbox:

<TextBox Text="{Binding SelectedRecord.FirstName}" />

This works as it should, until it comes to validation.

Problem

I added the following code to add validation:

XAML

<TextBox Text="{Binding SelectedRecord.FirstName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

View Model Validation

#region IDataErrorInfo
public string Error { get { return null; } }
public Dictionary<string, string> ErrorCollection { get; private set; } = new Dictionary<string, string>();
public string this[string propertyName]
{
    get
    {
        string result = null;

        switch (propertyName)
        {
            case "SelectedRecord.FirstName":
                if (string.IsNullOrWhiteSpace(SelectedRecord.FirstName))
                    result = "First Name cannot be empty";
                break;
            default:
                break;
        }

        if (ErrorCollection.ContainsKey(propertyName))
            ErrorCollection[propertyName] = result;
        else if (result != null)
            ErrorCollection.Add(propertyName, result);

        OnPropertyChanged(nameof(ErrorCollection));

        return result;
    }
}

#endregion

Question

This does not do anything. i.e No errors show up in the UI and also the debugger does not go into debug mode.

Is this the correct approach in validation a selected record? If not, what is the correct approach?

Upvotes: 0

Views: 537

Answers (2)

EldHasp
EldHasp

Reputation: 7908

Is this the correct approach in validation a selected record? If not, what is the correct approach?

Not. You are doing wrong.

It is impossible to give a complete answer - it requires more code of your implementation.
Therefore, I am writing what I could understand from your explanations and partial code.

Do you need to validate the values ​​of a single DataGrid row, the one that is currently selected.
And the interface IDataErrorInfo is most likely implemented at the collection level (ViewModel), and not in the element itself.
Therefore, you expect the "SelectedRecord.FirstName" composite index to be queried.

But bindings don't work that way.

The path in the binding only indicates how to find the property along that path. After finding it, the binding will work directly with the property itself, and not the path specified for its search.

Therefore, it is necessary to implement IDataErrorInfo in the type used to represent the string.
And wait for a request only for the name of a property of this type "FirstName".

Updated my question with project link

Your repository does not have a complete Solution, so I cannot check the changes made - the project is not going to be built.

I will describe here the changes that need to be made. You add them yourself, and then add your entire Solution to your repository. So that if there are errors, I can reproduce them.

  1. Remove the BaseViewModel and RelayCommand classes. Their implementation is too simplistic. Add the BaseInpc, RelayCommand, and RelayCommand classes to the project. These are more advanced implementations. They are easier to use and reduce the likelihood of some bugs.

  2. ViewModel:

using Simplified;
using System.Collections.ObjectModel;

namespace WpfApp1
{
    public class ViewModel : BaseInpc
    {
        public ViewModel()
        {
            Names.Add(new Model { Id = 1, FirstName = "First Name 1", LastName = "Last Name 1" });
            Names.Add(new Model { Id = 2, FirstName = "First Name 2", LastName = "Last Name 2" });
            Names.Add(new Model { Id = 3, FirstName = "First Name 3", LastName = "Last Name 3" });
        }

        #region Properties
        public ObservableCollection<Model> Names { get; } = new ObservableCollection<Model>();

        private Model _selectedRecord;
        public Model SelectedRecord
        {
            get => _selectedRecord;
            set => Set(ref _selectedRecord, value);
        }
        #endregion
    }
}
  1. Entity class.
    I didn’t change the name so you don’t get confused, but this is not a MODEL! The Model in MVVM is where the instances of this entity are created. The part of the ViewModel code that creates the Names collection is, in fact, your Model in MVVM.
using Simplified;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace WpfApp1
{
    public class Model : BaseInpc, IDataErrorInfo
    {
        private int _id;
        private string _firstName;
        private string _lastName;
        private string _error;

        public int Id { get => _id; set => Set(ref _id, value); }
        public string FirstName { get => _firstName; set => Set(ref _firstName, value); }
        public string LastName { get => _lastName; set => Set(ref _lastName, value); }

        #region IDataErrorInfo
        public string Error { get => _error; private set => Set(ref _error, value); }
        public Dictionary<string, string> ErrorCollection { get; } = new Dictionary<string, string>();
        public string this[string propertyName]
        {
            get
            {
                if (string.IsNullOrWhiteSpace(propertyName))
                    return null;

                string result = null;

                switch (propertyName)
                {
                    case nameof(FirstName):
                        if (string.IsNullOrWhiteSpace(FirstName))
                            result = "First Name cannot be empty";
                        break;
                    default:
                        break;
                }

                if (string.IsNullOrWhiteSpace(result))
                    ErrorCollection.Remove(propertyName);
                else
                    ErrorCollection[propertyName] = result;


                Error = string.Join(Environment.NewLine,
                    ErrorCollection.Where(pair => !string.IsNullOrWhiteSpace(pair.Value))
                        .Select(pair => $"{pair.Key}:\"{pair.Value}\""));

                return result;
            }
        }

        #endregion

    }
}
  1. Window.
    Move data context creation to XAML. It will be more convenient for you to construct the XAML of Windows yourself.
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

}
<Window x:Class="WpfApp1.MainWindow"
        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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <FrameworkElement.DataContext>
        <local:ViewModel/>
    </FrameworkElement.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Grid Grid.Column="0">
            <DataGrid ItemsSource="{Binding Names}"  SelectedItem="{Binding SelectedRecord}"/>
        </Grid>

        <StackPanel Grid.Column="1" DataContext="{Binding SelectedRecord}">
            <Label Content="First Name" />
            <TextBox Text="{Binding FirstName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <Label Content="Last Name" />
            <TextBox Text="{Binding LastName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
    </Grid>
</Window>

Upvotes: 1

Raj Shaw
Raj Shaw

Reputation: 51

activate the cell_click event handler for the DataGrid. Then

    private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
    {
        if (e.RowIndex >= 0)
        {
            DataGridViewRow row = this.dataGridView1.Rows[e.RowIndex];
            TextBox1.text = row.Cells["id_number"].Value.ToString();
        }
    }

** Replace textbox1.text with the ID of your textbox, dataGridView1 with your datagrid ID and replace "id_number" with the name of your datagride column name.

Upvotes: 0

Related Questions