Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type of object created by ListCollectionView.AddNew

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;
    }
}
like image 997
Tomi Junnila Avatar asked May 08 '09 20:05

Tomi Junnila


2 Answers

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).

like image 94
Erikest Avatar answered Sep 18 '22 15:09

Erikest


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.

like image 32
Julien Lebosquain Avatar answered Sep 20 '22 15:09

Julien Lebosquain