Type of object created by ListCollectionView.AddNew
Asked Answered
L

3

6

How does ListCollectionView.AddNew determine the type of object it creates, and how could one affect it?

I have a hierarchy of a few types (Base, DerivedA, and DerivedB), and currently my WPF Toolkit DataGrid creates DerivedA objects (why, I don't know -- probably because almost all the data in the grid is of that type), but I'd like it to create DerivedB objects instead.

Update: I've tried deriving a new class from ListCollectionView and implementing a new AddNew method for it, and now I'm almost there: the only remaining problem is that after adding a new item, a new new item placeholder isn't added, so I can only add one item. My current approach looks somewhat like this:

public class CustomView : ListCollectionView, IEditableCollectionView
{
    public CustomView(System.Collections.IList list)
        : base(list)
    {
    }

    object IEditableCollectionView.AddNew()
    {
        DerivedB obj = new DerivedB();
        InternalList.Add(obj);
        return obj;
    }
}
Landholder answered 8/5, 2009 at 20:20 Comment(0)
E
5

Stale questions deserve fresh answers :)

Deriving a class from ListCollectionView is the path I took to control the objects being added by AddNew as well, but after browsing through the source of ListCollectionView to find out what it does internally, I found that the safest way to redefine AddNew (it's not technically an override) is to use ListCollectionView.AddNewItem after creating my new object, so your code would look like this:

public class CustomView : ListCollectionView, IEditableCollectionView 
{ 
    public CustomView(System.Collections.IList list) 
        : base(list) 
    { 
    } 

    object IEditableCollectionView.AddNew() 
    { 
        DerivedB obj = new DerivedB(); 
        return base.AddNewItem(obj); 
    } 
} 

This works well because, in addition to having nearly identical implementations otherwise, ListCollectionView.AddNew() and ListCollectionView.AddNewItem(object item) both call AddNewCommon(object newItem):

public object AddNew() 
{ 
    VerifyRefreshNotDeferred();

    if (IsEditingItem)
        CommitEdit();   // implicitly close a previous EditItem

    CommitNew();        // implicitly close a previous AddNew 

    if (!CanAddNew)
        throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "AddNew")); 

    return AddNewCommon(_itemConstructor.Invoke(null));
}

public object AddNewItem(object newItem)
{
    VerifyRefreshNotDeferred(); 

    if (IsEditingItem) 
        CommitEdit();   // implicitly close a previous EditItem

    CommitNew();        // implicitly close a previous AddNew

    if (!CanAddNewItem) 
        throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "AddNewItem"));

    return AddNewCommon(newItem); 
}

AddNewCommon is where all the real magic happens; firing events, calling BeginInit and BeginEdit on the new item if supported, and eventually through callbacks on the datagrid, establishing the cell bindings:

object AddNewCommon(object newItem)
{
    _newItemIndex = -2; // this is a signal that the next Add event comes from AddNew
    int index = SourceList.Add(newItem); 

    // if the source doesn't raise collection change events, fake one 
    if (!(SourceList is INotifyCollectionChanged)) 
    {
        // the index returned by IList.Add isn't always reliable 
        if (!Object.Equals(newItem, SourceList[index]))
            index = SourceList.IndexOf(newItem);

        BeginAddNew(newItem, index); 
    } 

    Debug.Assert(_newItemIndex != -2 && Object.Equals(newItem, _newItem), "AddNew did not raise expected events"); 

    MoveCurrentTo(newItem);

    ISupportInitialize isi = newItem as ISupportInitialize; 
    if (isi != null)
        isi.BeginInit(); 

    IEditableObject ieo = newItem as IEditableObject;
    if (ieo != null)
        ieo.BeginEdit(); 

    return newItem; 
}

Here I've included the source code to my TypedListCollectionView, which I use to control the AddNew behavior when I don't know what type will be needed at design time:

public class TypedListCollectionView : ListCollectionView, IEditableCollectionView
{
    Type AddNewType { get; set; }

    public TypedListCollectionView(System.Collections.IList source, Type addNewType)
        : base(source)
    {
        AddNewType = addNewType;
    }

    object IEditableCollectionView.AddNew()
    {
        object newItem = Activator.CreateInstance(AddNewType);
        return base.AddNewItem(newItem);
    }
}

I like this approach since it provides maximum flexibility for cases where AddNew's type may need to be adjusted at runtime from one to another. It also allows AddNew to work for adding the first item in the collection, which is handy when the list source is initially empty, but its underlying type can be determined.

This link discusses an alternative way to force the type used by AddNew(). It uses reflection to set the private _itemConstructor property used by AddNew to a parameterless constructor of a specified type. This would be particularly useful when your ListCollectionView is coming from a component that's outside of your influence, or you need to add functionality into existing code and you're worried about breaking things (which I never am because I'm a cavalier coder who callously carouses with collections).

Ensemble answered 30/1, 2012 at 0:42 Comment(2)
I changed the architecture in my application so this is no longer an issue for me, but your answer does seem plausible based on just looking at it.Landholder
For some reason, I don't seem to have accepted this answer last year.Landholder
O
2

In .NET 4, there is now a new interface IEditableCollectionViewAddNewItem, implemented by ListCollectionView, which possesses a new method AddNewItem(object). You can use it instead of AddNew() to control the newly added item.

Ocko answered 13/5, 2012 at 13:4 Comment(1)
Indeed there is, and this answer is equally ok as the one from Erikest I've accepted (by virtue of that answer being older and also mentioning ListCollectionView.AddNewItem(object)).Landholder
O
1

TomiJ,

see if it helps, but isn't the answer ok?

http://www.cnblogs.com/winkingzhang/archive/2008/05/22/1204581.html

October answered 8/5, 2009 at 20:30 Comment(5)
The article (the original one is at <blogs.msdn.com/vinsibal/archive/2008/05/20/…) was of some help, as it did set me off looking at ListCollectionView.Landholder
I see, i you managed to get the right answer, don't forget to update here. It's a very interesting question :DCharity
I'm not quite there yet, as I can only add one new item, but at least the correct AddNew method gets called. I'll have to figure out what else I need to implement to get proper functionality.Landholder
why don't you implement a strategy pattern, are your familiar with the concept?Charity
Where do you mean I should apply the strategy pattern at? Basically add a new Context class which would contain the Base/DerivedA/DerivedB instances and the collection would then contain these? This is a bit awkward.Landholder

© 2022 - 2024 — McMap. All rights reserved.