Resolved How can I notify source property about the changes made in Wpf DataGrid?

destro

Well-known member
Joined
Mar 28, 2020
Messages
46
Programming Experience
1-3
I have a Datagrid bound to Datatable through Dependency property. I am unable to retrieve changes I make in datagrid even when I have set binding mode as two way.

What are the best possible ways to update changes back to Source DataTable when dataGrid is edited?

I know I can use DataAdapter.Update() to commit changes to database but that's not what I am asking so don't confuse. I want to know effecient ways to save changes back to ViewModel's DataTable Property.

The Datatable property is populated at runtime as:
C#:
class EmployeeViewModel : INotifyPropertyChanged
{



    private DataTable _dt = new DataTable();

    public event PropertyChangedEventHandler PropertyChanged;
    
    public DataTable _Table
    {
        get
        {
            return _dt;
        }
        set
        {
            _dt = value;
            OnPropertyChanges(nameof(_Table));
        }
    }

    private void OnPropertyChanges(String propertyName)
    {

        var h = PropertyChanged;
        if (h != null)
            h(this, new PropertyChangedEventArgs(propertyName));
        //   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }

    public EmployeeViewModel()
    {
        
        ReadData();
    }

    

  
    public void ReadData()
    {

        string cs = ConfigurationManager.ConnectionStrings["Employee.mdf"].ConnectionString;
        using (SqlConnection con = new SqlConnection(cs))
        {
            SqlDataAdapter da = new SqlDataAdapter("Select * from tblStudents", con);
            DataSet set = new DataSet();
            da.Fill(set, "Students");
            _Table = set.Tables[0];

        }
    }
}

The custom usercontrol named StudentsGrid is just a datagrid inside a grid:
C#:
<UserControl x:Class="ADO.Net_Practice.StudentsGrid"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:ADO.Net_Practice"
         mc:Ignorable="d"
         d:DesignHeight="450" d:DesignWidth="800">
<Grid>
    <DataGrid ItemsSource="{Binding StudentsTable, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:StudentsGrid}}, Mode=TwoWay}" Margin="10,10,0,0" HorizontalAlignment="Center" VerticalAlignment="Top" MinHeight="200"/>           
</Grid>
and this is the code-behind of above xaml where dependency property is registered:
C#:
    /// <summary>
    /// Interaction logic for StudentsGrid.xaml
    /// </summary>
    public partial class StudentsGrid : UserControl
    {
        public StudentsGrid()
        {
            InitializeComponent();
        }



        public DataTable StudentsTable
        {
            get { return (DataTable)GetValue(StudentsTableProperty); }
            set { SetValue(StudentsTableProperty, value); }
        }

        // Using a DependencyProperty as the backing store for StudentsTable.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StudentsTableProperty =
            DependencyProperty.Register("StudentsTable", typeof(DataTable), typeof(StudentsGrid), new PropertyMetadata(new DataTable(), StudentsTablePropertyChanged));

        private bool _updating;

        private static void StudentsTablePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var st = d as StudentsGrid;
            if(st!=null)
            {
                st.DataChanged();
            }
        }

        private void DataChanged()
        {
            if (_updating)
                return;
            _updating = true;
            
            _updating = false;
        }
    }
The final is MainWindow which binds the StudentsTable property to the custom user control:
C#:
<Grid>
        
        <local:StudentsGrid StudentsTable="{Binding _Table, Mode=TwoWay}"/>

</Grid>
The datacontext of mainWindow is set in code-behind's constructor as:
C#:
DataContext = new EmployeeViewModel();
This is the result I get:
UKIgP.png
 
Solution
I'm seeing the data updated in the DataTable:
C#:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SimpleWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public DataTable MyTable { get; set; }

        public MainWindow()
        {
            InitializeComponent()...
I'm seeing the data updated in the DataTable:
C#:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SimpleWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public DataTable MyTable { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            MyTable = new DataTable();
            MyTable.Columns.Add("Name");
            MyTable.Rows.Add("Hans Gruber");
            MyTable.Rows.Add("Roy Rogers");

            MyTable.RowChanged += (o, e) => ShowRows();

            DataContext = this;
        }

        void ShowRows()
        {
            var sb = new StringBuilder();
            foreach (DataRow row in MyTable.Rows)
            {
                sb.Append(row["Name"].ToString());
                sb.Append(", ");
            }
            MessageBox.Show(sb.ToString());
        }
    }
}

C#:
<Window x:Class="SimpleWPF.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:SimpleWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding MyTable, Mode=TwoWay}" />
    </Grid>
</Window>

Change "Roy Rogers" to "John McClane" and see the message box pop up with the values from the data table.
 
Solution
Yes, I am adding rows in the constructor the same way your data adapter would be adding rows when Fill() is called. The point of the code is that the the two way binding does update the row after editing. That's why I said to go modify the row that says "Roy Rogers" and notice that the RowChanged event is fired and and when it iterates over all the rows, it finds the new data that was entered by the user. This would suggest that the two way binding works.
 
Read more about DataTable events in the MS documentation. There may be some earlier events that maybe fired where the data will be available. I chose that specific event because of the description for that specific event says that it is fired when the changes to the row have been successfully applied.
 
I've never used a DataTable in WPF. I'm kind off surprised that the two-way binding is not putting changes back into the DataTable. I usually expose enumerables and collections of objects. With two-way binding, I see the changes applied to the objects in my enumerables and collections.
 
I've never used a DataTable in WPF. I'm kind off surprised that the two-way binding is not putting changes back into the DataTable. I usually expose enumerables and collections of objects. With two-way binding, I see the changes applied to the objects in my enumerables and collections.
I have used observable collection and that notifies absolutely fine. I was curious to know why it wouldn't be same with dataTable?
 
I'm seeing the data updated in the DataTable:
C#:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SimpleWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public DataTable MyTable { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            MyTable = new DataTable();
            MyTable.Columns.Add("Name");
            MyTable.Rows.Add("Hans Gruber");
            MyTable.Rows.Add("Roy Rogers");

            MyTable.RowChanged += (o, e) => ShowRows();

            DataContext = this;
        }

        void ShowRows()
        {
            var sb = new StringBuilder();
            foreach (DataRow row in MyTable.Rows)
            {
                sb.Append(row["Name"].ToString());
                sb.Append(", ");
            }
            MessageBox.Show(sb.ToString());
        }
    }
}

C#:
<Window x:Class="SimpleWPF.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:SimpleWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding MyTable, Mode=TwoWay}" />
    </Grid>
</Window>

Change "Roy Rogers" to "John McClane" and see the message box pop up with the values from the data table.
RowChanged event is what I haven't implemented yet. Will do that and let you know. Thanks for the suggestion.
 
I'm seeing the data updated in the DataTable:
C#:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SimpleWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public DataTable MyTable { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            MyTable = new DataTable();
            MyTable.Columns.Add("Name");
            MyTable.Rows.Add("Hans Gruber");
            MyTable.Rows.Add("Roy Rogers");

            MyTable.RowChanged += (o, e) => ShowRows();

            DataContext = this;
        }

        void ShowRows()
        {
            var sb = new StringBuilder();
            foreach (DataRow row in MyTable.Rows)
            {
                sb.Append(row["Name"].ToString());
                sb.Append(", ");
            }
            MessageBox.Show(sb.ToString());
        }
    }
}

C#:
<Window x:Class="SimpleWPF.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:SimpleWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding MyTable, Mode=TwoWay}" />
    </Grid>
</Window>

Change "Roy Rogers" to "John McClane" and see the message box pop up with the values from the data table.
Actually, you are adding rows from runtime windows constructor in c# which works fine but my problem is when I changed the data in the DataGrid(On the UI) itself, it is not notified. Why are changes in UI DataGrid not detecting in the ViewModel? I have binding set as two-way.
 
Yes, I am adding rows in the constructor the same way your data adapter would be adding rows when Fill() is called. The point of the code is that the the two way binding does update the row after editing. That's why I said to go modify the row that says "Roy Rogers" and notice that the RowChanged event is fired and and when it iterates over all the rows, it finds the new data that was entered by the user. This would suggest that the two way binding works.
Yes it works. Thanks a lot. RowChanged event is subscribed to the DataTable and changes in rows are seen in my view Model.
So, what I can understand from this is until row changed event is triggered we can't view Grid changes in the DataTable?
 
Back
Top Bottom