具有 TrulyObservableCollection 的 WPF Datagrid 失去对编辑的关注
WPF Datagrid with TrulyObservableCollection loses focus on edit
您好,我有一个与 TrulyObservableCollection
绑定的 WPF 数据网格,每当我尝试通过文本输入编辑单元格时,数据网格单元格在输入每个键后都会失去焦点,
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems)
: this()
{
foreach (var item in pItems) {
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null) {
foreach (Object item in e.NewItems) {
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null) {
foreach (Object item in e.OldItems) {
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
try {
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
catch {
// ignored
}
}
}
下面是我的 ViewModel,绑定到 Datagrid,
public TrulyObservableCollection<SubLotModel> SubLotCollection
{
get { return _subLotCollection; }
set
{
_subLotCollection = value;
NotifyOfPropertyChange(() => SubLotCollection);
SerialNumberAdded = _subLotCollection.Count;
}
}
在 Datagrid 中,我有一个文本框列,它绑定到 SublotCollection 中的数量 属性,
<DataGridTemplateColumn Header="Quantity">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Quantity, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
只要我在 texbox 列中键入有效键,单元格就会失去焦点。
诊断
正如我在评论中提到的,您的编辑失去焦点的原因是这一系列事件:
- 你输入了一个字母,新文本被推送到视图模型,因为
UpdateSourceTrigger=PropertyChanged
- 您的视图模型上的
Quantity
属性 已更新并引发 PropertyChanged
事件
- 您的
TrulyObservableCollection
处理 PropertyChanged
事件并因此引发 CollectionChanged
事件并将操作设置为 NotifyCollectionChangedAction.Reset
DataGrid
旨在在这种情况下重建自身,因此每个单元格都是从头开始构建(重新模板化),最终有一个全新的 TextBox
一个你曾经输入的字母
焦点没有返回到新的 TextBox
,因为 DataGrid
并没有真正意识到它的存在(因为它是在 DataTemplate
中定义的)。我认为即使您使用 DataGridTextColumn
也不会恢复焦点(但您可以检查)。
所以这里的核心问题是NotifyCollectionChangedAction.Reset
。当一件物品中只有一个 属性 被改变时,声称集合发生了巨大变化似乎完全是矫枉过正。
解决方案
更合理的选择是用 NotifyCollectionChangedAction.Replace
提高 CollectionChanged
,包括哪个项目被“替换”的信息。连同一些改进,这里有一个集合实现,它应该像您期望的那样运行(我测试它时确实如此):
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged inpc in e.NewItems)
inpc.PropertyChanged += Item_PropertyChanged;
}
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged inpc in e.OldItems)
inpc.PropertyChanged -= Item_PropertyChanged;
}
}
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = IndexOf((T)sender);
var args = new NotifyCollectionChangedEventArgs(
action: NotifyCollectionChangedAction.Replace,
newItem: sender,
oldItem: sender,
index: index);
//no need to call the override since we've already
//subscribed to that item's PropertyChanged event
base.OnCollectionChanged(args);
}
}
您好,我有一个与 TrulyObservableCollection
绑定的 WPF 数据网格,每当我尝试通过文本输入编辑单元格时,数据网格单元格在输入每个键后都会失去焦点,
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems)
: this()
{
foreach (var item in pItems) {
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null) {
foreach (Object item in e.NewItems) {
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null) {
foreach (Object item in e.OldItems) {
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
try {
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
catch {
// ignored
}
}
}
下面是我的 ViewModel,绑定到 Datagrid,
public TrulyObservableCollection<SubLotModel> SubLotCollection
{
get { return _subLotCollection; }
set
{
_subLotCollection = value;
NotifyOfPropertyChange(() => SubLotCollection);
SerialNumberAdded = _subLotCollection.Count;
}
}
在 Datagrid 中,我有一个文本框列,它绑定到 SublotCollection 中的数量 属性,
<DataGridTemplateColumn Header="Quantity">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Quantity, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
只要我在 texbox 列中键入有效键,单元格就会失去焦点。
诊断
正如我在评论中提到的,您的编辑失去焦点的原因是这一系列事件:
- 你输入了一个字母,新文本被推送到视图模型,因为
UpdateSourceTrigger=PropertyChanged
- 您的视图模型上的
Quantity
属性 已更新并引发PropertyChanged
事件 - 您的
TrulyObservableCollection
处理PropertyChanged
事件并因此引发CollectionChanged
事件并将操作设置为NotifyCollectionChangedAction.Reset
DataGrid
旨在在这种情况下重建自身,因此每个单元格都是从头开始构建(重新模板化),最终有一个全新的TextBox
一个你曾经输入的字母
焦点没有返回到新的 TextBox
,因为 DataGrid
并没有真正意识到它的存在(因为它是在 DataTemplate
中定义的)。我认为即使您使用 DataGridTextColumn
也不会恢复焦点(但您可以检查)。
所以这里的核心问题是NotifyCollectionChangedAction.Reset
。当一件物品中只有一个 属性 被改变时,声称集合发生了巨大变化似乎完全是矫枉过正。
解决方案
更合理的选择是用 NotifyCollectionChangedAction.Replace
提高 CollectionChanged
,包括哪个项目被“替换”的信息。连同一些改进,这里有一个集合实现,它应该像您期望的那样运行(我测试它时确实如此):
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged inpc in e.NewItems)
inpc.PropertyChanged += Item_PropertyChanged;
}
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged inpc in e.OldItems)
inpc.PropertyChanged -= Item_PropertyChanged;
}
}
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = IndexOf((T)sender);
var args = new NotifyCollectionChangedEventArgs(
action: NotifyCollectionChangedAction.Replace,
newItem: sender,
oldItem: sender,
index: index);
//no need to call the override since we've already
//subscribed to that item's PropertyChanged event
base.OnCollectionChanged(args);
}
}