如何使用 DataGrid.CanUserAddRows = true 的工厂

发布于 2024-10-08 08:05:16 字数 202 浏览 7 评论 0原文

我想使用 DataGrid.CanUserAddRows = true 功能。不幸的是,它似乎只适用于具有默认构造函数的具体类。我的业务对象集合不提供默认构造函数。

我正在寻找一种注册工厂的方法,该工厂知道如何为 DataGrid 创建对象。我查看了 DataGrid 和 ListCollectionView,但它们似乎都不支持我的场景。

I would like to use the DataGrid.CanUserAddRows = true feature. Unfortunately, it seems to work only with concrete classes which have a default constructor. My collection of business objects doesn't provide a default constructor.

I'm looking for a way to register a factory that knows how to create the objects for the DataGrid. I had a look at the DataGrid and the ListCollectionView but none of them seems to support my scenario.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

梦里人 2024-10-15 08:05:16

问题:

“我正在寻找一种注册工厂的方法,该工厂知道如何为 DataGrid 创建对象”。 (因为我的业务对象集合不提供默认构造函数。)

症状:

如果我们设置 DataGrid.CanUserAddRows = true,然后将项目集合绑定到 DataGrid,但该项目不有默认构造函数,则 DataGrid 不会显示“新项目行”。

原因:

当项目集合绑定到任何 WPF ItemControl 时,WPF 会将该集合包装在以下任一位置:

  1. a BindingListCollectionView(当绑定的集合是 BindingList 时)。 BindingListCollectionView 实现 IEditableCollectionView 但不实现 <代码>IEditableCollectionViewAddNewItem。

  2. a ListCollectionView(当绑定的集合是任何其他集合时) 。 ListCollectionView 实现 IEditableCollectionViewAddNewItem (因此 IEditableCollectionView)。

对于选项 2),DataGrid 将新项目的创建委托给 ListCollectionViewListCollectionView 在内部测试默认构造函数是否存在,如果不存在则禁用 AddNew。以下是使用 DotPeek 的 ListCollectionView 的相关代码。

public bool CanAddNewItem (method from IEditableCollectionView)
{
  get
  {
    if (!this.IsEditingItem)
      return !this.SourceList.IsFixedSize;
    else
      return false;
  }
}

bool CanConstructItem
{
  private get
  {
    if (!this._isItemConstructorValid)
      this.EnsureItemConstructor();
    return this._itemConstructor != (ConstructorInfo) null;
  }
}

似乎没有一种简单的方法可以覆盖这种行为。

对于选项 1),情况要好得多。 DataGrid 将新项目的创建委托给 BindingListView,而 BindingListView 又委托给 BindingListBindingList 还会检查默认构造函数是否存在,但幸运的是 BindingList 还允许客户端设置 AllowNew 属性并附加一个事件处理程序以提供一个新项目。稍后查看解决方案,但这里是 BindingList 中的相关代码

public bool AllowNew
{
  get
  {
    if (this.userSetAllowNew || this.allowNew)
      return this.allowNew;
    else
      return this.AddingNewHandled;
  }
  set
  {
    bool allowNew = this.AllowNew;
    this.userSetAllowNew = true;
    this.allowNew = value;
    if (allowNew == value)
      return;
    this.FireListChanged(ListChangedType.Reset, -1);
  }
}

非解决方案:

  • DataGrid 支持(不可用)

期望 DataGrid 允许客户端附加一个回调,DataGrid 通过该回调请求默认的新项目,就像上面的 BindingList 一样。这将使客户在需要时能够首先创建新项目。

不幸的是,即使在 .NET 4.5 中,DataGrid 也不直接支持这一点。

.NET 4.5 似乎确实有一个以前不可用的新事件“AddingNewItem”,但这只会让您知道正在添加新项目。

解决方法:

  • 由工具创建的业务对象相同的程序集:使用分部类

这种情况似乎不太可能,但想象一下实体框架创建的实体类没有默认构造函数(不太可能,因为它们不可序列化),那么我们可以简单地创建一个具有默认构造函数的分部类构造函数。问题解决了。

  • 业务对象位于另一个程序集中,并且未密封:创建业务对象的超类型。

这里我们可以继承业务对象类型并添加默认构造函数。

最初这似乎是一个好主意,但转念一想,这可能需要比必要的更多的工作,因为我们需要将业务层生成的数据复制到业务对象的超类型版本中。

我们需要像这样的代码

class MyBusinessObject : BusinessObject
{
    public MyBusinessObject(BusinessObject bo){ ... copy properties of bo }
    public MyBusinessObject(){}
}

,然后需要一些 LINQ 在这些对象的列表之间进行投影。

  • 业务对象位于另一个程序集中,并且是密封的(或不密封的):封装业务对象。

这要容易得多

class MyBusinessObject
{
    public BusinessObject{ get; private set; }

    public MyBusinessObject(BusinessObject bo){ BusinessObject = bo;  }
    public MyBusinessObject(){}
}

现在我们需要做的就是使用一些 LINQ 在这些对象的列表之间进行投影,然后绑定到 DataGrid 中的 MyBusinessObject.BusinessObject。不需要混乱的属性包装或值复制。

解决方案:(找到一个)

  • 使用 BindingList

如果我们将业务对象集合包装在 BindingList 中,然后将 DataGrid 绑定到此,用几行代码我们的问题就解决了,DataGrid 将适当地显示一个新的项目行。

public void BindData()
{
   var list = new BindingList<BusinessObject>( GetBusinessObjects() );
   list.AllowNew = true;
   list.AddingNew += (sender, e) => 
       {e.NewObject = new BusinessObject(... some default params ...);};
}

其他解决方案

  • 在现有集合类型之上实现 IEditableCollectionViewAddNewItem。可能有很多工作。
  • 继承自 ListCollectionView 并覆盖功能。我的尝试取得了部分成功,可能需要付出更多努力才能完成。

The problem:

"I'm looking for a way to register a factory that knows how to create the objects for the DataGrid". (Because my collection of business objects doesn't provide a default constructor.)

The symptoms:

If we set DataGrid.CanUserAddRows = true and then bind a collection of items to the DataGrid where the item doesn't have a default constructor, then the DataGrid doesn't show a 'new item row'.

The causes:

When a collection of items is bound to any WPF ItemControl, WPF wraps the collection in either:

  1. a BindingListCollectionView when the collection being bound is a BindingList<T>. BindingListCollectionView implements IEditableCollectionView but doesn't implement IEditableCollectionViewAddNewItem.

  2. a ListCollectionView when the collection being bound is any other collection. ListCollectionView implements IEditableCollectionViewAddNewItem (and hence IEditableCollectionView).

For option 2) the DataGrid delegates creation of new items to the ListCollectionView. ListCollectionView internally tests for the existence of a default constructor and disables AddNew if one doesn't exist. Here's the relevant code from ListCollectionView using DotPeek.

public bool CanAddNewItem (method from IEditableCollectionView)
{
  get
  {
    if (!this.IsEditingItem)
      return !this.SourceList.IsFixedSize;
    else
      return false;
  }
}

bool CanConstructItem
{
  private get
  {
    if (!this._isItemConstructorValid)
      this.EnsureItemConstructor();
    return this._itemConstructor != (ConstructorInfo) null;
  }
}

There doesn't seem to be an easy way to override this behaviour.

For option 1) the situation is a lot better. The DataGrid delegates creation of new items to the BindingListView, which in turn delegates to BindingList. BindingList<T> also checks for the existence of a default constructor, but fortunately BindingList<T> also allows the client to set the AllowNew property and attach an event handler for supplying a new item. See the solution later, but here's the relevant code in BindingList<T>

public bool AllowNew
{
  get
  {
    if (this.userSetAllowNew || this.allowNew)
      return this.allowNew;
    else
      return this.AddingNewHandled;
  }
  set
  {
    bool allowNew = this.AllowNew;
    this.userSetAllowNew = true;
    this.allowNew = value;
    if (allowNew == value)
      return;
    this.FireListChanged(ListChangedType.Reset, -1);
  }
}

Non-solutions:

  • Support by DataGrid (not available)

It would reasonable to expect the DataGrid to allow the client to attach a callback, through which the DataGrid would request a default new item, just like BindingList<T> above. This would give the client the first crack at creating a new item when one is required.

Unfortunately this isn't supported directly from the DataGrid, even in .NET 4.5.

.NET 4.5 does appear to have a new event 'AddingNewItem' that wasn't available previously, but this only lets you know a new item is being added.

Work arounds:

  • Business object created by a tool in the same assembly: use a partial class

This scenario seems very unlikely, but imagine that Entity Framework created its entity classes with no default constructor (not likely since they wouldn't be serializable), then we could simply create a partial class with a default constructor. Problem solved.

  • Business object is in another assembly, and isn't sealed: create a super-type of the business object.

Here we can inherit from the business object type and add a default constructor.

This initially seemed like a good idea, but on second thoughts this may require more work than is necessary because we need to copy data generated by the business layer into our super-type version of the business object.

We would need code like

class MyBusinessObject : BusinessObject
{
    public MyBusinessObject(BusinessObject bo){ ... copy properties of bo }
    public MyBusinessObject(){}
}

And then some LINQ to project between lists of these objects.

  • Business object is in another assembly, and is sealed (or not): encapsulate the business object.

This is much easier

class MyBusinessObject
{
    public BusinessObject{ get; private set; }

    public MyBusinessObject(BusinessObject bo){ BusinessObject = bo;  }
    public MyBusinessObject(){}
}

Now all we need to do is use some LINQ to project between lists of these objects, and then bind to MyBusinessObject.BusinessObject in the DataGrid. No messy wrapping of properties or copying of values required.

The solution: (hurray found one)

  • Use BindingList<T>

If we wrap our collection of business objects in a BindingList<BusinessObject> and then bind the DataGrid to this, with a few lines of code our problem is solved and the DataGrid will appropriately show a new item row.

public void BindData()
{
   var list = new BindingList<BusinessObject>( GetBusinessObjects() );
   list.AllowNew = true;
   list.AddingNew += (sender, e) => 
       {e.NewObject = new BusinessObject(... some default params ...);};
}

Other solutions

  • implement IEditableCollectionViewAddNewItem on top of an existing collection type. Probably a lot of work.
  • inherit from ListCollectionView and override functionality. I was partially successful trying this, probably can be done with more effort.
能怎样 2024-10-15 08:05:16

我找到了解决这个问题的另一种方法。就我而言,我的对象需要使用工厂进行初始化,并且实际上没有任何方法可以解决这个问题。

我无法使用 BindingList,因为我的集合必须支持分组、排序和过滤,而 BindingList 不支持。

我通过使用 DataGrid 的 AddingNewItem 事件解决了该问题。这几乎完全没有记录的事件不仅告诉您正在添加一个新项目,而且允许让您选择要添加的项目AddingNewItem 在其他任何事情之前触发; EventArgsNewItem 属性只是 null

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

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

反编译控件让我们可以看到其中发生了什么。

private object AddNewItem()
{
  this.UpdateNewItemPlaceholder(true);
  object newItem1 = (object) null;
  IEditableCollectionViewAddNewItem collectionViewAddNewItem = (IEditableCollectionViewAddNewItem) this.Items;
  if (collectionViewAddNewItem.CanAddNewItem)
  {
    AddingNewItemEventArgs e = new AddingNewItemEventArgs();
    this.OnAddingNewItem(e);
    newItem1 = e.NewItem;
  }
  object newItem2 = newItem1 != null ? collectionViewAddNewItem.AddNewItem(newItem1) : this.EditableItems.AddNew();
  if (newItem2 != null)
    this.OnInitializingNewItem(new InitializingNewItemEventArgs(newItem2));
  CommandManager.InvalidateRequerySuggested();
  return newItem2;
}

正如我们所看到的,在 4.5 版本中,DataGrid 确实使用了 AddNewItemCollectionListView.CanAddNewItem 的内容很简单:

public bool CanAddNewItem
{
  get
  {
    if (!this.IsEditingItem)
      return !this.SourceList.IsFixedSize;
    else
      return false;
  }
}

因此,这并不能解释为什么我们仍然需要一个构造函数(即使它是一个虚拟的)才能显示添加行选项。我相信答案在于一些使用 CanAddNew 而不是 CanAddNewItem 确定 NewItemPlaceholder 行的可见性的代码。这可能被认为是某种错误。

I've found another solution to this problem. In my case, my objects need to be initialized using a factory, and there isn't really any way to get around that.

I couldn't use BindingList<T> because my collection must support grouping, sorting, and filtering, which BindingList<T> does not support.

I solved the problem by using DataGrid's AddingNewItem event. This almost entirely undocumented event not only tells you a new item is being added, but also allows lets you choose which item is being added. AddingNewItem fires before anything else; the NewItem property of the EventArgs is simply null.

Even if you provide a handler for the event, DataGrid will refuse to allow the user to add rows if the class doesn't have a default constructor. However, bizarrely (but thankfully) if you do have one, and set the NewItem property of the AddingNewItemEventArgs, it will never be called.

If you choose to do this, you can make use of attributes such as [Obsolete("Error", true)] and [EditorBrowsable(EditorBrowsableState.Never)] in order to make sure no one ever invokes the constructor. You can also have the constructor body throw an exception

Decompiling the control lets us see what's happening in there.

private object AddNewItem()
{
  this.UpdateNewItemPlaceholder(true);
  object newItem1 = (object) null;
  IEditableCollectionViewAddNewItem collectionViewAddNewItem = (IEditableCollectionViewAddNewItem) this.Items;
  if (collectionViewAddNewItem.CanAddNewItem)
  {
    AddingNewItemEventArgs e = new AddingNewItemEventArgs();
    this.OnAddingNewItem(e);
    newItem1 = e.NewItem;
  }
  object newItem2 = newItem1 != null ? collectionViewAddNewItem.AddNewItem(newItem1) : this.EditableItems.AddNew();
  if (newItem2 != null)
    this.OnInitializingNewItem(new InitializingNewItemEventArgs(newItem2));
  CommandManager.InvalidateRequerySuggested();
  return newItem2;
}

As we can see, in version 4.5, the DataGrid does indeed make use of AddNewItem. The contents of CollectionListView.CanAddNewItem are simply:

public bool CanAddNewItem
{
  get
  {
    if (!this.IsEditingItem)
      return !this.SourceList.IsFixedSize;
    else
      return false;
  }
}

So this doesn't explain why we we still need to have a constructor (even if it is a dummy) in order for the add row option to appear. I believe the answer lies in some code that determines the visibility of the NewItemPlaceholder row using CanAddNew rather than CanAddNewItem. This might be considered some sort of bug.

千寻… 2024-10-15 08:05:16

我查看了 IEditableCollectionViewAddNewItem 而且似乎正在添加此功能。

来自MSDN

IEditableCollectionViewAddNewItem
接口使能应用程序
开发人员指定什么类型
要添加到集合中的对象。这
接口扩展
IEditableCollectionView,这样你就可以
添加、编辑和删除项目
收藏。
IEditableCollectionViewAddNewItem 添加
AddNewItem 方法,它需要一个
添加到的对象
收藏。此方法在以下情况下很有用
您的集合和对象
想要添加一个或多个
以下特点:

  • CollectionView 中的对象是不同类型的。
  • 这些对象没有默认构造函数。
  • 该对象已存在。
  • 您想要向集合中添加一个 null 对象。

尽管在 Bea Stollnitz 博客中,您可以阅读以下内容

  • 当来源没有时无法添加新项目的限制
    默认构造函数很好
    被团队理解。 WPF 4.0 测试版 2
    有一个新功能给我们带来了
    距离找到解决方案又近了一步:
    介绍
    IEditableCollectionViewAddNewItem
    包含 AddNewItem 方法。你
    可以阅读MSDN文档了解
    这个功能。 MSDN 中的示例显示
    创建自己的时如何使用它
    自定义 UI 添加新项目(使用
    ListBox 用于显示数据和
    对话框输入新项目)。
    据我所知,DataGrid 没有
    但仍然使用这种方法(尽管
    100%确定有点困难
    因为Reflector不能反编译
    4.0 Beta 2 位)。

这个答案来自 2009 年,所以也许它现在可用于 DataGrid

I had a look at IEditableCollectionViewAddNewItem and it seems to be adding this functionality.

From MSDN

The IEditableCollectionViewAddNewItem
interface enables application
developers to specify what type of
object to add to a collection. This
interface extends
IEditableCollectionView, so you can
add, edit, and remove items in a
collection.
IEditableCollectionViewAddNewItem adds
the AddNewItem method, which takes an
object that is added to the
collection. This method is useful when
the collection and objects that you
want to add have one or more of the
following characteristics:

  • The objects in the CollectionView are different types.
  • The objects do not have a default constructor.
  • The object already exists.
  • You want to add a null object to the collection.

Although at Bea Stollnitz blog, you can read the following

  • The limitation of not being able to add a new item when the source has no
    default constructor is very well
    understood by the team. WPF 4.0 Beta 2
    has a new feature that brings us a
    step closer to having a solution: the
    introduction of
    IEditableCollectionViewAddNewItem
    containing the AddNewItem method. You
    can read the MSDN documentation about
    this feature. The sample in MSDN shows
    how to use it when creating your own
    custom UI to add a new item (using a
    ListBox to display the data and a
    dialog box to enter the new item).
    From what I can tell, DataGrid doesn’t
    yet use this method though (although
    it’s a bit hard to be 100% sure
    because Reflector doesn’t decompile
    4.0 Beta 2 bits).

That answer is from 2009 so maybe it's usable for the DataGrid now

玩物 2024-10-15 08:05:16

我建议最简单的方法是为您的类提供没有默认构造函数的包装器,其中将调用源类的构造函数。
例如,您的此类没有默认构造函数:

/// <summary>
/// Complicate class without default constructor.
/// </summary>
public class ComplicateClass
{
    public ComplicateClass(string name, string surname)
    {
        Name = name;
        Surname = surname;
    }

    public string Name { get; set; }
    public string Surname { get; set; }
}

为其编写一个包装器:

/// <summary>
/// Wrapper for complicated class.
/// </summary>
public class ComplicateClassWraper
{
    public ComplicateClassWraper()
    {
        _item = new ComplicateClass("def_name", "def_surname");
    }

    public ComplicateClassWraper(ComplicateClass item)
    {
        _item = item;
    }

    public ComplicateClass GetItem() { return _item; }

    public string Name
    {
        get { return _item.Name; }
        set { _item.Name = value; }
    }
    public string Surname
    {
        get { return _item.Surname; }
        set { _item.Surname = value; }
    }

    ComplicateClass _item;
}

Codebehind。
在您的 ViewModel 中,您需要为源集合创建包装器集合,它将处理数据网格中的项目添加/删除。

    public MainWindow()
    {
        // Prepare collection with complicated objects.
        _sourceCollection = new List<ComplicateClass>();
        _sourceCollection.Add(new ComplicateClass("a1", "b1"));
        _sourceCollection.Add(new ComplicateClass("a2", "b2"));

        // Do wrapper collection.
        WrappedSourceCollection = new ObservableCollection<ComplicateClassWraper>();
        foreach (var item in _sourceCollection)
            WrappedSourceCollection.Add(new ComplicateClassWraper(item));

        // Each time new item was added to grid need add it to source collection.
        // Same on delete.
        WrappedSourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged);

        InitializeComponent();
        DataContext = this;
    }

    void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
            foreach (ComplicateClassWraper wrapper in e.NewItems)
                _sourceCollection.Add(wrapper.GetItem());
        else if (e.Action == NotifyCollectionChangedAction.Remove)
            foreach (ComplicateClassWraper wrapper in e.OldItems)
                _sourceCollection.Remove(wrapper.GetItem());
    }

    private List<ComplicateClass> _sourceCollection;

    public ObservableCollection<ComplicateClassWraper> WrappedSourceCollection { get; set; }
}

最后是 XAML 代码:

<DataGrid CanUserAddRows="True"   AutoGenerateColumns="False"
          ItemsSource="{Binding Path=Items}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name"  Binding="{Binding Path=Name}"/>
        <DataGridTextColumn Header="SecondName"  Binding="{Binding Path=Surname}"/>
    </DataGrid.Columns>
</DataGrid>

The simplest way I could suggest to provide wrapper for your class without default constructor, in which constructor for source class will be called.
For example you have this class without default constructor:

/// <summary>
/// Complicate class without default constructor.
/// </summary>
public class ComplicateClass
{
    public ComplicateClass(string name, string surname)
    {
        Name = name;
        Surname = surname;
    }

    public string Name { get; set; }
    public string Surname { get; set; }
}

Write a wrapper for it:

/// <summary>
/// Wrapper for complicated class.
/// </summary>
public class ComplicateClassWraper
{
    public ComplicateClassWraper()
    {
        _item = new ComplicateClass("def_name", "def_surname");
    }

    public ComplicateClassWraper(ComplicateClass item)
    {
        _item = item;
    }

    public ComplicateClass GetItem() { return _item; }

    public string Name
    {
        get { return _item.Name; }
        set { _item.Name = value; }
    }
    public string Surname
    {
        get { return _item.Surname; }
        set { _item.Surname = value; }
    }

    ComplicateClass _item;
}

Codebehind.
In your ViewModel you need to create wrapper collection for your source collection, which will handle item adding/removing in datagrid.

    public MainWindow()
    {
        // Prepare collection with complicated objects.
        _sourceCollection = new List<ComplicateClass>();
        _sourceCollection.Add(new ComplicateClass("a1", "b1"));
        _sourceCollection.Add(new ComplicateClass("a2", "b2"));

        // Do wrapper collection.
        WrappedSourceCollection = new ObservableCollection<ComplicateClassWraper>();
        foreach (var item in _sourceCollection)
            WrappedSourceCollection.Add(new ComplicateClassWraper(item));

        // Each time new item was added to grid need add it to source collection.
        // Same on delete.
        WrappedSourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged);

        InitializeComponent();
        DataContext = this;
    }

    void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
            foreach (ComplicateClassWraper wrapper in e.NewItems)
                _sourceCollection.Add(wrapper.GetItem());
        else if (e.Action == NotifyCollectionChangedAction.Remove)
            foreach (ComplicateClassWraper wrapper in e.OldItems)
                _sourceCollection.Remove(wrapper.GetItem());
    }

    private List<ComplicateClass> _sourceCollection;

    public ObservableCollection<ComplicateClassWraper> WrappedSourceCollection { get; set; }
}

And finally, XAML code:

<DataGrid CanUserAddRows="True"   AutoGenerateColumns="False"
          ItemsSource="{Binding Path=Items}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name"  Binding="{Binding Path=Name}"/>
        <DataGridTextColumn Header="SecondName"  Binding="{Binding Path=Surname}"/>
    </DataGrid.Columns>
</DataGrid>
倒带 2024-10-15 08:05:16

我只是想提供使用 BindingList 的替代解决方案。在我的情况下,业务对象保存在可移植项目 (Silverlight) 的 IEntitySet 中,该项目不支持 IBindingList。

首先,解决方案是对网格进行子类化,并覆盖 CanUserAddRows 的强制回调以使用 IEditableCollectionViewAddNewItem:

public class DataGridEx : DataGrid
{
    static DataGridEx()
    {
        CanUserAddRowsProperty.OverrideMetadata(typeof(DataGridEx), new FrameworkPropertyMetadata(true, null, CoerceCanUserAddRows));
    }

    private static object CoerceCanUserAddRows(DependencyObject sender, object newValue)
    {            
        var dataGrid = (DataGrid)sender;
        var canAddValue= (bool)newValue;

        if (canAddValue)
        {
            if (dataGrid.IsReadOnly || !dataGrid.IsEnabled)
            {
                return false;
            }
            if (dataGrid.Items is IEditableCollectionViewAddNewItem v && v.CanAddNewItem == false)
            {
                // The view does not support inserting new items
                return false;
            }                
        }

        return canAddValue;
    }
}

然后使用 AddingNewItem 事件来创建项目:

dataGrid.AddingNewItem += (sender, args) => args.NewItem = new BusinessObject(args);

如果您关心细节,这就是它首先成为问题的原因。框架中的强制回调如下所示:

private static bool OnCoerceCanUserAddOrDeleteRows(DataGrid dataGrid, bool baseValue, bool canUserAddRowsProperty)
    {
        // Only when the base value is true do we need to validate that the user
        // can actually add or delete rows.
        if (baseValue)
        {
            if (dataGrid.IsReadOnly || !dataGrid.IsEnabled)
            {
                // Read-only/disabled DataGrids cannot be modified.
                return false;
            }
            else
            {
                if ((canUserAddRowsProperty && !dataGrid.EditableItems.CanAddNew) ||
                    (!canUserAddRowsProperty && !dataGrid.EditableItems.CanRemove))
                {
                    // The collection view does not allow the add or delete action
                    return false;
                }
            }
        }

        return baseValue;
    }

您看到它如何获取 IEditableCollectionView.CanAddNew 了吗?这意味着它仅在视图可以插入并构造项目时才启用添加。有趣的是,当我们想要添加新项目时,它会检查IEditableCollectionViewAddNewItem.CanAddNewItem,它只询问视图是否支持插入新项目(而不是创建):

 object newItem = null;
        IEditableCollectionViewAddNewItem ani = (IEditableCollectionViewAddNewItem)Items;

        if (ani.CanAddNewItem)
        {
            AddingNewItemEventArgs e = new AddingNewItemEventArgs();
            OnAddingNewItem(e);
            newItem = e.NewItem;
        }

I just wanted to provide an alternate solution to using a BindingList. In my situtation, the Business objects was held in an IEntitySet in a portable project (Silverlight), which did not support IBindingList.

The solution, first and foremost, is to subclass the grid, and overwrite the coerce callback for CanUserAddRows to use IEditableCollectionViewAddNewItem:

public class DataGridEx : DataGrid
{
    static DataGridEx()
    {
        CanUserAddRowsProperty.OverrideMetadata(typeof(DataGridEx), new FrameworkPropertyMetadata(true, null, CoerceCanUserAddRows));
    }

    private static object CoerceCanUserAddRows(DependencyObject sender, object newValue)
    {            
        var dataGrid = (DataGrid)sender;
        var canAddValue= (bool)newValue;

        if (canAddValue)
        {
            if (dataGrid.IsReadOnly || !dataGrid.IsEnabled)
            {
                return false;
            }
            if (dataGrid.Items is IEditableCollectionViewAddNewItem v && v.CanAddNewItem == false)
            {
                // The view does not support inserting new items
                return false;
            }                
        }

        return canAddValue;
    }
}

And then use the AddingNewItem event to create the item:

dataGrid.AddingNewItem += (sender, args) => args.NewItem = new BusinessObject(args);

And if you care for the details, here is the reason why it is a problem in the first place. The coerce callback in the framework looks like this:

private static bool OnCoerceCanUserAddOrDeleteRows(DataGrid dataGrid, bool baseValue, bool canUserAddRowsProperty)
    {
        // Only when the base value is true do we need to validate that the user
        // can actually add or delete rows.
        if (baseValue)
        {
            if (dataGrid.IsReadOnly || !dataGrid.IsEnabled)
            {
                // Read-only/disabled DataGrids cannot be modified.
                return false;
            }
            else
            {
                if ((canUserAddRowsProperty && !dataGrid.EditableItems.CanAddNew) ||
                    (!canUserAddRowsProperty && !dataGrid.EditableItems.CanRemove))
                {
                    // The collection view does not allow the add or delete action
                    return false;
                }
            }
        }

        return baseValue;
    }

You see how it gets the IEditableCollectionView.CanAddNew? That means that it only enables adding when the view can insert and construct an item. The funny thing is that when we want to add a new item, it checks the IEditableCollectionViewAddNewItem.CanAddNewItem instead, which only asks if the view supports inserting new items (not creating):

 object newItem = null;
        IEditableCollectionViewAddNewItem ani = (IEditableCollectionViewAddNewItem)Items;

        if (ani.CanAddNewItem)
        {
            AddingNewItemEventArgs e = new AddingNewItemEventArgs();
            OnAddingNewItem(e);
            newItem = e.NewItem;
        }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文