Ich bin gezwungen, View First MVVM in einer WPF-Anwendung zu verwenden, und ich kämpfe darum, wie man es elegant gestalten kann.
Die Wurzel des Problems liegt in verschachteltem UserControls
. In einer MVVM-Architektur muss für jedes UserControl
das View-Modell seinem DataContext
zugewiesen werden. Dies hält die Bindungsausdrücke einfach. Darüber hinaus wird WPF jede über ein DataTemplate
generierte Ansicht instanziieren.
Aber wenn ein Kind UserControl
Abhängigkeitseigenschaften hat, die der Elternteil an sein eigenes Ansichtsmodell binden muss, dann bedeutet die Tatsache, dass das Kind UserControl
sein DataContext
auf sein eigenes Ansichtsmodell gesetzt hat, dass es sich um eine 'implizite Pfadbindung' handelt in der übergeordneten XAML-Datei wird in das Viewmodel des untergeordneten Elements anstelle der übergeordneten aufgelöst.
Um dieses Problem zu umgehen, muss jeder Elternteil jedes UserControl
in der Anwendung entweder explizit benannte Bindings für alles standardmäßig verwenden (was ausführlich, hässlich und fehlerträchtig ist), oder er muss wissen, ob ein bestimmtes Steuerelement vorhanden ist Sein DataContext
wird auf ein eigenes Viewmodel gesetzt oder nicht und verwendet die entsprechende Bindesyntax (was ebenfalls fehlerbehaftet ist und eine wesentliche Verletzung der Grundkapselung darstellt).
Nach Tagen der Forschung habe ich keine einzige halbwegs vernünftige Lösung für dieses Problem gefunden. Der Lösung, die mir am nächsten kommt, ist, das UserControl's
viewmodel auf ein inneres Element von UserControl
(das oberste Grid
oder was auch immer) zu setzen, was immer noch ein Problem beim Binden von Eigenschaften des UserControl
selbst zu einem eigenen Viewmodel! ( ElementName
binding funktioniert in diesem Fall nicht, da die Bindung vor dem benannten Element deklariert würde, wobei das Viewmodel seinem DataContext
zugewiesen ist.)
Ich vermute, dass der Grund, warum nicht viele andere Leute darauf stoßen, dass sie entweder viewmodel zuerst MVVM verwenden, das dieses Problem nicht hat, oder sie verwenden zuerst MVVM in Verbindung mit einer Dependency-Injektion-Implementierung, die dieses Problem verbessert .
Hat jemand dafür bitte eine saubere Lösung?
UPDATE:
Beispielcode wie angefordert.
%Vor% %Vor% %Vor% %Vor% %Vor%Dies führt zu:
System.Windows.Data Error: 40: BindingExpression-Pfadfehler: 'MainWindowVmString1' -Eigenschaft nicht auf 'Objekt' '' UserControl6Vm 'gefunden (HashCode = 44204140) '. BindingExpression: Pfad = MainWindowVmString1; DataItem = 'UserControl6Vm' (HashCode = 44204140); Zielelement ist 'UserControl6' (Name = '_ this'); Zieleigenschaft ist 'Text' (Typ 'String')
weil in MainWindow.xaml
die Deklaration <local:UserControl6 Text="{Binding MainWindowVmString1}"/>
versucht, MainWindowVmString1
in UserControl6Vm
aufzulösen.
In UserControl6.xaml
kommentiert die Deklaration von DataContext
und der erste TextBlock
den Code, aber der UserControl
benötigt ein DataContext
. In MainWIndow1
mit einer ElementName
anstelle einer imvictt Pfadbindung funktioniert auch, aber um die ElementName
Bindesyntax zu verwenden, müsstest du entweder wissen, dass UserControl
sein Viewmodel seinem DataContext
( Einkapselungsfehler) oder alternativ eine Richtlinie zur Verwendung von ElementName
bindings überall . Keine von beiden ist ansprechend.
Eine sofortige Lösung besteht darin, ein RelativeSource
zu verwenden und nach dem DataContext
eines Elternteils UserControl
:
Sie können die untergeordneten Ansichtsmodelle auch als Eigenschaften des übergeordneten Ansichtsmodells behandeln und sie vom übergeordneten Objekt weiterleiten. Auf diese Weise erkennt das übergeordnete Ansichtsmodell die untergeordneten Elemente, sodass es ihre Eigenschaften aktualisieren kann. Die untergeordneten Ansichtsmodelle können auch eine "Parent"
-Eigenschaft aufweisen, die einen Verweis auf das übergeordnete Element enthält, das vom übergeordneten Element selbst bei ihrer Erstellung eingegeben wird, was direkten Zugriff auf das übergeordnete Element gewähren kann.
BEARBEITEN
Ein anderer Ansatz und komplexer wäre es, die DataContext
des Elternteils für das Kind UserControl
unter Verwendung einer angehängten Eigenschaft verfügbar zu machen. Ich habe es noch nicht vollständig implementiert, aber es würde in einer angehängten Eigenschaft bestehen, um das Feature anzufordern (etwas wie "HasAccessToParentDT"
), in dem DependencyPropertyChanged
event die Load- und Unload
-Ereignisse von ChildUserControl
miteinander verbindet. Greifen Sie auf die Eigenschaft Parent
zu (verfügbar, wenn das Steuerelement geladen ist) und binden Sie ihr DataContext
an eine zweite angehängte Eigenschaft, "ParentDataContext"
, die dann in xaml verwendet werden kann.
Die offensichtlichste Lösung ist die Verwendung der RelativeSource. Die Bindung selbst sieht nicht sehr hübsch aus, aber es ist tatsächlich sehr häufig zu sehen. Ich würde es nicht vermeiden - das ist genau das Szenario, warum es da ist.
Ein anderer Ansatz, den Sie verwenden können, ist ein Verweis auf ein Eltern-View-Modell, wenn es logisch ist, es zu haben. So wie ich eine FlightPlan-Ansicht habe, die eine Liste von Navigationspunkten und deren graphischer "Karte" nebeneinander zeigt. Die Liste der Punkte ist eine separate Ansicht mit separatem Ansichtsmodell:
%Vor%Dann kann die Ansicht wie folgt an diese Eigenschaft gebunden werden:
%Vor%Allerdings ist das Erstellen von Viewmodels oft fraglich.