Problem:
"szukam sposobu zarejestrowania zakładu, który wie, jak tworzyć obiekty do siatki danych" . (Ponieważ mój zbiór obiektów biznesowych nie zapewnia konstruktora domyślnego.)
Objawy:
Jeśli ustawimy DataGrid.CanUserAddRows = true
a następnie powiązać zbiór elementów do DataGrid gdzie pozycja nie posiada domyślne konstruktor, a następnie DataGrid nie wyświetla "nowego wiersza pozycji".
Przyczyny:
Kiedy zbiór elementów jest związany z dowolnym WPF ItemControl, WPF owija w kolekcji albo:
BindingListCollectionView gdy zbiór wiążąc to BindingList<T>
. BindingListCollectionView
implementuje IEditableCollectionView, ale nie implementuje IEditableCollectionViewAddNewItem
.
a ListCollectionView, gdy kolekcja jest związana, jakakolwiek inna kolekcja. ListCollectionView
implementuje IEditableCollectionViewAddNewItem (i tym samym IEditableCollectionView
).
Dla opcji 2) DataGrid deleguje tworzenie nowych pozycji do ListCollectionView
. ListCollectionView
Wewnętrznie testuje istnienie domyślnego konstruktora i wyłącza AddNew
, jeśli taki nie istnieje. Oto odpowiedni kod z ListCollectionView przy użyciu 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;
}
}
Wydaje się, że nie istnieje łatwy sposób na zastąpienie tego zachowania.
Dla opcji 1) sytuacja jest o wiele lepsza. DataGrid deleguje tworzenie nowych elementów do BindingListView, który z kolei deleguje na BindingList. BindingList<T>
sprawdza także istnienie domyślnego konstruktora, ale na szczęście BindingList<T>
pozwala również klientowi ustawić właściwość AllowNew i dołączyć procedurę obsługi zdarzeń do dostarczania nowego elementu.Zobacz rozwiązanie później, ale tutaj jest odpowiedni kod w 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-rozwiązań:
- Wsparcie przez DataGrid (niedostępne)
Byłoby rozsądnie oczekiwać DataGrid, aby umożliwić klientowi dołączenie wywołania zwrotnego, za pomocą którego DataGrid zażąda domyślnego nowego elementu, tak jak powyżej BindingList<T>
. Dałoby to klientowi pierwsze pęknięcie przy tworzeniu nowego przedmiotu, gdy jest on wymagany.
Niestety, nie jest to obsługiwane bezpośrednio z DataGrid, nawet w .NET 4.5.
. Wydaje się, że .NET 4.5 ma nowe wydarzenie "AddingNewItem", które nie było wcześniej dostępne, ale to tylko pozwala na dodanie nowego przedmiotu.
arounds pracy:
- firm obiekt utworzony przez narzędzie w tym samym zespole: stosować częściowe klasa
Ten scenariusz wydaje się bardzo mało prawdopodobne, ale wyobrażam sobie, że Entity Framework stworzony swoich klas encji bez domyślnego konstruktora (mało prawdopodobne, ponieważ nie można ich było serializować), moglibyśmy po prostu utworzyć klasę częściową z domyślnym konstruktorem. Problem rozwiązany.
- Obiekt biznesowy znajduje się w innym zespole i nie jest zaplombowany: utwórz supertyp obiektu biznesowego.
Tutaj możemy dziedziczyć z typu obiektu biznesowego i dodawać domyślny konstruktor.
To początkowo wydawało się dobrym pomysłem, ale na drugi rzut oka może to wymagać więcej pracy, niż to konieczne, ponieważ musimy skopiować dane generowane przez warstwę biznesową do naszej superplikowej wersji obiektu biznesowego.
Musielibyśmy kod jak
class MyBusinessObject : BusinessObject
{
public MyBusinessObject(BusinessObject bo){ ... copy properties of bo }
public MyBusinessObject(){}
}
A potem jakiś LINQ do projektu pomiędzy listami tych obiektów.
- obiektu biznesowego jest w innym zespole i jest uszczelniony (lub nie): hermetyzacji obiektu biznesowego.
Jest to o wiele łatwiejsze
class MyBusinessObject
{
public BusinessObject{ get; private set; }
public MyBusinessObject(BusinessObject bo){ BusinessObject = bo; }
public MyBusinessObject(){}
}
Teraz wszystko, co musimy zrobić, to użyć trochę LINQ do projektu pomiędzy listami tych obiektów, a następnie wiążą się MyBusinessObject.BusinessObject
w DataGrid. Nie jest wymagane zawiłe pakowanie właściwości lub kopiowanie wartości.
Rozwiązanie: (hurra znaleźć jeden)
- Zastosowanie
BindingList<T>
Jeśli owinąć naszą kolekcję obiektów biznesowych w BindingList<BusinessObject>
a następnie powiązać DataGrid do tego, z kilkoma linie kodu nasz problem został rozwiązany, a DataGrid odpowiednio wyświetli nowy wiersz pozycji.
public void BindData()
{
var list = new BindingList<BusinessObject>(GetBusinessObjects());
list.AllowNew = true;
list.AddingNew += (sender, e) =>
{e.NewObject = new BusinessObject(... some default params ...);};
}
Inne rozwiązania
- wdrożyć IEditableCollectionViewAddNewItem na szczycie istniejącego typu kolekcji. Prawdopodobnie dużo pracy.
- dziedziczy z ListCollectionView i zastępuje funkcjonalność. Częściowo mi się to udało, prawdopodobnie można to zrobić przy większym wysiłku.
Dzięki za wspaniałą odpowiedź. Klasa ListCollectionView implementuje interfejs IEditableCollectionViewAddNewItem. Spojrzałem na implementację za pośrednictwem Reflectora.Microsoft wykonał wiele optymalizacji wydajności w tej klasie. Nie chcę zaimplementować tego interfejsu dla samej tylko metody fabrycznej. – jbe
@jbe. Rozumiem, że :) Ponadto, nie było wiele informacji na temat IEditableCollectionViewAddNewItem, co najmniej nie udało mi się znaleźć. Pamiętaj, aby zaktualizować, jeśli znajdziesz sposób na wykonanie zadania. –