Collection of DataGrid and Observable in WPF

I have a datagrid as shown below in my WPF application.

<Window x:Class="MyApp.TestWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <DataGrid x:Name="dgTest" ItemsSource="{Binding TestSource}" AutoGenerateColumns="False" > <DataGrid.Columns> <DataGridTemplateColumn Width="125" > <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBox Text="{Binding Column1}"></TextBox> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn Width="500" > <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBox Text="{Binding Column2}"></TextBox> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> <Button Click="SaveButton_Click">Save</Button> </Grid> </Window> 

I am linking it using the following code. Now my requirement is that when the user enters some text in these text fields inside the datagrid and clicks the "Save" button, he must update the database. How can I achieve this?

 namespace MyApp { public partial class TestWindow: Window { private ObservableCollection<Test> _testSource public ObservableCollection<Test> TestSource { get { return _testSource; } set { _testSource = value; OnPropertyChanged("TestSource"); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propName) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName)); } } public TestWindow() { InitializeComponent(); TestSource= new ObservableCollection<Test>(); string strConnString = Application.Current.Properties["connectionStr"].ToString(); SqlConnection con = new SqlConnection(strConnString); SqlCommand cmd = new SqlCommand("SELECT Column1,Column2 FROM MyTable", con); SqlDataAdapter da = new SqlDataAdapter(cmd); DataTable dtTest = new DataTable(); da.Fill(dtTest); foreach (DataRow row in dtTest) { Test cd = new Test(); cd.Column1 = row["Column1"].ToString(); cd.Column2 = row["Column2"].ToString(); TestSource.Add(cd); } this.DataContext = this; } private void SaveButton_Click(object sender, RoutedEventArgs e) { // here I need to get the updated ObservableCollection, but now it is showing old data foreach Test t in TestSource) { string a = t.Column1; string b = t.Column2; } } } public class Test: { public string Column1{ get; set; } public string Column2{ get; set; } } } 

thanks

+4
source share
2 answers

When creating your own user interface in the DataGridTemplateColumn (or user DataGrid.RowStyle ), the DataGrid changes the UpdateSourceTrigger (i.e. when the underlying data needs to be updated) for all bindings to Explicit , if you did not specify them yourself.

This "feature" is briefly described here: 5 Random Gotchas with WPF DataGrid , and although the author says that this happens regardless of whether you install UpdateSourceTrigger , the setup itself actually works (at least in .Net 4.0).

Use LostFocus to simulate the default behavior of a TextBox (and PropertyChanged on a CheckBox , etc.):

 ... <TextBox Text="{Binding Column1, UpdateSourceTrigger=LostFocus}"></TextBox> ... <TextBox Text="{Binding Column2, UpdateSourceTrigger=LostFocus}"></TextBox> ... 
+9
source

There are a few side notes that I would like to make.
1) You do not need to have a setter in the TestSource property. You set this value once and before setting the DataContext, so this is pointless.
2) You do not implement INotifyPropertyChanged in class Test
3) You load data into the user interface stream. This can cause the application to freeze (stop responding) while the data is loading.

Try updating the C # code below:

 namespace MyApp { public partial class TestWindow: Window { private ObservableCollection<Test> _testSource = new ObservableCollection<Test>(); public TestWindow() { InitializeComponent(); //NOTE: this blocks the UI thread. Slow DB/Network will freeze the App while we wait. // This should be done on a background thread. string strConnString = Application.Current.Properties["connectionStr"].ToString(); SqlConnection con = new SqlConnection(strConnString); SqlCommand cmd = new SqlCommand("SELECT Column1,Column2 FROM MyTable", con); SqlDataAdapter da = new SqlDataAdapter(cmd); DataTable dtTest = new DataTable(); da.Fill(dtTest); foreach (DataRow row in dtTest) { Test cd = new Test(); cd.Column1 = row["Column1"].ToString(); cd.Column2 = row["Column2"].ToString(); TestSource.Add(cd); } this.DataContext = this; } public ObservableCollection<Test> TestSource { get { return _testSource; } } private void SaveButton_Click(object sender, RoutedEventArgs e) { var rowIdx = 0; foreach(var t in TestSource) { string a = t.Column1; string b = t.Column2; Console.WriteLine("Row {0}, col1='{1}', col2='{2}'", rowIdx++, a, b); } } } public sealed class Test : INotifyPropertyChanged { private string _column1; private string _column2; public string Column1 { get{return _column1;} set { if(_column1!=value) { _column1 = value; OnPropertyChanged("Column1"); } } } public string Column2 { get{return _column2;} set { if(_column2!=value) { _column2 = value; OnPropertyChanged("Column2"); } } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propName) { var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propName)); } } } } 

You can also upgrade the binding to twoway, but I think this is the default value.

 <TextBox Text="{Binding Column1, Mode=TwoWay}" /> 
+2
source

All Articles