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;
}
}
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With