Laden...

ItemsControl-Item Listenelement bei klick auf Element löschen

Erstellt von DeSharper vor 6 Jahren Letzter Beitrag vor 6 Jahren 2.025 Views
D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 6 Jahren
ItemsControl-Item Listenelement bei klick auf Element löschen

Hi,

ich habe ein ItemsControl, und möchte jetzt aus der Source-Liste ein Element löschen. Jetzt hab ich zwei Probleme: Erstens kann ich im ItemsTemplate ja erstmal nur auf das ViewModel des Items zugreifen. Der Löschvorgang muss aber ja eine Ebene drüber stattfinden. Und zweitens bräuchte mein Command ja einen Parameter, damit ich weiß, WELCHES Element gelöscht werden soll.

Also konkret hab ich ein Fenster in dem dann irgendwo das hier steht


<ItemsControl 
    ItemsSource="{Binding Path=ListElements}"
    ItemTemplate="{StaticResource MyTemplate}"/>

Dazu gibts ein ViewModel


public ObservableCollection<ItemViewModel> ListElements;
public ICommand RemoveElement {get; }

Und natürlcih das Template:


<DataTemplate x:Key="MyTemplate" DataType="viewModels:ItemViewModel">
...
    <Button Content="X"
        Command="{Binding RemoveElement }"/>

Das Element soll nicht bei jedem Klick auf das Element gelöscht werden (wär ja auch zu einfach). Nur wenn der Löschbutton geklickt wird.

Mein bisher "bester" Plan war, beim Füllen der Liste ListElements jedem ItemViewModel einen Verweis auf das übergeordnete ViewModele mitzugeben. Dann könnte die Löschfunktion im übergeordneten ViewModel liegen und das ItemViewModel ruft sie dann mit sich selbst als Parameter auf. Aber schön erscheint mir das nicht. Wie macht man sowas vernünftig?

2.078 Beiträge seit 2012
vor 6 Jahren

Drei Möglichkeiten fallen mir spontan ein.

Möglichkeit 1:
Schau dir mal das BindingProxy an.

Vorher in den Resourcen (z.B. vom ItemsControl, oder dessem Parent) anlegen, Data="{Binding}" und schon hast Du einen Proxy auf das vorherige ViewModel.
Im Item-Binding schreibst Du dann:

<Button Command="{Binding Data.DeleteItemCommand, Source={StaticResource MyViewModelProxy}}" />

Möglichkeit 2:
Mit der RelativeSource nach dem ItemsControl suchen und dann gegen DataContext binden.
Das hat aber den Nachteil, dass Du das Binding vom Aufbau der View abhängig machst.

<ItemsControl Name="MyItemsControl" />
<!-- In diesem Fall muss der Button ein direktes oder indirektes Child vom ItemsControl sein -->
<Button Command="{Binding DataContext.DeleteItemCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" />

Möglichkeit 3:
Dem ItemsControl ein Name geben und im Binding dann die Property ElementName entsprechend setzen.

<ItemsControl Name="MyItemsControl" />
<Button Command="{Binding DataContext.DeleteItemCommand, ElementName=MyItemsControl}" />

In allen drei Fällen:

<Button CommandParameter="{Binding}" />

Um das ItemViewModel dem Command als Parameter mitzugeben, damit es weiß, was es löschen soll.

Ich persönlich bevorzuge die Lösung mit dem BindingProxy, weil ich da auch gleich Converter dran hängen und so spätere mehrfach vorkommende Binding "vorbereiten" kann.
Manchmal nehme ich aber auch Variante 3 mit dem ElementName, weil sie die einfachste Möglichkeit ist.
RelativeSource mag ich nicht so gerne, weil ich mich damit vom konkreten Aufbau der View abhängig mache. Der Name bleibt immer gleich und die Resource bleibt auch immer mit dem selben Key am selben Ort, wenn ich umstrukturiere. Aber je nachdem, wie die RelativeSource aufgebaut ist, dann das eventuell nicht mehr klappen.

PS:
Der Code ist ungetestet und direkt im Forum getestet.
Bitte verzeiht kleine Fehler, aber das Grundpronzip sollte denke ich klar geworden sein.

3.170 Beiträge seit 2006
vor 6 Jahren

Hallo,

die Löschfunktion muss ja ohnehin dort ausgeführt werden, wo die Liste gehalten wird. Das Element kann sich ja nicht selbst löschen.
Das ist aber relativ einfach. Dein Ansatz ist auch schon ganz gut. Einen Verweis auf das übergeordnete ViewModel brauchst Du aber in den Items nicht.

Es reicht ja, wenn Dein ICommand zum Löschen das ItemViewModel als Parameter bekommt.
Dann musst Du eben nur das Command aus dem übergeordneten DataContext (dem ViewModel, das die Liste enthält) binden.

<Button Content="X"
        Command="{Binding DataContext.RemoveElement, RelativeSource={RelativeSource AncestorType=ItemsControl}}" 
        CommandParameter="{Binding}"/>

Gruß, MarsStein

Palladin007 war schneller. Ob ich hier allerdings einen BindingProxy nehmen würde... Kanonen, Spatzen und so...

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

2.078 Beiträge seit 2012
vor 6 Jahren

Ja, die BindingProxy-Lösung ist ein bisschen Überdimensioniert, aber sie sind einfach zu nutzen und damit kann man so ziemlich jede vergleichbare Problem erschlagen.
Selbst bei so nervigen Problemen wie wenn man sich außerhalb des VisualTrees "befindet", z.B. beim ContextMenu, kann man so ein vorheriges ViewModel finden. Das geht mit dem ElementName glaub aber auch.

Ist wohl einfach Geschmackssache.
Ich mag, dass ich mein gesuchtes ViewModel in den Resourcen habe und keinen "Umweg" über ein Parent-Control gehen muss.

D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 6 Jahren

Vielen Dankf für die ausführlichen Antworten, hab alle drei Möglichkeiten ausprobiert. Funktioniert alles wunderbar, auch wenn der Proxy sich zuerst ein bisschen geziert hat 😃 Ich stimme euch zu, der Proxy erscheint mir hier auch ein wenig überdimensioniert, aber trotzdem Danke für den Vorschlag, ich werde ihn auf jeden Fall in meinen Werkzeugkasten aufnehmen - heute sind es vielleicht nur Spatzen, aber wer weiß wann man so ne Kanone mal brauchen kann.

Mir persönlich gefällt die RelativeSource-Lösung an dieser Stelle ganz gut. Ich habs auch nach vielen Spielereien mit der View nicht kaputt bekommen. Wann wäre es denn kein direktes oder indirektes child mehr? Höchstens, wenn ich auf Ebene des ItemsControl einen Button hätte, der einfach das letzte Element löscht oder so. Das wäre aber von der Funktionalität her eine Änderung, die es auch rechtfertigt, dass ich in dem Fall das CommandBinding nochmal anfassen muss.

ps. könnte es sein, dass die Code-Schnipsel für Möglichkeit 2 und 3 vertauscht sind? 😃

2.078 Beiträge seit 2012
vor 6 Jahren

Stimmt, die Schnipsel sind vertauscht 😄
Werd ich gleich anpassen, danke für den Hinweis

Problematisch könnte ein ContextMenu sein.
Soweit ich mich erinnere, gibts da Probleme, den Tree zu durchsuchen und nichts anderes tut die RelativeSource.
Wenn Du mit dem ContextMenu arbeitest und Schwierigkeiten hast, solltest Du den Proxy mal ausprobieren, der kann viel Zeit ersparen.

Oder wenn die RelativeSource nach einer ListBox sucht und Du das aus irgendeinem Grund in einen anderen Auflistungs-Typ änderst.

Aber wie gesagt: Ist auch eine Frage des Geschmacks 😄