Dev
Dev

Reputation: 329

DataGrid and Observable Collection in WPF

I have a datagrid like 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 binding it using the following code. Now my requirement is when the user enters some text into these textboxes inside datagrid and click on save button, it should 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

Upvotes: 4

Views: 14631

Answers (2)

Lee Campbell
Lee Campbell

Reputation: 10783

There are a couple of side notes I would like to make.
1) You dont need to have a setter on your TestSource property. You set this value once, and before the DataContext is set, so this is pointless.
2) You dont implement INotifyPropertyChanged on the Test class
3) You load the data on the UI thread. This can cause the App to freeze (become unresponsive) while the Data is being loaded.

Try updating the C# code to the 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 may also want to update the Binding to twoway, however I do think this is the default.

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

Upvotes: 2

Sphinxxx
Sphinxxx

Reputation: 13017

When creating your own UI in DataGridTemplateColumn (or a custom DataGrid.RowStyle for that matter), the DataGrid changes the UpdateSourceTrigger (i.e. when the underlying data should be updated) on all your bindings to Explicit if you didn't specify them yourself.

That "feature" is described briefly here: 5 Random Gotchas with the WPF DataGrid, and even though the author says this happens whether or not you set the UpdateSourceTrigger yourself, setting it yourself does actually work (at least in .Net 4.0).

Use LostFocus to mimic the default TextBox behavior (and PropertyChanged on CheckBox etc):

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

Upvotes: 9

Related Questions