Ich versuche, WPF und das MVVM-Problem zu lernen, habe aber einen Haken bekommen. Diese Frage ist ähnlich, aber nicht ganz dieselbe wie diese (Umgang mit Dialogen in wpf-mit-mvvm) ...
Ich habe ein "Login" -Formular, das mit dem MVVM-Muster geschrieben wurde.
Dieses Formular verfügt über ein ViewModel, das den Benutzernamen und das Kennwort enthält, die mithilfe normaler Datenbindungen an die Ansicht in der XAML gebunden sind. Es hat auch einen "Login" -Befehl, der an die "Login" -Schaltfläche im Formular gebunden ist, auch unter Verwendung der normalen Datenbindung.
Wenn der Befehl "Anmelden" ausgelöst wird, ruft er eine Funktion im ViewModel auf, die ausgelöst wird und Daten über das Netzwerk sendet, um sich anzumelden. Wenn diese Funktion abgeschlossen ist, gibt es zwei Aktionen:
Das Login war ungültig - wir zeigen nur eine MessageBox und alles ist in Ordnung
Der Login war gültig, wir müssen das Login-Formular schließen und es als
DialogResult
...
Das Problem ist, dass das ViewModel nichts über die tatsächliche Ansicht weiß. Wie kann es also die Ansicht schließen und anweisen, ein bestimmtes DialogResult zurückzugeben? Ich könnte etwas Code in das CodeBehind stecken und / oder die Ansicht an das ViewModel weiterleiten, aber das scheint den ganzen Sinn von MVVM völlig zu zerstören ...
Aktualisieren
Am Ende habe ich nur die "Reinheit" des MVVM-Musters verletzt und die Ansicht ein Closed
Ereignis veröffentlichen lassen und eine Close
Methode verfügbar machen lassen . Das ViewModel würde dann einfach aufrufen view.Close
. Die Ansicht ist nur über eine Schnittstelle bekannt und über einen IOC-Container verkabelt, sodass keine Testbarkeit oder Wartbarkeit verloren geht.
Es scheint ziemlich dumm, dass die akzeptierte Antwort bei -5 Stimmen liegt! Während ich mir der guten Gefühle bewusst bin, die man bekommt, wenn man ein Problem löst, während man "rein" ist, bin ich sicherlich nicht der einzige, der glaubt, dass 200 Zeilen mit Ereignissen, Befehlen und Verhaltensweisen nur eine einzeilige Methode vermeiden Der Name "Muster" und "Reinheit" ist ein bisschen lächerlich ....
Close
Methode immer noch die beste Lösung ist. Alles andere in den anderen komplexeren Dialogen ist MVVM und datengebunden, aber es schien nur albern, die riesigen "Lösungen" hier anstelle einer einfachen Methode zu implementieren ...Antworten:
Ich war inspiriert von Thejuans Antwort , eine einfachere angehängte Eigenschaft zu schreiben. Keine Stile, keine Auslöser; Stattdessen können Sie dies einfach tun:
Dies ist fast so sauber, als hätte das WPF-Team alles richtig gemacht und DialogResult überhaupt zu einer Abhängigkeitseigenschaft gemacht.
bool? DialogResult
Fügen Sie einfach eine Eigenschaft in Ihr ViewModel ein und implementieren Sie INotifyPropertyChanged. Voilà, Ihr ViewModel kann das Fenster schließen (und sein DialogResult festlegen), indem Sie einfach eine Eigenschaft festlegen. MVVM wie es sein sollte.Hier ist der Code für DialogCloser:
Ich habe dies auch in meinem Blog gepostet .
quelle
Aus meiner Sicht ist die Frage ziemlich gut, da der gleiche Ansatz nicht nur für das "Anmelde" -Fenster verwendet wird, sondern für jede Art von Fenster. Ich habe viele Vorschläge geprüft und keine ist für mich in Ordnung. Bitte überprüfen Sie meinen Vorschlag, der dem MVVM-Entwurfsmusterartikel entnommen wurde .
Jede ViewModel-Klasse sollte davon erben,
WorkspaceViewModel
dass sie dasRequestClose
Ereignis und dieCloseCommand
Eigenschaft desICommand
Typs hat. Die Standardimplementierung derCloseCommand
Eigenschaft löst dasRequestClose
Ereignis aus.Um das Fenster zu schließen, sollte die
OnLoaded
Methode Ihres Fensters überschrieben werden:oder
OnStartup
Methode Ihrer App:Ich denke, dass die Implementierung von
RequestClose
Ereignissen undCloseCommand
Eigenschaften in derWorkspaceViewModel
ziemlich klar ist, aber ich werde zeigen, dass sie konsistent sind:Und der Quellcode des
RelayCommand
:PS Behandle mich nicht schlecht für diese Quellen! Wenn ich sie gestern gehabt hätte, hätte mir das ein paar Stunden gespart ...
PPS Kommentare oder Vorschläge sind willkommen.
quelle
customer.RequestClose
im Code hinter Ihrer XAML-Datei eingebunden haben, verstößt nicht gegen das MVVM-Muster? Sie hätten sich genauso gut an denClick
Ereignishandler auf Ihrer Schaltfläche zum Schließen binden können, wenn Sie den Code dahinter ohnehin berührt und einethis.Close()
! Richtig?Ich habe angehängte Verhaltensweisen verwendet, um das Fenster zu schließen. Binden Sie eine "Signal" -Eigenschaft in Ihrem ViewModel an das angehängte Verhalten (ich verwende tatsächlich einen Trigger). Wenn es auf "true" gesetzt ist, schließt das Verhalten das Fenster.
http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/
quelle
Es gibt viele Kommentare, die die Vor- und Nachteile von MVVM hier diskutieren. Für mich stimme ich Nir zu; Es geht darum, das Muster richtig zu verwenden, und MVVM passt nicht immer. Die Leute scheinen bereit zu sein, alle wichtigen Prinzipien des Software-Designs NUR zu opfern, um es an MVVM anzupassen.
Das heißt, ich denke, Ihr Fall könnte gut zu ein bisschen Refactoring passen.
In den meisten Fällen, auf die ich gestoßen bin, können Sie mit WPF OHNE mehrere
Window
s auskommen . Vielleicht könnten Sie versuchen,Frame
s undPage
s anstelle von Windows mitDialogResult
s zu verwenden.In Ihrem Fall wäre mein Vorschlag, das zu
LoginFormViewModel
behandelnLoginCommand
und wenn die Anmeldung ungültig ist, setzen Sie eine Eigenschaft aufLoginFormViewModel
einen geeigneten Wert (false
oder einen Aufzählungswert wieUserAuthenticationStates.FailedAuthentication
). Sie würden dasselbe für eine erfolgreiche Anmeldung (true
oder einen anderen Aufzählungswert) tun . Sie würden dann ein verwenden,DataTrigger
das auf die verschiedenen Benutzerauthentifizierungszustände reagiert und ein einfaches verwenden könnteSetter
, um dieSource
Eigenschaft des zu ändernFrame
.Wenn Sie Ihr Anmeldefenster zurückgeben, werden Sie meiner
DialogResult
Meinung nach verwirrt. DasDialogResult
ist wirklich eine Eigenschaft Ihres ViewModel. In meiner zugegebenermaßen begrenzten Erfahrung mit WPF, wenn sich etwas normalerweise nicht richtig anfühlt, weil ich darüber nachdenke, wie ich dasselbe in WinForms gemacht hätte.Hoffentlich hilft das.
quelle
Angenommen, Ihr Anmeldedialog ist das erste Fenster, das erstellt wird, versuchen Sie dies in Ihrer LoginViewModel-Klasse:
quelle
Dies ist eine einfache und saubere Lösung. Sie fügen dem ViewModel ein Ereignis hinzu und weisen das Fenster an, sich selbst zu schließen, wenn dieses Ereignis ausgelöst wird.
Weitere Informationen finden Sie in meinem Blogbeitrag " Fenster schließen" von ViewModel .
XAML:
ViewModel:
Hinweis: In diesem Beispiel werden Prismas verwendet
DelegateCommand
(siehe Prisma: Befehle ), es kann jedoch jedeICommand
Implementierung verwendet werden.Sie können Verhaltensweisen aus diesem offiziellen Paket verwenden.
quelle
Ich würde damit umgehen, indem ich meinem ViewModel einen Ereignishandler hinzufüge. Wenn der Benutzer erfolgreich angemeldet war, würde ich das Ereignis auslösen. In meiner Ansicht würde ich an dieses Ereignis anhängen und wenn es ausgelöst wird, würde ich das Fenster schließen.
quelle
Hier ist, was ich anfangs getan habe, was funktioniert, aber es scheint ziemlich langatmig und hässlich zu sein (globale Statik, alles ist nie gut)
1: App.xaml.cs
2: LoginForm.xaml
3: LoginForm.xaml.cs
4: LoginFormViewModel.cs
Ich habe später dann den ganzen Code entfernt und hatte gerade den
LoginFormViewModel
Aufruf der Close-Methode in seiner Ansicht. Es war viel schöner und leichter zu verfolgen. IMHO geht es bei Mustern darum, den Leuten einen einfacheren Weg zu geben, um zu verstehen, was Ihre App tut. In diesem Fall machte MVVM das Verständnis weitaus schwieriger, als wenn ich es nicht verwendet hätte, und war jetzt ein Anti- Muster.quelle
Zu Ihrer Information, ich bin auf dasselbe Problem gestoßen und ich denke, ich habe eine Lösung gefunden, die keine globalen oder statischen Elemente erfordert, obwohl dies möglicherweise nicht die beste Antwort ist. Ich überlasse es euch, das selbst zu entscheiden.
In meinem Fall kennt das ViewModel, das das anzuzeigende Fenster instanziiert (nennen wir es ViewModelMain), auch das LoginFormViewModel (am Beispiel der obigen Situation).
Also habe ich eine Eigenschaft im LoginFormViewModel vom Typ ICommand erstellt (nennen wir es CloseWindowCommand). Bevor ich dann .ShowDialog () im Fenster aufrufe, setze ich die CloseWindowCommand-Eigenschaft im LoginFormViewModel auf die window.Close () -Methode des von mir instanziierten Fensters. Dann muss ich im LoginFormViewModel nur noch CloseWindowCommand.Execute () aufrufen, um das Fenster zu schließen.
Ich nehme an, es ist eine Art Workaround / Hack, aber es funktioniert gut, ohne das MVVM-Muster wirklich zu brechen.
Fühlen Sie sich frei, diesen Prozess so oft zu kritisieren, wie Sie möchten, ich kann es nehmen! :) :)
quelle
Dies ist wahrscheinlich sehr spät, aber ich bin auf dasselbe Problem gestoßen und habe eine Lösung gefunden, die für mich funktioniert.
Ich kann nicht herausfinden, wie man eine App ohne Dialoge erstellt (vielleicht ist es nur ein Mind Block). Also war ich mit MVVM in einer Sackgasse und zeigte einen Dialog. Also bin ich auf diesen CodeProject-Artikel gestoßen:
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
Dies ist ein UserControl, mit dem sich ein Fenster im visuellen Baum eines anderen Fensters befinden kann (in xaml nicht zulässig). Außerdem wird eine boolesche DependencyProperty namens IsShowing verfügbar gemacht.
Sie können einen Stil wie in einem Ressourcenwörterbuch festlegen, der den Dialog grundsätzlich immer dann anzeigt, wenn die Content-Eigenschaft des Steuerelements! = Null über Trigger:
In der Ansicht, in der Sie den Dialog anzeigen möchten, haben Sie einfach Folgendes:
In Ihrem ViewModel müssen Sie lediglich die Eigenschaft auf einen Wert setzen (Hinweis: Die ViewModel-Klasse muss INotifyPropertyChanged unterstützen, damit die Ansicht weiß, dass etwas passiert ist).
wie so:
Um das ViewModel mit der Ansicht abzugleichen, sollten Sie Folgendes in einem Ressourcenwörterbuch haben:
Mit all dem erhalten Sie einen Einzeiler-Code, um den Dialog anzuzeigen. Das Problem ist, dass Sie den Dialog nicht wirklich nur mit dem obigen Code schließen können. Aus diesem Grund müssen Sie ein Ereignis in eine ViewModel-Basisklasse einfügen, von der DisplayViewModel erbt, und anstelle des obigen Codes dieses schreiben
Anschließend können Sie das Ergebnis des Dialogs über den Rückruf bearbeiten.
Dies mag etwas komplex erscheinen, aber sobald die Grundlagen geschaffen sind, ist es ziemlich einfach. Auch dies ist meine Implementierung, ich bin mir sicher, dass es noch andere gibt :)
Hoffe das hilft, es hat mich gerettet.
quelle
Ok, diese Frage ist fast 6 Jahre alt und ich kann hier immer noch nicht finden, was ich für die richtige Antwort halte. Erlauben Sie mir also, meine "2 Cent" zu teilen ...
Ich habe tatsächlich zwei Möglichkeiten, die erste ist die einfache ... die zweite auf der rechten. Wenn Sie also nach der richtigen suchen, überspringen Sie einfach # 1 und springen Sie zu # 2 :
1. Schnell und einfach (aber nicht vollständig)
Wenn ich nur ein kleines Projekt habe, erstelle ich manchmal einfach eine CloseWindowAction im ViewModel:
Und wer auch immer die Ansicht erstellt oder im Code der Ansicht dahinter, ich habe nur die Methode festgelegt, die die Aktion aufruft:
(Denken Sie daran, bei MVVM geht es um die Trennung von Ansicht und ViewModel. Der Code der Ansicht ist immer noch die Ansicht. Solange eine ordnungsgemäße Trennung vorliegt, verletzen Sie das Muster nicht.)
Wenn ein ViewModel ein neues Fenster erstellt:
Oder wenn Sie es in Ihrem Hauptfenster haben möchten, platzieren Sie es einfach unter dem Konstruktor Ihrer Ansicht:
Wenn Sie das Fenster schließen möchten, rufen Sie einfach die Aktion in Ihrem ViewModel auf.
2. Der richtige Weg
Die richtige Vorgehensweise ist nun die Verwendung von Prisma (IMHO). Alles darüber finden Sie hier .
Sie können eine Interaktionsanforderung stellen , sie mit den Daten füllen , die Sie in Ihrem neuen Fenster benötigen, sie zu Mittag essen, schließen und sogar Daten zurückerhalten . All dies gekapselt und MVVM genehmigt. Sie erhalten sogar einen Status darüber, wie das Fenster geschlossen wurde , z. B. ob der Benutzer
Canceled
oderAccepted
(OK) das Fenster und Daten zurück, wenn Sie sie benötigen . Es ist etwas komplizierter und Antwort Nr. 1, aber es ist viel vollständiger und ein empfohlenes Muster von Microsoft.Der Link, den ich gegeben habe, enthält alle Codefragmente und Beispiele, sodass ich keinen Code hier einfügen muss. Lesen Sie einfach den Artikel zum Herunterladen des Prisma-Schnellstarts und führen Sie ihn aus. Es ist wirklich einfach, etwas ausführlicher zu verstehen Damit es funktioniert, sind die Vorteile größer als nur das Schließen eines Fensters.
quelle
+=
diese Option, um einen Delegaten hinzuzufügen, und rufen Sie die Aktion auf. Sie wird alle auslösen . Oder Sie werden es tun Sie müssen eine spezielle Logik für Ihre VM erstellen, damit sie weiß, welches Fenster geschlossen werden muss (möglicherweise eine Sammlung von Abschlussaktionen). Ich denke jedoch, dass es nicht empfehlenswert ist, mehrere Ansichten an eine VM zu binden Es ist besser, eine Ansicht und eine VM-Instanz miteinander zu verknüpfen und möglicherweise eine übergeordnete VM, die alle untergeordneten VMs verwaltet, die an alle Ansichten gebunden sind.quelle
Das ViewModel kann ein Ereignis verfügbar machen, für das sich die Ansicht registriert. Wenn das ViewModel dann entscheidet, wann die Ansicht geschlossen werden soll, wird das Ereignis ausgelöst, durch das die Ansicht geschlossen wird. Wenn Sie möchten, dass ein bestimmter Ergebniswert zurückgegeben wird, haben Sie dafür eine Eigenschaft im ViewModel.
quelle
Um die enorme Anzahl von Antworten zu ergänzen, möchte ich Folgendes hinzufügen. Angenommen, Sie haben einen ICommand in Ihrem ViewModel und möchten, dass dieser Befehl sein Fenster (oder eine andere Aktion) schließt, können Sie Folgendes verwenden.
Es ist nicht perfekt und möglicherweise schwierig zu testen (da es schwierig ist, eine statische Aufladung zu verspotten / zu stoppen), aber es ist sauberer (IMHO) als die anderen Lösungen.
Erick
quelle
Ich habe die Lösung von Joe White implementiert, bin jedoch auf Probleme mit gelegentlichen Fehlern " DialogResult kann erst festgelegt werden, nachdem das Fenster erstellt und als Dialog angezeigt wird " gestoßen .
Ich habe das ViewModel beibehalten, nachdem die Ansicht geschlossen wurde, und gelegentlich habe ich später eine neue Ansicht mit derselben VM geöffnet. Es scheint, dass das Schließen der neuen Ansicht vor der Speicherbereinigung der alten Ansicht dazu führte, dass DialogResultChanged versuchte, die DialogResult- Eigenschaft im geschlossenen Fenster festzulegen , wodurch der Fehler ausgelöst wurde .
Meine Lösung bestand darin, DialogResultChanged zu ändern , um die IsLoaded- Eigenschaft des Fensters zu überprüfen :
Nach dieser Änderung werden alle Anhänge zu geschlossenen Dialogen ignoriert.
quelle
Am Ende habe ich die Antwort von Joe White und einen Code aus der Antwort von Adam Mills gemischt , da ich ein Benutzersteuerelement in einem programmgesteuert erstellten Fenster anzeigen musste. Der DialogCloser muss sich also nicht im Fenster befinden, sondern kann sich auf dem Benutzersteuerelement selbst befinden
Und der DialogCloser findet das Fenster des Benutzersteuerelements, wenn es nicht an das Fenster selbst angehängt wurde.
quelle
Verhalten ist hier der bequemste Weg.
Einerseits kann es an das angegebene Ansichtsmodell gebunden werden (dies kann signalisieren, dass das Formular geschlossen wird!)
Von einer anderen Seite hat es Zugriff auf das Formular selbst, sodass er die erforderlichen formularspezifischen Ereignisse abonnieren oder einen Bestätigungsdialog oder etwas anderes anzeigen kann.
Das Schreiben des notwendigen Verhaltens kann beim ersten Mal als langweilig angesehen werden. Von nun an können Sie es jedoch für jedes einzelne Formular, das Sie benötigen, mit einem exakten einzeiligen XAML-Snippet wiederverwenden. Bei Bedarf können Sie es als separate Baugruppe extrahieren, damit es in jedes gewünschte nächste Projekt aufgenommen werden kann.
quelle
Warum nicht einfach das Fenster als Befehlsparameter übergeben?
C #:
XAML:
quelle
Window
Typ zu beschränken , der etwas nicht "reine" MVVM ist. In dieser Antwort wird die VM nicht auf einWindow
Objekt beschränkt.Eine andere Lösung besteht darin, eine Eigenschaft mit INotifyPropertyChanged im Ansichtsmodell wie DialogResult zu erstellen und dann in Code Behind Folgendes zu schreiben:
Das wichtigste Fragment ist
_someViewModel_PropertyChanged
.DialogResultPropertyName
kann eine öffentliche const-Zeichenfolge seinSomeViewModel
.Ich verwende diese Art von Trick, um einige Änderungen in den Ansichtssteuerelementen vorzunehmen, falls dies in ViewModel schwierig ist. OnPropertyChanged in ViewModel können Sie in View alles tun, was Sie wollen. ViewModel ist immer noch "Unit-testbar" und einige kleine Codezeilen im Code dahinter machen keinen Unterschied.
quelle
Ich würde diesen Weg gehen:
quelle
Ich habe alle Antworten gelesen, aber ich muss sagen, die meisten sind einfach nicht gut genug oder noch schlimmer.
Sie können dies mit der DialogService- Klasse wunderbar handhaben , deren Aufgabe es ist, das Dialogfenster anzuzeigen und das Dialogergebnis zurückzugeben. Ich habe ein Beispielprojekt erstellt , das die Implementierung und Verwendung demonstriert.
Hier sind die wichtigsten Teile:
Ist das nicht einfach einfacher? direkter, lesbarer und nicht zuletzt einfacher zu debuggen als EventAggregator oder andere ähnliche Lösungen?
Wie Sie sehen können, habe ich in meinen Ansichtsmodellen den ersten in meinem Beitrag hier beschriebenen ViewModel-Ansatz verwendet: Best Practice für den Aufruf von View aus ViewModel in WPF
In der realen Welt
DialogService.ShowDialog
müssen natürlich mehr Optionen zum Konfigurieren des Dialogfelds vorhanden sein, z. B. Schaltflächen und Befehle, die ausgeführt werden sollen. Es gibt verschiedene Möglichkeiten, dies zu tun, aber es liegt außerhalb des Anwendungsbereichs :)quelle
Dies beantwortet zwar nicht die Frage, wie dies über das Ansichtsmodell zu tun ist, zeigt jedoch, wie dies nur mit XAML + dem Blend-SDK erfolgt.
Ich habe mich entschieden, zwei Dateien aus dem Blend SDK herunterzuladen und zu verwenden, die Sie beide als Paket von Microsoft über NuGet verwenden können. Die Dateien sind:
System.Windows.Interactivity.dll und Microsoft.Expression.Interactions.dll
Microsoft.Expression.Interactions.dll bietet Ihnen nützliche Funktionen wie die Möglichkeit, Eigenschaften festzulegen oder eine Methode für Ihr Ansichtsmodell oder ein anderes Ziel aufzurufen, und enthält auch andere Widgets.
Einige XAML:
Beachten Sie, dass Sie, wenn Sie nur ein einfaches OK / Abbrechen-Verhalten anstreben, mit den Eigenschaften IsDefault und IsCancel davonkommen können, solange das Fenster mit Window.ShowDialog () angezeigt wird.
Ich persönlich hatte Probleme mit einer Schaltfläche, bei der die IsDefault-Eigenschaft auf true gesetzt war, die jedoch beim Laden der Seite ausgeblendet wurde. Es schien nicht gut spielen zu wollen, nachdem es gezeigt wurde, also setze ich stattdessen nur die Window.DialogResult-Eigenschaft wie oben gezeigt und es funktioniert für mich.
quelle
Hier ist die einfache fehlerfreie Lösung (mit Quellcode). Sie funktioniert für mich.
Leiten Sie Ihr ViewModel von ab
INotifyPropertyChanged
Erstellen Sie eine beobachtbare Eigenschaft CloseDialog in ViewModel
}}
Fügen Sie in Hand einen Handler für diese Eigenschaftsänderung hinzu
Jetzt bist du fast fertig. Im Event-Handler machen
DialogResult = true
quelle
Erstellen Sie ein
Dependency Property
in IhremView
/ anyUserControl
(oderWindow
Sie möchten schließen). Wie unten:Und binden Sie es aus der Eigenschaft Ihres ViewModel :
Eigentum in
VeiwModel
:Lösen Sie nun den Schließvorgang aus, indem Sie den
CloseWindow
Wert in ViewModel ändern . :) :)quelle
Wenn Sie das Fenster schließen müssen, fügen Sie dies einfach in das Ansichtsmodell ein:
ta-da
quelle
Das ist genug!
quelle