Laden...

MVVM: ViewModel Instanz wird doppelt erzeugt

Erstellt von Sprotti vor 8 Jahren Letzter Beitrag vor 2 Jahren 2.939 Views
Sprotti Themenstarter:in
112 Beiträge seit 2006
vor 8 Jahren
MVVM: ViewModel Instanz wird doppelt erzeugt

Hallo zusammen,
ich habe in CodeProject und hier im Forum schon viel Hilfreiches zum Thema MVVM gefunden und umgesetzt, bin aber nun auf ein ärgerliches Hindernis gestossen.

Situation:
View:
Ich habe eine MainView mit einem ContentControl und Buttons die den anzuzeigenden Inhalt (UserControl) in dieser umschalten. Jedes dieser UserControl entspricht also einer View mit einem dazu gehörenden ViewModel.


<Window.Resources>
 <DataTemplate  x:Key="OverviewTemplate" DataType="vm:ViewModelOverview">
   <local:Overview/>
 </DataTemplate>
 <helper:ViewTemplateSelector x:Key="VTemplateSelector"
  overviewVM="{StaticResource OverviewTemplate}" 
  infoVM="{StaticResource InfoTemplate}"
 />
</Window.Resources>
<Grid>
<ContentControl Content="{Binding ActiveViewModel}" ContentTemplateSelector="{StaticResource VTemplateSelector}"/>
</Grid>

ViewModel:
Im MainViewViewModel wird bei der Umschaltung der gebundenen ActiveViewModel Property das ViewModel der anzuzeigenden View zugewiesen. Im Konstruktor des MainViewViewModels werden alle ViewModels aller möglichen Views instanziiert.


public ViewModelMainPage()
{
   #region create ViewModel Instances
            _overviewViewModel = new ViewModelOverview();            
            _infoViewModel = new ViewModelInfo();
  #endregion
}

private ViewModelBase _activeViewModel;
public ViewModelBase ActiveViewModel
{
   get { return _activeViewModel; }
   set { _activeViewModel = value; RaisePropertyChanged( "ActiveViewModel" ); }
}

In der entsprechenden View wird das entsprechende ViewModel als Resource hinterlegt.


<UserControl.Resources>
   <vm:ViewModelOverview x:Key="OverviewVM"/>
</UserControl.Resources>


Nun wird also stets die dem gerade selektierten ViewModel entsprechende View im ContentControl angezeigt. Aber...

Herausforderung:
wenn ich von einer View in die andere wechsele wird jedes Mal beim erneuten Anzeigen eine neue Instanz der View erzeugt und was noch schlimmer ist, auch eine neue Instanz des zugehörigen ViewModel. Im CallStack ist ersichtlich, dass ausgehend von InitializeComponent der View der Konstruktor des zugeordneten ViewModels aufgerufen wird.

Frage:
Wie stelle ich meinen Code so um, dass die View stets auf die eine Instanz des ViewModel zugreift, welche in dem Konstruktor des MainViewViewModel beim Anwendungsstart erzeugt wurde? Die Instanziierung der ViewModel muss auch nicht unbedingt im MainViewViewModel erfolgen. Ich wäre hier für Vorschläge dankbar wo ein besserer Ort dafür wäre.

heut debug ich, morgen browse ich, übermorgen cast ich die Königin auf int...

3.170 Beiträge seit 2006
vor 8 Jahren

Hallo,

In der entsprechenden View wird das entsprechende ViewModel als Resource hinterlegt.

Warum denn? Das ist genau der Knackpunkt, warum immer eine neue Instanz des ViewModels zusammen mit der View erstellt wird...
In der View selbst ist das aus der übergeordenten View gebundene ActiveViewModel gleichzeitig der DataContext. Darüber kannst Du von der View aus aufs VM zugreifen.

Gruß, MarsStein

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

5.299 Beiträge seit 2008
vor 8 Jahren

jou.

Nur wenn du den Datacontext jetzt nicht mehr im lokalen Xaml festlegst, dann können im Xaml-Editor Intellisense und PropertyGrid nicht mehr plausible Bindings vorschlagen.

Also falls du das Feature genutzt hast, und es nun vermisst, kannst du dir mitte d:DataContext - MarkupExtension behelfen - Interesse?

Der frühe Apfel fängt den Wurm.

Sprotti Themenstarter:in
112 Beiträge seit 2006
vor 8 Jahren

Warum denn? Das ist genau der Knackpunkt,...

Darüber kannst Du von der View aus aufs VM zugreifen.

Brauche ich denn keinen Key für die Bindungen in der View. Was gebe ich den Bindungen als Source mit wenn ich in den Resources den Key OverviewVM nicht mehr deklariere? Bisher habe ich ja die Bindungen der View so geschrieben...


<Button.Command>
  <Binding Source="OverviewVM" Path="CmdApplyPLCSettings"/>
</Button.Command>

Wenn ich die Deklaration in Resources und Source in den Bindungen einfach weglasse, scheint keine Datenbindung für die Felder zu existieren.

heut debug ich, morgen browse ich, übermorgen cast ich die Königin auf int...

5.299 Beiträge seit 2008
vor 8 Jahren

also normalerweise (TrivialFall) sollte es so funzen:

<Button.Command>
  <Binding Path="CmdApplyPLCSettings"/>
</Button.Command>

keine Source angeben, dann gilt der geerbte DataContext.
funzt natürlich nur, wenn du dich auch im richtigen DataContext befindest.

Liegt der Kram in Resourcen, Styles, Templates, oder eingeschachtelt in Listboxen, ItemsControls etc., dann gilt evtl. ein anderer DataContext, also die Logik, wie der DataContext sich durch den VisualTree vererbt (und wasses da für Ausnahmen gibt) kann im Einzelfall nicht ganz trivial sein.

Also empfehle ich, probier erstmal mit wirklich trivialen Samples.

Der frühe Apfel fängt den Wurm.

Sprotti Themenstarter:in
112 Beiträge seit 2006
vor 8 Jahren

Es hat auch funktioniert. Ich hatte nur verpasst, dass ich aus Probierwut noch den DataContext auf den Key gesetzt hatte.


 <DockPanel LastChildFill="True" DataContext="OverviewVM">

Hab ihn entfernt und nun läuft es auch.

⚠ An alle WPF Anfänger wie mich... weniger ist mehr. Der notwendige Rest wird von WPF Magic erledigt. ⚠

Problem ist gelöst. Vielen Dank Euch.

Eine Frage hätte ich aber doch noch. Wie wird denn das ActiveViewModel aus dem MainViewViewModel zum DataContext für die View. Welche Stelle in meinem Code sorgt dafür? Blöd wenn etwas funktioniert was du machst und du weißt nicht warum... Hat das was mit dem DataTemplate zu tun? Wird der DataContext der MainView ergänzt oder geändert? Ich habe den DataContext für die MainView eigentlich nur einmal im Code gesetzt.


<Window.DataContext>
   <vm:ViewModelMainPage />
</Window.DataContext>

Von den anderen ViewModels habe ich eigentlich nirgendwo sonst deklariert, dass sie als DataContext für ihre View dienen sollen.

Ach ja, und es wäre natürlich arg hilfreich wenn IntelliSens weiterhin dazu gebracht werden könnte vernünftige Binding Vorschläge zu liefern. Wie muss ich denn MarkupExtension anwenden?

heut debug ich, morgen browse ich, übermorgen cast ich die Königin auf int...

5.299 Beiträge seit 2008
vor 8 Jahren

ich bin zu faul, alle deine Fragen hier zu behandeln.
Zum Problemkreis habich paar Tuts verzapft - ist bestimmt einiges dir nützliches dabei:
https://www.vb-paradise.de/index.php/Thread/83442-MVVM-Binding-Picking-im-Xaml-Editor/
https://www.vb-paradise.de/index.php/Thread/115475-MVVM-Anwendungs-Struktur/?postID=1005812

Der frühe Apfel fängt den Wurm.

Sprotti Themenstarter:in
112 Beiträge seit 2006
vor 8 Jahren

Danke für die Links. Mehr ist doch auch nicht erforderlich. Wenn ich die Antwort gefunden habe kann ich sie ja dann immer noch für das Forum aufbereiten. (Um das Thema rund zu machen=)

heut debug ich, morgen browse ich, übermorgen cast ich die Königin auf int...

K
7 Beiträge seit 2019
vor 2 Jahren

Kann jemand einen Ausschnitt von dem ViewTemplateSelector posten oder die Funktionsweise beschreiben?