Laden...

Kann man Aufrufe im Konstruktor des ViewModels überspringen, wenn man im Designer ist?

Erstellt von JimStark vor 3 Jahren Letzter Beitrag vor 3 Jahren 545 Views
JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren
Kann man Aufrufe im Konstruktor des ViewModels überspringen, wenn man im Designer ist?

Hey,

ich gebe meinem Window im XAML Code das ViewModel.


    <Window.Resources>
        <ResourceDictionary>
            <local:MainViewModel x:Key="vm"/>
        </ResourceDictionary>
    </Window.Resources>

Das Problem ist jetzt, im Konstruktor kann es sein dass ein Dialog aufgerufen wird (mit ShowDialog(), also der wartet). Das passiert auch im Designer.

Gibt es da eine Möglichkeit wie z.B. sowas in der Art:


#if DESIGNER 

Also dass er den Teil überspringt wenn er im Designermodus ist. Mit Debug ist es mir zu umständlich da ich es auch dort testen muss.

Viele Grüße

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren
2.078 Beiträge seit 2012
vor 3 Jahren

Es gibt eine DesignerProperties.GetIsInDesignMode-Methode.

Aber ganz ehrlich: Für mich ist das ein Konzept-Fehler
Der Konstruktor sollte nicht irgendwas aufwändiges tun, dessen Aufgabe ist es, das Objekt vorzubereiten, nichts weiter.
Das heißt: UI, Dateisystem, Datenbank, etc. sind im Konstruktor tabu.

Und ich persönlich lege auch nie irgendein ViewModel in den Resourcen ab.
In der App.xaml.cs zeige ich das Window an und stecke direkt das ViewModel rein. Das könnte man auch über einen DI-Container machen, der nimmt dann noch ein bisschen Arbeit ab.
Alles Andere sollte das MainViewModel verwalten oder eines dieser verwalteten ViewModels oder der DI-Container.

Und um zu erreichen, dass etwas nach dem Start passiert, würde ich ein Interface mit passenden OnXYZ-Methoden (z.B. OnLoaded) und die Views gucken, ob ihr DataContext dieses Interface hat. Dafür könnte man z.B. ein Behavior oder eine AttachedProperty implementieren, dann muss man nicht jeden CodeBehind anpassen. Oder nur das Window kümmert sich darum und das MainViewModel reicht es weiter.
Diese Methoden kann man dann auch asynchron machen und z.B. einen Ladebalken anzeigen.

Wenn es dir um IntelliSense in XAML geht, schau dir das mal an:
https://docs.microsoft.com/de-de/windows/uwp/xaml-platform/xaml-namespaces-and-namespace-mapping#other-xaml-namespaces

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Hm okay. Ich habe hier nur mal gelesen dass in der Codebehind am besten gar nichts stehen soll.

Wäre es dann sauberer in der Startup-Methode (z.B. Startup="Application_Startup") im app.xaml Code-Behind
meine Datenbankverbindung aufzubauen, MainWindow erstellen, VM übergeben, usw. Also ich verwalte meine "Services" derzeit alle im MainViewModel und übergebe die per DI an die anderen VMs.

2.078 Beiträge seit 2012
vor 3 Jahren

Ich habe hier nur mal gelesen dass in der Codebehind am besten gar nichts stehen soll.

Das stimmt nur so halb. Ja, bei WPF/MVVM braucht man so gut wie nie CodeBehind, aber verboten ist der nicht.
Du musst unterscheiden, wofür der Code ist, ist es Anwendungslogik oder reine View-Steuerung? Letzteres ist in Ordnung und manchmal auch notwendig.
Besonders bei CustomControls geht es nicht ohne.

Mein Vorschlag ist aber auch keine Logik in dem Sinne, sondern eine Art selbst gebautes Event-Binding, um das ViewModel über View-Änderungen zu informieren, ohne dass das ViewModel dabei von der View abhängig ist.
Wenn Du darauf verzichten willst, kannst Du aber auch die genannten Alternativen Behavior oder AttachedProperty nutzen, damit kannst Du sozusagen den Code "auslagern".
Es bleibt dann immer noch eine Art CodeBehind, aber es ist unabhängig von der konkreten View und kann wiederverwendet werden.

Und der Code in der App.xaml.cs spielt sowieso eine besondere Rolle, denn da beginnt alles bzw. alles läuft da zusammen.
Egal wie schön oder elegant man etwas löst, irgendwo bleibt ein kleiner Rest, den man nicht weiter optimieren kann. Dort liest Du dann z.B. Configs ein, bereitest die UI vor, etc. und startest am Ende die eigentliche Anwendung.

meine Datenbankverbindung aufzubauen

Eine Datenbankverbindung solltest Du dann aufbauen, wenn Du sie brauchst und nicht beim Start der Anwendung.
Wenn Du aus irgendeinem Grund dauerhaft eine Verbindung offen halten musst, ok, aber ich wüsste keinen Grund, warum das notwendig sein sollte.

Also ich verwalte meine "Services" derzeit alle im MainViewModel und übergebe die per DI an die anderen VMs.

Das kann ein DI-Container übernehmen. Da registrierst Du alle Services mit ihren Lifetimes und der kümmert sich dann um die Erstellung inklusive der Abhängigkeiten.
Das erfordert etwas Umdenken und Zeit, um ein Gefühl dafür zu entwickeln, da Du nicht mehr "geradeaus" arbeitest und erzeugst, was Du brauchst, sondern ausschließlich rein gereicht bekommst.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Mein Vorschlag ist aber auch keine Logik in dem Sinne, sondern eine Art selbst gebautes Event-Binding, um das ViewModel über View-Änderungen zu informieren, ohne dass das ViewModel dabei von der View abhängig ist.
Wenn Du darauf verzichten willst, kannst Du aber auch die genannten Alternativen Behavior oder AttachedProperty nutzen, damit kannst Du sozusagen den Code "auslagern".
Es bleibt dann immer noch eine Art CodeBehind, aber es ist unabhängig von der konkreten View und kann wiederverwendet werden.

Danke für deine Tipps!

Verstehe ich das richtig, ich abonniere mir im MainViewModel z.B. OnServicesLoaded um dann darin z.B. die ViewModels der Objekte usw. initalisieren zu können? Das Event trigger ich dann von der App.xaml.cs wenn ich meine License-, Db-,... Services erstellt habe. Also damit ich Code im Konstruktor im ViewModel vermeide?

5.657 Beiträge seit 2006
vor 3 Jahren

Der Konstruktor eines ViewModels ist der denkbar schlechteste Ort, um ein Dialogfenster anzuzeigen. Oder überhaupt, um irgendeine Logik auszuführen. Du schreibst leider nicht, warum du glaubst, daß das notwendig ist. Daher kann man auch schlecht eine Alternative vorschlagen.

Aber wenn du einen Dialog anzeigen mußt, sobald z.B. ein Fenster geöffnet wird, dann gibt es dafür das entsprechende Ereignis, auf das du reagieren kannst.

Weeks of programming can save you hours of planning

2.078 Beiträge seit 2012
vor 3 Jahren
public class MyViewModel : IViewObserver
{
    void IViewObserver.OnViewLoaded(...) { ... }
}

public interface IViewObserver
{
    void OnViewLoaded(...) { ... }
}

public class MyWindow : Window
{
    private void OnLoaded(...)
    {
        (DataContext as IViewObserver)?.OnViewLoaded(...);
    }
}

Keine Garantie, dass die Namen stimmen

Den Teil im Window kann man auch raus ziehen, z.B. eine LoadedCommand-DependencyProperty, an die man dann ein passendes Command vom ViewModel binden kann. Oder ein Behavior, dass den CodeBehind in eine eigene Klasse ziehen kann.
Du kannst das natürlich auch in der App.xaml.cs regeln, allerdings bist Du da eingeschränkter, als beim Window oder den Controls.

Wenn ich so darüber nachdenke, wäre die LoadedCommand-Property mein Favorit, dann hast Du es direkt im XAML stehen und ohne Umwege den üblichen Weg über's Binding.

Ob das nun für dich die optimale Lösung ist, kannst nur Du wissen.