WPF 中的 DataGrid 和 Observable 集合
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14193507/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
DataGrid and Observable Collection in WPF
提问by Dev
I have a datagrid like below in my WPF application.
我的 WPF 应用程序中有一个如下所示的数据网格。
<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
谢谢
回答by Sphinxxx
When creating your own UI in DataGridTemplateColumn(or a custom DataGrid.RowStylefor that matter), the DataGrid changes the UpdateSourceTrigger(i.e. when the underlying data should be updated) on all your bindings to Explicitif you didn't specify them yourself.
在DataGridTemplateColumn(或就此而言的自定义DataGrid.RowStyle)中创建您自己的 UIUpdateSourceTrigger时,Explicit如果您没有自己指定它们,DataGrid 会将所有绑定上的(即底层数据应该何时更新)更改为。
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 UpdateSourceTriggeryourself, setting it yourself does actually work (at least in .Net 4.0).
此处简要描述了该“功能”:5 Random Gotchas with the WPF DataGrid,尽管作者说无论您是否设置UpdateSourceTrigger自己都会发生这种情况,但您自己设置它确实有效(至少在 .Net 4.0 中)。
Use LostFocusto mimic the default TextBoxbehavior (and PropertyChangedon CheckBoxetc):
使用LostFocus模仿默认TextBox行为(和PropertyChanged对CheckBox等):
...
<TextBox Text="{Binding Column1, UpdateSourceTrigger=LostFocus}"></TextBox>
...
<TextBox Text="{Binding Column2, UpdateSourceTrigger=LostFocus}"></TextBox>
...
回答by Lee Campbell
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.
我想做一些旁注。
1) 你不需要在你的 TestSource 属性上有一个 setter。您设置一次该值,并且在设置 DataContext 之前,因此这是毫无意义的。
2) 您没有在 Test 类上实现 INotifyPropertyChanged
3) 您在 UI 线程上加载数据。这可能会导致应用程序在加载数据时冻结(变得无响应)。
Try updating the C# code to the below:
尝试将 C# 代码更新为以下内容:
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.
您可能还想将 Binding 更新为 twoway,但我认为这是默认设置。
<TextBox Text="{Binding Column1, Mode=TwoWay}" />

