【问题标题】:What is the best way to force the WPF DataGrid to add a specific new item?强制 WPF DataGrid 添加特定新项目的最佳方法是什么?
【发布时间】:2012-04-14 15:47:10
【问题描述】:

我在 WPF 应用程序中有一个 DataGrid,它的 ItemsSource 有一个我编写的自定义集合。集合强制其所有项目满足特定要求(即它们必须介于某些最小值和最大值之间)。

集合的类签名是:

   public class CheckedObservableCollection<T> : IList<T>, ICollection<T>, IList, ICollection,
                                            INotifyCollectionChanged
                                             where T : IComparable<T>, IEditableObject, ICloneable, INotifyPropertyChanged

我希望能够使用DataGrid 功能,在该功能中,对DataGrid 的最后一行进行编辑会导致在ItemsSource 的末尾添加一个新项目。

不幸的是,DataGrid 只是添加了一个使用默认构造函数创建的新项目。因此,在添加新项目时,DataGrid 间接(通过其 ItemCollection 这是一个密封类)声明:

ItemsSource.Add(new T())

其中 T 是 CheckedObservableCollection 中元素的类型。我希望网格改为添加一个不同的 T,它满足对集合施加的约束。

我的问题是:有内置的方法可以做到这一点吗?有人已经这样做了吗?最好的(最简单、最快速的编码;性能不是问题)方法是什么?

目前我只是派生DataGrid 来用我自己的覆盖OnExecutedBeginEdit 函数,如下所示:

public class CheckedDataGrid<T> : DataGrid where T : IEditableObject, IComparable<T>, INotifyPropertyChanged, ICloneable
{
  public CheckedDataGrid() : base() { }

  private IEditableCollectionView EditableItems {
     get { return (IEditableCollectionView)Items; }
  }

  protected override void OnExecutedBeginEdit(ExecutedRoutedEventArgs e) {
     try {
        base.OnExecutedBeginEdit(e);
     } catch (ArgumentException) {
        var source = ItemsSource as CheckedObservableCollection<T>;
        source.Add((T)source.MinValue.Clone());
        this.Focus();
     }
  }
}

其中MinValue 是集合中允许的最小项目。

我不喜欢这个解决方案。如果你们有任何建议,我将非常感激!

谢谢

【问题讨论】:

标签: wpf datagrid datacontract itemssource type-constraints


【解决方案1】:

这个问题现在可以在 4.5 下使用 DataGridAddingNewItem 事件半解决。 Here is my answer to a similar question.

我通过使用 DataGrid 的 AddingNewItem 事件解决了这个问题。这几乎是entirely undocumented event 不仅告诉您正在添加新项目,而且[允许您选择要添加的项目][2]。 AddingNewItem 先触发; EventArgsNewItem 属性就是 null

即使您为事件提供处理程序,DataGrid 也会拒绝允许用户添加 如果类没有默认构造函数,则为行。然而,奇怪的是(但幸运的是)如果你确实有一个,并且设置了 AddingNewItemEventArgsNewItem 属性,它将永远不会被调用。

如果您选择这样做,您可以使用[Obsolete("Error", true)][EditorBrowsable(EditorBrowsableState.Never)] 等属性来确保没有人调用构造函数。你也可以让构造函数体抛出异常

反编译控件让我们看看里面发生了什么......

【讨论】:

  • 遗憾的是,我的解决方案(公认的答案)仍然是唯一符合我需求的静态类型通用解决方案。事件最新版本的 WPF 通过对象投射 DataGrid 项目并破坏虚拟化性能。我知道我说过性能无关紧要,但我改变了主意。
【解决方案2】:

对于任何感兴趣的人,我最终通过从BindingList&lt;T&gt;而不是ObservableCollection&lt;T&gt;派生来解决问题,在常规DataGrid中使用我的派生类作为ItemsSource

   public class CheckedBindingList<T> : BindingList<T>, INotifyPropertyChanged where T : IEditableObject, INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private Predicate<T> _check;
  private DefaultProvider<T> _defaultProvider;

  public CheckedBindingList(Predicate<T> check, DefaultProvider<T> defaultProvider) {
     if (check == null)
        throw new ArgumentNullException("check cannot be null");
     if (defaultProvider != null && !check(defaultProvider()))
        throw new ArgumentException("defaultProvider does not pass the check");

     _check = check;
     _defaultProvider = defaultProvider;
  }

  /// <summary>
  /// Predicate the check item in the list against.
  /// All items in the list must satisfy Check(item) == true
  /// </summary>
  public Predicate<T> Check {
     get { return _check; }

     set {
        if (value != _check) {
           RaiseListChangedEvents = false;

           int i = 0;
           while (i < Items.Count)
              if (!value(Items[i]))
                 ++i;
              else
                 RemoveAt(i);

           RaiseListChangedEvents = true;
           SetProperty(ref _check, value, "Check");

           ResetBindings();
        }
     }
  }

  public DefaultProvider<T> DefaultProvider {
     get { return _defaultProvider; }
     set {
        if (!_check(value()))
           throw new ArgumentException("value does not pass the check");
     }
  }

  protected override void OnAddingNew(AddingNewEventArgs e) {
     if (e.NewObject != null)
        if (!_check((T)e.NewObject)) {
           if (_defaultProvider != null)
              e.NewObject = _defaultProvider();
           else
              e.NewObject = default(T);
        }

     base.OnAddingNew(e);
  }

  protected override void OnListChanged(ListChangedEventArgs e) {
     switch (e.ListChangedType) {
        case (ListChangedType.ItemAdded):
           if (!_check(Items[e.NewIndex])) {
              RaiseListChangedEvents = false;
              RemoveItem(e.NewIndex);
              if (_defaultProvider != null)
                 InsertItem(e.NewIndex, _defaultProvider());
              else
                 InsertItem(e.NewIndex, default(T));
              RaiseListChangedEvents = true;
           }
           break;
        case (ListChangedType.ItemChanged):
           if (e.NewIndex >= 0 && e.NewIndex < Items.Count) {
              if (!_check(Items[e.NewIndex])) {
                 Items[e.NewIndex].CancelEdit();
                 throw new ArgumentException("item did not pass the check");
              }
           }
           break;
        default:
           break;
     }

     base.OnListChanged(e);
  }

  protected void SetProperty<K>(ref K field, K value, string name) {
     if (!EqualityComparer<K>.Default.Equals(field, value)) {
        field = value;
        if (PropertyChanged != null)
           PropertyChanged(this, new PropertyChangedEventArgs(name));
     }
  }
}

这个类不完整,但上面的实现足以验证静态类型(不是通过反射或 DLR 构建)对象或值类型的列表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多