Laden...

Wie implementiere ich einen Zurück-Button in einer App-Navigation mit MVVM?

Erstellt von CombatKarl vor 3 Jahren Letzter Beitrag vor 3 Jahren 2.269 Views
CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren
Wie implementiere ich einen Zurück-Button in einer App-Navigation mit MVVM?

Hallo zusammen,

ich benötige mal bitte erneut eure Hilfe. Ich versuche seit einiger Zeit mich mit MVVM vertraut zu machen und kann für mich auch sicherlich schon den einen oder anderen Fortschritt verzeichnen, jedoch bin ich dabei auf einen Umstand gestolpert, bei dem ich nicht weiterkomme und wirklich Hilfe bei Google hab ich bisher nicht gefunden bzw. wusste ich nicht, wonach ich effektiv suchen soll.

Erklärung und Beschreibung (Skizze im Anhang)

Mein MainWindow enthält eine UserControl, welche nur 4 Buttons ( Btn 1 - 4) beinhaltet und eine ContentControl, die für die Anzeige meiner 4 Views verantwortlich ist. Die Buttons sind jeweils an ein Command gebunden, welches das relevante ViewModel (VM 1 - 4) ändert und dadurch der verknüpfte View (View 1 - 4, ebenfalls alles UserControls) angezeigt wird. Funktioniert alles prima mit ICommand & INotifyPropertyChanged (etc.).

Mein View 4 beinhaltet jetzt wiederum eine weitere UserControl mit 2 Buttons (Btn 5 & 6) und ein ContentControl, welche ebenfalls den mein MainViewModel als DataContext besitzt, allerdings an eine andere Property gebunden ist, um View 5 & View 6 darin anzuzeigen.(funktioniert auch alles fein !!)

Frage & Problem

Wie muss mein Command für Btn 6 gestaltet sein, dass dieses nicht meinen View anzeigt, sondern zurückspringt und im ContentControl des MainWindows meinen View 1 anzeigt (quasi ein Zurück-Button). Jeden Versuch den ich unternommen haben, hat meinen View 1 immer nur im ContentControl des Views selbst anzeigen lassen.

Ich hoffe ich hab es irgendwie transparent darstellen können und ihr ballert mich mit Tipps und Infos zu.

Herzlichen Dank dafür schon mal im Voraus !!

<--- Wer übt, ist feige ! --->

5.658 Beiträge seit 2006
vor 3 Jahren

ViewModel4 könnte ein Event besitzen, das beim Klick auf den Button ausgelöst wird. Das MainViewModell reagiert dann auf das Event, indem es wieder zur ersten View navigiert.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zurück,

so denke ich es auch in der Theorie, nur leider scheitert es dann etwas an der praktischen Umsetzung. Gibt's vielleicht irgendwie vergleichbaren Quellcode zu finden, den ich dann anpassen könnte ?

Danke

<--- Wer übt, ist feige ! --->

5.658 Beiträge seit 2006
vor 3 Jahren

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zurück,

ich muss leider das Handtuch werfen. Ich komme ehrlich gesagt nicht klar mit dem Konstrukt aus Event und ViewModel. Ich wäre dankbar, wenn ich hier einfach mal einen BeispielCode posten kann und ihr mir dann den notwendigen Code ergänzt. Damit erarbeite ich es mir leichter, wenn ich die fehlenden Puzzle für meinen Code anpasse.

Ihr sollt mir nicht die Arbeit abnehmen, aber an einem Beispiel einfach verdeutlichen.

Der zip File ist im Anhang !

Vielen Dank

<--- Wer übt, ist feige ! --->

16.835 Beiträge seit 2008
vor 3 Jahren

In Anbetracht unserer Regeln in [Hinweis] Wie poste ich richtig? habe ich das Zip entfernt.

Versteh mich nicht falsch; es ist hier nicht die Idee, dass Du irgendwelchen Code postest und wir sind die Hiwis, die ihn vervollständigen.
Der Sinn eines Forum ist, dass wir Dich bei der Problemlösung unterstützen, aber Dir nicht kostenlos den Code generieren.

Wenn wir Dir jetzt irgendwas schreiben, dann lernst Du dabei nichts.
Der Sinn ist aber, dass Du es verstehst, lernst und dann selbst zum Erfolg kommst.

5.658 Beiträge seit 2006
vor 3 Jahren

Ich komme ehrlich gesagt nicht klar mit dem Konstrukt aus Event und ViewModel.

Erkläre doch bitte mal, wo genau dein Problem liegt. Es geht doch hier lediglich darum, ein Event zu erstellen, auszulösen und zu abonnieren. Das ist alles in der Doku und in dem verlinkten Artikel beschrieben. Wenn du trotzdem etwas nicht verstehst, dann mußt du das halt mal erklären.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zusammen,

danke erstmal für die Infos. Das Regelwerk zum Posten habe ich erstmal durchgekaut und sorry für die Umstände. Anbei sind die Codes meiner beiden ViewModel (MainVM & ChildVM). Mein MainWindow hat den DataContext von MainVM und die ContentControl ist mit der NavigateTo Property verbunden. Beide Buttons des MainWindow sind mit den Commands verbunden.

Das ChildVMl ist ähnlich strukturiert mit der NavigateToChild-Property, die mit der ContentControl auf dem View2.xaml verbunden ist. Die beiden Buttons haben nutzen die Commands des ChildVM.

MainViewModel


using System;
using System.Windows.Input;

namespace Classes
{
    public class MainVM : BindableBase
    {
        private object _navigateTo;        
        public object NavigateTo
        {
            get { return this._navigateTo; }
            set { SetProperty(ref this._navigateTo, value); }
        }
        public ICommand Com1 { get; set; }
        public ICommand Com2 { get; set; }
        public MainVM()
        {
            Com1 = new BaseCommand(View1);
            Com2 = new BaseCommand(View2);
            NavigateTo = new VM1();
        }

        #region Methods
        private void View1(object obj)
        {           
            NavigateTo = new VM1();
        }

        private void View2(object obj)
        {
            NavigateTo = new VM2();
        }


        #endregion
    }
}

ChildViewModel


using System.ComponentModel.Design;
using System.Windows.Input;

namespace Classes
{
    public class ChildVM : BindableBase
    {
        private object _navigateToChild;
        public object NavigateToChild
        {
            get { return this._navigateToChild; }
            set { SetProperty(ref this._navigateToChild, value); }  
        }
        public ICommand Com3 { get; set; }
        public ICommand Com4 { get; set; }
        public ChildVM()
        {
            Com3 = new BaseCommand(View3);
            Com4 = new BaseCommand(View4);
            NavigateToChild = new VM3();
        }
        private void View3(object obj)
        {
            NavigateToChild = new VM3();
        }

        private void View4(object obj)
        {
            NavigateToChild = new VM4();
        }
    }
}

Ich verstehe das nun so, dass auf dem ChildVM das Event erstellt werden muss. Nur wie löse ich dieses Event aus, um mit Com4 - Command das ViewModel VM1 zu laden und den korrespondieren View1 im ContentControl des MainWindows anzeigen zu lassen ?

Danke für Eure Hilfe.

<--- Wer übt, ist feige ! --->

5.658 Beiträge seit 2006
vor 3 Jahren

Leider zeigtst du nicht, wo du das Event definiert hast, und wo ChildVM initialisiert wird. Offenbar hast du doch ein paar mehr Schichten, als im Eingangsbeitrag, wo es noch halbwegs übersichtlich klingt.

Es gibt mehrere Möglichkeiten:

  • Das Event durch die verschiedenen Schichten (VM4 => ChildVM => MainVM?) durchreichen

  • Ein Command in die andere Richtung durchreichen (MainVM => ChildVM => VM4):


public class MainVM : BindableBase
{
  private ICommand navigateToHomePageCommand = new BaseCommand(NavigateToHomepage);
  private ICommand navigateToChildViewCommand = new BaseCommand(NavigateToChildView);

  public void NavigateToHomepage(object parameter)
  {
     NavigateTo = new HomePageVM();
  }

  public void NavigateToChildView(object parameter)
  {
     NavigateTo = new VMXXX(navigateToHomePageCommand);
  }
}

So kann der Button direkt auf das navigateToHomePageCommand gebunden werden.

  • Die Navigation in einen NavigationService auslagern und diesen (z.B. per Dependency Injection) an alle ViewModels übergeben. Dann kannst du diesen Service von überall verwenden, um zu einer Seite zu navigieren, und das MainViewModel kann auf die Änderungen reagieren.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Ist mir auch grad aufgefallen (da dies ja nur Beispielcode ist), dass das ChildVM an der falschen Stelle initialisiert wird. Anbei der korrigierte Quellcode.

MainViewModel


using System;
using System.Windows.Input;

namespace Classes
{
    public class MainVM : BindableBase
    {
        private object _navigateTo;
        public object NavigateTo
        {
            get { return this._navigateTo; }
            set { SetProperty(ref this._navigateTo, value); }
        }
        public ICommand Com1 { get; set; }
        public ICommand Com2 { get; set; }
        public MainVM()
        {
            Com1 = new BaseCommand(View1);
            Com2 = new BaseCommand(View2);
            NavigateTo = new VM1();
        }

        #region Methods
        private void View1(object obj)
        {           
            NavigateTo = new VM1();
        }
        private void View2(object obj)
        {
            NavigateTo = new ChildVM();
        }
        #endregion
    }
}

ChildViewModel


using System;
using System.Windows.Input;

namespace Classes
{
    public class ChildVM : BindableBase
    {

        private object _navigateToChild;
        public object NavigateToChild
        {
            get { return this._navigateToChild; }
            set { SetProperty(ref this._navigateToChild,value); }
        }
        public event EventHandler<EventArgs> BackToFirstView;
        public ICommand Com3 { get; set; }
        public ICommand Com4 { get; set; }
        public ChildVM()
        {
            Com3 = new BaseCommand(View3);
            Com4 = new BaseCommand(View4);
            NavigateToChild = new VM3();
        }
        private void View3(object obj)
        {
            NavigateToChild = new VM3();
        }

        private void View4(object obj)
        {
            NavigateToChild = new VM4();
        }
    }
}

Den Eventhandler habe ich im ChildVM deklariert, weil dies ja das VM ist, welches da Event auslösen muss.

Ist mein Ansatz bisher richtig ? Wie führe ich meinen Code weiter, damit Com4 das VM1 initialisiert und damit View1 in der ContentControl vom MainWindow angezeigt wird?

Danke

<--- Wer übt, ist feige ! --->

A
764 Beiträge seit 2007
vor 3 Jahren
  • Die Navigation in einen NavigationService auslagern und diesen (z.B. per Dependency Injection) an alle ViewModels übergeben. Dann kannst du diesen Service von überall verwenden, um zu einer Seite zu navigieren, und das MainViewModel kann auf die Änderungen reagieren.

Hallo CombatKarl

Das was du vorhast, kann schnell komplex werden, wie du das schon merkst. Mit einem NavigationService kannst du die Navigations-Logik aus den Views heraushalten.
Versuch dich doch mal mit dem oben genannten Stichwort. NavigationService + MVVM
Ich habe z.B. das hier gefunden:
https://marcominerva.wordpress.com/2020/01/13/an-mvvm-aware-navigationservice-for-wpf-running-on-net-core/

Gruß
Alf

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zurück,

vielen Dank Alf für die Info. Grundsätzlich macht der Ansatz einen Navigationservice zu nutzen auch für meinen laienhaften Kenntnisstand sind. Allerdings denke ich auch, da ich grundsätzlich in meinem Programm nur einen ChildView habe, ist es vielleicht etwas wie mit Kanonen auf Spatzen.

Aber gern nehm ich mal die Herausforderung an und versuche mich mal damit, wobei ich auch bei mehrmaligem Durchlesen des Codes schnell an meine Grenzen gekommen bin.

Das Beispiel für den NavigationService ist so konstruiert, um jedes mal neue Windows zu öffnen. Kann ich das problemlos adaptieren auf meine ContentControls der einzelnen Views?

Danke

<--- Wer übt, ist feige ! --->

4.939 Beiträge seit 2008
vor 3 Jahren

Du hast doch zwei Klassen (MainVM und ChildVM), welche Navigation durchführen - daher macht es dann doch Sinn, diese Funktionalität in einen Navigationsservice auszulagern.

PS: Was mir an deinem Code noch aufgefallen ist: möchtest du wirklich jedesmal wieder eine neue VM-Klasse beim Navigieren erstellen?

16.835 Beiträge seit 2008
vor 3 Jahren

Für Navigationen (besonders in WPF) eignet sich auch https://reactiveui.net/ super.
Allgemein macht der Einsatz von Reactive Extensions bei Desktop Anwendungen sinn.

Nehmen extrem viel Komplexität ab.

5.658 Beiträge seit 2006
vor 3 Jahren

Grundsätzlich macht der Ansatz einen Navigationservice zu nutzen auch für meinen laienhaften Kenntnisstand sind. Allerdings denke ich auch, da ich grundsätzlich in meinem Programm nur einen ChildView habe, ist es vielleicht etwas wie mit Kanonen auf Spatzen.

Deswegen hatte ich ja noch zwei andere Herangehensweisen vorgeschlagen.

Das Beispiel für den NavigationService ist so konstruiert, um jedes mal neue Windows zu öffnen. Kann ich das problemlos adaptieren auf meine ContentControls der einzelnen Views?

Eigentlich braucht dein Service nur eine Methode um eine Navigation auszulösen, und ein Event, um über die Navigation zu benachrichtigen.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Deswegen hatte ich ja noch zwei andere Herangehensweisen vorgeschlagen.

Ich habe den Vorschlag aufgenommen das Command durchzureichen und habe dafür folgenden Code benutzt:


        public void View2(object obj)
        {
            NavigateTo = new ChildVM(Com1);
        }

Anschließend musste ich einen entsprechenden Konstrukor im ChildVM erstellen, der mein ICommand als Argument annimmt.

Nur wie verfahre ich jetzt auf der ChildVM Seite weiter ? Dieses ist der DataContext meines ChildViews und besitzt selbst wiederum 2 weitere Commands.

Eigentlich braucht dein Service nur eine Methode um eine Navigation auszulösen, und ein Event, um über die Navigation zu benachrichtigen.

Da bin ich aktuelle gänzlich überfordert und wüsste nicht mal, wie ich starten sollte. 🤔

<--- Wer übt, ist feige ! --->

187 Beiträge seit 2009
vor 3 Jahren

Hallo CombatKarl,

ich habe da mal eine Frage. Verwendest Du die Prism library?

public class MainVM : BindableBase

deutet jedenfalls darauf hin. Es gäbe nämlich in den Beispielprogrammen zu Prism mehrere Beispiele zur Navigation. Vielleicht helfen Dir diese weiter! Ich stand vor einigen Wochen vor dem gleichen Problem und mir haben diese Beispiele schon enorm geholfen.

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zurück,

bislang habe ich noch keine Libraries eingebunden und wollte zu Beginn auch den Part so weit es irgendwie möglich auslassen, um dabei die Prinzipien und Zusammenhänge besser zu verstehen.

Sollte das allerdings die Lösung meines Problems sein, dann werde ich da mal einen Blick riskieren.

Noch immer hänge ich an dem Punkt, nachdem das Command dem ViewModel übergeben wurde und das ViewModel einen entsprechenden Constructor besitzt, wie der Code weiterzuführen ist auf ViewModel-Seite.

Danke

<--- Wer übt, ist feige ! --->

5.658 Beiträge seit 2006
vor 3 Jahren

Noch immer hänge ich an dem Punkt, nachdem das Command dem ViewModel übergeben wurde und das ViewModel einen entsprechenden Constructor besitzt, wie der Code weiterzuführen ist auf ViewModel-Seite.

Du hast ein Command und einen Button, der das Command auslösen soll. Dann mußt du nur das Command an den Button binden:

<Button Command="{Binding MyCommand}" />

Ich empfehle, dir mal diesen Artikel durchzulesen, besonders den Abschnitt zu Commands: [Artikel] MVVM und DataBinding

Eigentlich braucht dein Service nur eine Methode um eine Navigation auszulösen, und ein Event, um über die Navigation zu benachrichtigen.

Da bin ich aktuelle gänzlich überfordert und wüsste nicht mal, wie ich starten sollte. 👶

Du mußt ein Event erstellen, dieses Event auslösen, und dann auf das Event reagieren. Das wird alles in dem oben verlinkten Artikel zu Events erklärt.

Wenn das im Moment noch zu kompliziert ist, solltest du dich nochmal mit den Basics beschäftigen, bevor du WPF-Programme entwickelst. Das sind die Grundlagen, die überall benötigt werden. Siehe auch: [FAQ] Wie finde ich den Einstieg in C#?

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zusammen,

nach all dem Durchlesen und Durchforsten der hier angebotenen Quellen ist der Knoten endlich geplatzt und ich habe mein Problem lösen können. Schlussendlich arbeitet ein Navigation - Interface im Hintergrund, das auf meine Navigationviewmodel implementiert ist. Um dann zwischen den verschiedenen ContentControls hin und her zu navigieren übergeben ich das Navigationviewmodel samt Commands an den Konstruktor des jeweiligen ViewModels.

Läuft bestens...

Also nochmals vielen Dank.

<--- Wer übt, ist feige ! --->