Umgang mit Dialogen in WPF mit MVVM

235

Im MVVM-Muster für WPF ist die Behandlung von Dialogen eine der komplexeren Operationen. Da Ihr Ansichtsmodell nichts über die Ansicht weiß, kann die Dialogkommunikation interessant sein. Ich kann zeigen, ICommanddass beim Aufrufen der Ansicht ein Dialogfeld angezeigt werden kann.

Kennt jemand einen guten Weg, um mit Ergebnissen aus Dialogen umzugehen? Ich spreche von Windows-Dialogen wie MessageBox.

Eine Möglichkeit, dies zu tun, bestand darin, ein Ereignis im Ansichtsmodell zu haben, das die Ansicht abonnieren würde, wenn ein Dialog erforderlich war.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Dies ist in Ordnung, bedeutet jedoch, dass für die Ansicht Code erforderlich ist, von dem ich mich fernhalten möchte.

Ray Booysen
quelle
Warum nicht an ein Hilfsobjekt in der Ansicht binden?
Paul Williams
1
Nicht sicher was du meinst.
Ray Booysen
1
Wenn ich die Frage verstehe, möchten Sie nicht, dass die VM-Dialogfelder angezeigt wird, und Sie möchten keinen Code-Behind in der Ansicht. Außerdem klingt es so, als würden Sie Befehle Ereignissen vorziehen. Ich stimme all diesen zu, daher verwende ich eine Hilfsklasse in der Ansicht, die einen Befehl zur Behandlung des Dialogfelds verfügbar macht. Ich habe diese Frage in einem anderen Thread hier beantwortet: stackoverflow.com/a/23303267/420400 . Allerdings macht der letzte Satz es klingen wie Sie nicht wollen , jeden Code überhaupt, überall in der Ansicht. Ich verstehe dieses Problem, aber der fragliche Code ist nur eine Bedingung und wird sich wahrscheinlich nicht ändern.
Paul Williams
4
Das Ansichtsmodell sollte immer für die Logik verantwortlich sein, die hinter der Erstellung des Dialogfelds steht. Das ist der ganze Grund für seine Existenz. Das heißt, es macht (und sollte) nicht das hebende Heben, die Ansicht selbst zu erstellen. Ich habe einen Artikel zu diesem Thema unter codeproject.com/Articles/820324/… geschrieben, in dem ich zeige, dass der gesamte Lebenszyklus von Dialogfeldern über die reguläre WPF-Datenbindung und ohne Unterbrechung des MVVM-Musters verwaltet werden kann.
Mark Feldman

Antworten:

131

Ich schlage vor, auf die modalen Dialoge der 90er Jahre zu verzichten und stattdessen ein Steuerelement als Overlay (Canvas + absolute Positionierung) zu implementieren, dessen Sichtbarkeit an einen Booleschen Wert in der VM gebunden ist. Näher an einem Ajax-Steuerelement.

Das ist sehr nützlich:

<BooleanToVisibilityConverter x:Key="booltoVis" />

wie in:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Hier ist, wie ich eine als Benutzersteuerung implementiert habe. Durch Klicken auf das 'x' wird das Steuerelement in einer Codezeile im Code des Benutzersteuerelements dahinter geschlossen. (Da ich meine Ansichten in einer EXE-Datei und ViewModels in einer DLL habe, habe ich kein schlechtes Gefühl bei Code, der die Benutzeroberfläche manipuliert.)

Wpf-Dialog

Jeffrey Knight
quelle
20
Ja, ich mag diese Idee auch, möchte aber ein Beispiel für dieses Steuerelement in Bezug auf die Anzeige und das Abrufen von Dialogergebnissen usw. sehen. Insbesondere im MVVM-Szenario in Silverlight.
Roboblob
16
Wie verhindern Sie, dass der Benutzer mit Steuerelementen unter dieser Dialogüberlagerung interagiert?
Andrew Garrison
16
Das Problem bei diesem Ansatz ist, dass Sie keinen zweiten modalen Dialog vom ersten öffnen können, zumindest nicht ohne einige schwerwiegende Änderungen am Overlay-System ...
Thomas Levesque
6
Ein weiteres Problem bei diesem Ansatz ist, dass der "Dialog" nicht verschoben werden kann. In unseren Anwendungen müssen wir einen beweglichen Dialog haben, damit der Benutzer sehen kann, was dahinter steckt.
JAB
12
Dieser Ansatz erscheint mir schrecklich. Was vermisse ich? Wie ist das besser als ein echtes Dialogfeld?
Jonathan Wood
51

Sie sollten hierfür einen Mediator verwenden. Mediator ist ein allgemeines Entwurfsmuster, das auch als bekannt ist in einigen Implementierungen Messenger bezeichnet wird . Es ist ein Paradigma vom Typ Registrieren / Benachrichtigen und ermöglicht es Ihrem ViewModel und Ihren Ansichten, über einen niedrig gekoppelten Messaging-Mechanismus zu kommunizieren.

Sie sollten sich die Google WPF Disciples-Gruppe ansehen und einfach nach Mediator suchen. Sie werden mit den Antworten sehr zufrieden sein ...

Sie können jedoch damit beginnen:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

Genießen !

Bearbeiten: Die Antwort auf dieses Problem mit dem MVVM Light Toolkit finden Sie hier:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338

Roubachof
quelle
2
Marlon Grech hat gerade eine brandneue Implementierung des Mediators veröffentlicht: marlongrech.wordpress.com/2009/04/16/…
Roubachof
21
Nur eine Bemerkung: Das Mediator-Muster wurde nicht von den WPF-Jüngern eingeführt, es ist ein klassisches GoF-Muster ... ( dofactory.com/Patterns/PatternMediator.aspx ). Ansonsten schöne Antwort;)
Thomas Levesque
10
Bitte Gott, benutze keinen Vermittler oder verdammten Boten. Diese Art von Code mit Dutzenden von herumfliegenden Nachrichten ist sehr schwer zu debuggen, es sei denn, Sie können sich irgendwie an all die vielen Punkte in Ihrer gesamten Codebasis erinnern, die jedes Ereignis abonnieren und behandeln. Es wird ein Albtraum für neue Entwickler. Tatsächlich betrachte ich die gesamte MvvMLight-Bibliothek als ein massives Anti-Pattern für die allgegenwärtige und unnötige Verwendung von asynchronem Messaging. Die Lösung ist einfach: Rufen Sie einen separaten Dialogdienst (dh IDialogService) Ihres Entwurfs auf. Die Schnittstelle verfügt über Methoden und Ereignisse für Rückrufe.
Chris Bordeman
34

Ein guter MVVM-Dialog sollte:

  1. Wird nur mit XAML deklariert.
  2. Holen Sie sich das gesamte Verhalten aus der Datenbindung.

Leider bietet WPF diese Funktionen nicht an. Das Anzeigen eines Dialogfelds erfordert einen CodeBehind-Aufruf von ShowDialog(). Die Window-Klasse, die Dialoge unterstützt, kann in XAML nicht deklariert werden, sodass sie nicht einfach an die gebunden werden kann DataContext.

Um dies zu lösen, habe ich ein XAML-Stub-Steuerelement geschrieben, das sich im logischen Baum befindet und die Datenbindung an a weiterleitet und das Ein- Windowund Ausblenden des Dialogfelds behandelt. Sie finden es hier: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Es ist wirklich einfach zu bedienen und erfordert keine merkwürdigen Änderungen an Ihrem ViewModel und erfordert keine Ereignisse oder Nachrichten. Der grundlegende Aufruf sieht folgendermaßen aus:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

Sie möchten wahrscheinlich einen Stil hinzufügen, der festgelegt wird Showing. Ich erkläre es in meinem Artikel. Ich hoffe das hilft dir.

user92541
quelle
2
Das ist ein wirklich interessanter Ansatz für das Problem, Dialogfenster in MVVM anzuzeigen.
Dthrasher
2
"Showing a dialog requires a code-behind"mmm können Sie das in ViewModel
Brock Hensley
Ich würde Punkt 3 hinzufügen - Sie können sich an andere Objekte in der Ansicht binden. Wenn Sie den Code des Dialogfelds leer lassen, bedeutet dies, dass sich in der Ansicht kein C # -Code befindet, und die Datenbindung bedeutet keine Bindung an die VM.
Paul Williams
25

Ich benutze diesen Ansatz für Dialoge mit MVVM.

Jetzt muss ich nur noch Folgendes aus meinem Ansichtsmodell aufrufen.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);
blindmeis
quelle
Aus welcher Bibliothek stammt uiDialogService?
Aggietech
1
keine Bibliothek. ist nur eine kleine Schnittstelle und Implementierung: stackoverflow.com/questions/3801681/… .
Um
16

Meine aktuelle Lösung löst die meisten der von Ihnen erwähnten Probleme, ist jedoch vollständig von plattformspezifischen Dingen abstrahiert und kann wiederverwendet werden. Außerdem habe ich keine Code-Behind-Bindung nur mit DelegateCommands verwendet, die ICommand implementieren. Dialog ist im Grunde eine Ansicht - ein separates Steuerelement, das über ein eigenes ViewModel verfügt und im ViewModel des Hauptbildschirms angezeigt wird, jedoch über die DelagateCommand-Bindung von der Benutzeroberfläche ausgelöst wird.

Die vollständige Silverlight 4-Lösung finden Sie hier. Modale Dialoge mit MVVM und Silverlight 4

Roboblob
quelle
Genau wie bei @Elad Katz fehlt Ihrer Antwort der verknüpfte Inhalt. Bitte verbessern Sie Ihre Antwort, indem Sie sie einfügen, da dies hier auf SO als gute Antwort angesehen wird. Trotzdem vielen Dank für Ihren Beitrag! :)
Yoda
6

Ich hatte eine Weile mit diesem Konzept zu kämpfen, als ich MVVM lernte (noch lernte). Was ich beschlossen habe und was andere meiner Meinung nach bereits entschieden haben, was mir aber nicht klar war, ist Folgendes:

Mein ursprünglicher Gedanke war, dass ein ViewModel ein Dialogfeld nicht direkt aufrufen darf, da es kein Geschäft hat, zu entscheiden, wie ein Dialogfeld angezeigt werden soll. Aus diesem Grund begann ich darüber nachzudenken, wie ich Nachrichten weitergeben könnte, wie ich es in MVP getan hätte (dh View.ShowSaveFileDialog ()). Ich denke jedoch, dass dies der falsche Ansatz ist.

Für ein ViewModel ist es in Ordnung, einen Dialog direkt aufzurufen. Wenn Sie jedoch ein ViewModel testen, bedeutet dies, dass das Dialogfeld entweder während des Tests angezeigt wird oder insgesamt fehlschlägt (dies wurde nie wirklich versucht).

Während des Testens muss also eine "Test" -Version Ihres Dialogfelds verwendet werden. Dies bedeutet, dass Sie für jeden Dialog, den Sie haben, eine Schnittstelle erstellen und entweder die Dialogantwort verspotten oder einen Test verspotten müssen, der ein Standardverhalten aufweist.

Sie sollten bereits eine Art Service Locator oder IoC verwenden, die Sie so konfigurieren können, dass Sie je nach Kontext die richtige Version erhalten.

Mit diesem Ansatz kann Ihr ViewModel weiterhin getestet werden. Je nachdem, wie Sie Ihre Dialoge verspotten, können Sie das Verhalten steuern.

Hoffe das hilft.

Mike Rowley
quelle
6

Es gibt zwei gute Möglichkeiten, dies zu tun: 1) einen Dialogdienst (einfach, sauber) und 2) eine unterstützte Ansicht. View Assisted bietet einige nette Funktionen, ist es aber normalerweise nicht wert.

DIALOG-SERVICE

a) eine Dialogdienstschnittstelle wie über einen Konstruktor oder einen Abhängigkeitscontainer:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) Ihre Implementierung von IDialogService sollte ein Fenster öffnen (oder ein Steuerelement in das aktive Fenster einfügen), eine Ansicht erstellen, die dem Namen des angegebenen dlgVm-Typs entspricht (verwenden Sie die Containerregistrierung oder -konvention oder einen ContentPresenter mit typassoziierten DataTemplates). ShowDialogAsync sollte eine TaskCompletionSource erstellen und ihre .Task-Proptery zurückgeben. Die DialogViewModel-Klasse selbst benötigt ein Ereignis, das Sie in der abgeleiteten Klasse aufrufen können, wenn Sie schließen möchten, und in der Dialogansicht beobachten können, um den Dialog tatsächlich zu schließen / auszublenden und die TaskCompletionSource abzuschließen.

b) Rufen Sie zur Verwendung einfach await this.DialogService.ShowDialog (myDlgVm) auf Ihrer Instanz einer von DialogViewModel abgeleiteten Klasse auf. Überprüfen Sie nach dem Warten auf die Rückkehr die Eigenschaften, die Sie Ihrer Dialog-VM hinzugefügt haben, um festzustellen, was passiert ist. Sie brauchen nicht einmal einen Rückruf.

ANSICHT UNTERSTÜTZT

Dadurch hört Ihre Ansicht ein Ereignis im Ansichtsmodell ab. Dies alles könnte in ein Mischverhalten eingepackt werden, um Code-Behind und Ressourcennutzung zu vermeiden, wenn Sie dazu neigen (FMI, Unterklasse "Behavior", um eine Art Blendable-angehängte Eigenschaft für Steroide anzuzeigen). Im Moment machen wir das manuell in jeder Ansicht:

a) Erstellen Sie ein OpenXXXXXDialogEvent mit einer benutzerdefinierten Nutzlast (einer von DialogViewModel abgeleiteten Klasse).

b) Lassen Sie die Ansicht das Ereignis in ihrem OnDataContextChanged-Ereignis abonnieren. Stellen Sie sicher, dass Sie den alten Wert! = Null und im Ereignis Unloaded des Fensters ausblenden und abbestellen.

c) Wenn das Ereignis ausgelöst wird, lassen Sie die Ansicht Ihre Ansicht öffnen, die sich möglicherweise in einer Ressource auf Ihrer Seite befindet, oder Sie können sie gemäß Konvention an anderer Stelle suchen (wie im Dialogdienstansatz).

Dieser Ansatz ist flexibler, erfordert jedoch mehr Arbeit. Ich benutze es nicht viel. Der einzige schöne Vorteil ist die Möglichkeit, die Ansicht beispielsweise physisch in einem Tab zu platzieren. Ich habe einen Algorithmus verwendet, um ihn in die Grenzen des aktuellen Benutzersteuerelements zu setzen, oder wenn er nicht groß genug ist, den visuellen Baum zu durchlaufen, bis ein ausreichend großer Container gefunden wurde.

Auf diese Weise können sich Dialoge in der Nähe des Ortes befinden, an dem sie tatsächlich verwendet werden, nur den Teil der App dimmen, der sich auf die aktuelle Aktivität bezieht, und der Benutzer kann sich innerhalb der App bewegen, ohne die Dialoge manuell wegschieben zu müssen, selbst wenn mehrere Quasi vorhanden sind. Modale Dialoge werden auf verschiedenen Registerkarten oder Unteransichten geöffnet.

Chris Bordeman
quelle
Ein Dialogdienst ist sicherlich viel einfacher und was ich normalerweise mache. Es macht es auch einfach, den Dialog der Ansicht über das übergeordnete Ansichtsmodell zu schließen. Dies ist erforderlich, wenn das übergeordnete Ansichtsmodell geschlossen oder abgebrochen wird.
Chris Bordeman
4

Verwenden Sie einen Befehl zum Einfrieren

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}
Maxm007
quelle
Dieser Code erfordert einige Arbeit, ist jedoch bei weitem die beste Idee, insbesondere für Systemdialoge wie Datei- oder Druckerdialoge. Dialoge gehören zu Ansicht, wenn etwas funktioniert. Bei Dateidialogen kann das Ergebnis (Dateiname ausgewählt) als Parameter an den inneren Befehl übergeben werden.
Anton Tykhyy
3

Ich denke, dass die Handhabung eines Dialogs in der Verantwortung der Ansicht liegen sollte, und die Ansicht muss Code enthalten, um dies zu unterstützen.

Wenn Sie die ViewModel-View-Interaktion ändern, um Dialoge zu verarbeiten, ist das ViewModel von dieser Implementierung abhängig. Der einfachste Weg, um dieses Problem zu lösen, besteht darin, die Ansicht für die Ausführung der Aufgabe verantwortlich zu machen. Wenn dies bedeutet, dass ein Dialog angezeigt wird, ist dies in Ordnung, kann aber auch eine Statusmeldung in der Statusleiste usw. sein.

Mein Punkt ist, dass der springende Punkt des MVVM-Musters darin besteht, die Geschäftslogik von der GUI zu trennen. Sie sollten also die GUI-Logik (zum Anzeigen eines Dialogfelds) nicht in der Business-Schicht (dem ViewModel) mischen.

Cameron MacFarland
quelle
2
Die VM würde den Dialog niemals verarbeiten. In meinem Beispiel hätte sie einfach ein Ereignis, bei dem der Dialog gestartet und Informationen in irgendeiner Form von EventArgs zurückgegeben werden müssten. Wie gibt die Ansicht Informationen an die VM zurück, wenn sie dafür verantwortlich ist?
Ray Booysen
Angenommen, die VM muss etwas löschen. Die VM ruft beim View Delete eine Methode auf, die einen Booleschen Wert zurückgibt. Die Ansicht kann dann entweder das Element direkt löschen und true zurückgeben oder einen Bestätigungsdialog anzeigen und true / false zurückgeben, abhängig von der Antwort des Benutzers.
Cameron MacFarland
Die VM weiß nichts über den Dialog, sondern hat die Ansicht nur aufgefordert, etwas zu löschen, was die Ansicht entweder bestätigt oder abgelehnt hat.
Cameron MacFarland
Ich habe immer gedacht, dass der Punkt von MVVM Modell: Geschäftslogik, ViewModel: GUI-Logik und Ansicht: keine Logik ist. Was Ihrem letzten Absatz irgendwie widerspricht. Bitte erkläre!
David Schmitt
2
Zunächst muss festgestellt werden, ob die Anforderung einer Bestätigung vor dem Löschen eine Geschäftslogik oder eine Ansichtslogik ist. Wenn es sich um eine Geschäftslogik handelt, darf die DeleteFile-Methode im Modell dies nicht tun, sondern muss das Bestätigungsfrageobjekt zurückgeben. Dies beinhaltet einen Verweis auf den Delegaten, der die eigentliche Löschung vornimmt. Wenn es sich nicht um Geschäftslogik handelt, muss die VM im DeleteFileCommand eine VM der Frage mit zwei ICommand-Mitgliedern erstellen. Eins für Ja und eins für Nein. Es gibt wahrscheinlich Argumente für beide Ansichten, und in RL werden die meisten Benutzer wahrscheinlich auf beide stoßen.
Guge
3

Eine interessante Alternative ist die Verwendung von Controllern, die für die Anzeige der Ansichten (Dialoge) verantwortlich sind.

Wie dies funktioniert, zeigt das WPF Application Framework (WAF) .

jbe
quelle
3

Warum nicht einfach ein Ereignis in der VM auslösen und das Ereignis in der Ansicht abonnieren? Dies würde die Anwendungslogik und die Ansicht getrennt halten und es Ihnen weiterhin ermöglichen, ein untergeordnetes Fenster für Dialoge zu verwenden.

Eric Grover
quelle
3

Ich habe ein Verhalten implementiert, das eine Nachricht aus dem ViewModel abhört. Es basiert auf der Laurent Bugnion-Lösung, aber da es keinen Code dahinter verwendet und wiederverwendbarer ist, finde ich es eleganter.

So verhält sich WPF so, als würde MVVM sofort unterstützt

Elad Katz
quelle
1
Sie sollten hier den vollständigen Code einfügen, da dies für gute Antworten erforderlich ist. Trotzdem ist der verknüpfte Ansatz ziemlich ordentlich, also danke dafür! :)
Yoda
2
@yoda der vollständige Code ist ziemlich lang, und deshalb möchte ich lieber darauf verlinken. Ich habe meine Antwort bearbeitet, um Änderungen widerzuspiegeln und auf einen Link zu verweisen, der nicht defekt ist
Elad Katz
Danke für die Verbesserung. Trotzdem ist es besser, hier auf SO lange ganzseitige Code-3-Schriftrollen bereitzustellen, als einen Link, der eines Tages möglicherweise offline ist. Gute Artikel für komplexe Themen sind immer ziemlich lang - und ich sehe keinen Vorteil darin, einen neuen Tab zu öffnen, dorthin zu wechseln und dort zu scrollen, indem ich auf derselben Seite / Registerkarte scrolle, auf der ich vorher war. ;)
Yoda
@EladKatz Ich habe gesehen, dass Sie einen Teil Ihrer WPF-Implementierung in dem von Ihnen angegebenen Link geteilt haben. Haben Sie eine Lösung zum Öffnen eines neuen Fensters in ViewModel? Grundsätzlich habe ich zwei Formulare und jedes hat ein ViewModel. Ein Benutzer klickt auf eine Schaltfläche, ein anderes Formular wird angezeigt und viewmodel1 sendet sein Objekt an viewmodel2. In Formular 2 kann der Benutzer das Objekt ändern. Wenn er das Fenster schließt, wird das aktualisierte Objekt an das erste ViewModel zurückgesendet. Haben Sie eine Lösung dafür?
Ehsan
2

Ich denke, die Ansicht könnte Code enthalten, um das Ereignis aus dem Ansichtsmodell zu behandeln.

Abhängig vom Ereignis / Szenario kann es auch einen Ereignisauslöser geben, der das Anzeigen von Modellereignissen abonniert, und eine oder mehrere Aktionen, die als Antwort aufgerufen werden.

Nikhil Kothari
quelle
1

Karl Shifflett hat eine Beispielanwendung zum Anzeigen von Dialogfeldern mit dem Service-Ansatz und dem Prism InteractionRequest-Ansatz erstellt.

Ich mag den Service-Ansatz - Er ist weniger flexibel, sodass Benutzer weniger wahrscheinlich etwas kaputt machen :) Er stimmt auch mit dem WinForms-Teil meiner Anwendung (MessageBox.Show) überein. Wenn Sie jedoch viele verschiedene Dialoge anzeigen möchten, ist InteractionRequest eine besserer Weg zu gehen.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/

surfen
quelle
1

Ich weiß, dass es eine alte Frage ist, aber als ich diese Suche durchgeführt habe, habe ich viele verwandte Fragen gefunden, aber ich habe keine wirklich klare Antwort gefunden. Also mache ich meine eigene Implementierung eines Dialogfelds / einer Nachrichtenbox / eines Popins und teile es!
Ich denke, es ist "MVVM-Beweis", und ich versuche, es einfach und richtig zu machen, aber ich bin neu in WPF, also zögern Sie nicht, Kommentare abzugeben oder sogar Pull-Anfragen zu stellen.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Sie können es so verwenden:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

Oder so, wenn Sie ein anspruchsvolleres Popin möchten:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

Und es zeigt solche Dinge:

2

Xav987
quelle
1

Der Standardansatz

Nachdem ich mich jahrelang mit diesem Problem in WPF befasst hatte, fand ich endlich heraus, wie Dialoge in WPF standardmäßig implementiert werden können. Hier sind die Vorteile dieses Ansatzes:

  1. REINIGEN
  2. Verstößt nicht gegen das MVVM-Entwurfsmuster
  3. ViewModal verweist niemals auf eine der UI-Bibliotheken (WindowBase, PresentationFramework usw.)
  4. Perfekt für automatisierte Tests
  5. Dialoge können einfach ersetzt werden.

Also, was ist der Schlüssel. Es ist DI + IoC .

So funktioniert es. Ich verwende MVVM Light, aber dieser Ansatz kann auch auf andere Frameworks erweitert werden:

  1. Fügen Sie Ihrer Lösung ein WPF-Anwendungsprojekt hinzu. Nennen Sie es App .
  2. Fügen Sie eine ViewModal-Klassenbibliothek hinzu. Nennen Sie es VM .
  3. App verweist auf VM-Projekt. VM-Projekt weiß nichts über App.
  4. Fügen Sie beiden Projekten einen NuGet-Verweis auf MVVM Light hinzu . Ich verwende heutzutage MVVM Light Standard , aber Sie sind auch mit der Vollversion von Framework einverstanden.
  5. Fügen Sie dem VM-Projekt eine Schnittstelle IDialogService hinzu :

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. Stellen Sie eine öffentliche statische Eigenschaft vom IDialogServiceTyp in Ihrem bereit ViewModelLocator, lassen Sie jedoch den Registrierungsteil für die Ansichtsebene. Dies ist der Schlüssel .:

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Fügen Sie eine Implementierung dieser Schnittstelle in das App-Projekt ein.

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. Während einige dieser Funktionen generisch sind ( ShowMessage, AskBooleanQuestionetc.), andere sind spezifisch für dieses Projekt und die Verwendung benutzerdefiniert Windows. Sie können auf dieselbe Weise weitere benutzerdefinierte Fenster hinzufügen. Der Schlüssel besteht darin, UI-spezifische Elemente in der Ansichtsebene beizubehalten und die zurückgegebenen Daten mithilfe von POCOs in der VM-Ebene verfügbar zu machen .
  9. Führen Sie mit dieser Klasse eine IoC-Registrierung Ihrer Schnittstelle in der Ansichtsebene durch. Sie können dies im Konstruktor Ihrer Hauptansicht tun (nach dem InitializeComponent()Aufruf):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. Los geht's. Sie haben jetzt Zugriff auf alle Dialogfunktionen auf VM- und View-Ebene. Ihre VM-Schicht kann diese Funktionen folgendermaßen aufrufen:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. So sauber, siehst du? Die VM-Schicht weiß nichts darüber, wie eine Ja / Nein-Frage dem Benutzer von der UI-Schicht präsentiert wird, und kann weiterhin erfolgreich mit dem zurückgegebenen Ergebnis aus dem Dialogfeld arbeiten.

Andere kostenlose Vergünstigungen

  1. Zum Schreiben von Komponententests können Sie eine benutzerdefinierte Implementierung IDialogServicein Ihrem Testprojekt bereitstellen und diese Klasse in IoC im Konstruktor Ihrer Testklasse registrieren.
  2. Sie müssen einige Namespaces importieren, um auf die Dialogfelder Microsoft.Win32Öffnen und Speichern zugreifen zu können. Ich habe sie weggelassen, da auch eine WinForms-Version dieser Dialoge verfügbar ist und jemand möglicherweise eine eigene Version erstellen möchte. Beachten Sie auch, dass einige der in verwendeten DialogPresenterBezeichner Namen meiner eigenen Fenster sind (zSettingsWindow . ). Sie müssen sie entweder sowohl von der Benutzeroberfläche als auch von der Implementierung entfernen oder Ihre eigenen Fenster bereitstellen.
  3. Wenn Ihre VM Multithreading ausführt, rufen Sie MVVM Light DispatcherHelper.Initialize()früh im Lebenszyklus Ihrer Anwendung auf.
  4. Mit Ausnahme dessen, DialogPresenterwas in die Ansichtsebene eingefügt wird, sollten andere ViewModals registriert ViewModelLocatorund anschließend eine öffentliche statische Eigenschaft dieses Typs für die Ansichtsebene verfügbar gemacht werden . Etwas wie das:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. Zum größten Teil sollten Ihre Dialoge keinen Code-Behind für Dinge wie das Binden oder Festlegen von DataContext usw. haben. Sie sollten nicht einmal Dinge als Konstruktorparameter übergeben. XAML kann das alles für Sie tun:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. Wenn Sie DataContextdiese Einstellung vornehmen, erhalten Sie alle möglichen Vorteile für die Entwurfszeit, z. B. Intellisense und automatische Vervollständigung.

Hoffe das hilft allen.

Punkt net
quelle
0

Ich habe über ein ähnliches Problem nachgedacht, als ich gefragt habe, wie das Ansichtsmodell für eine Aufgabe oder einen Dialog aussehen soll .

Meine aktuelle Lösung sieht folgendermaßen aus:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

Wenn das Ansichtsmodell entscheidet, dass Benutzereingaben erforderlich sind, ruft es eine Instanz SelectionTaskModelmit den möglichen Auswahlmöglichkeiten für den Benutzer auf. Die Infrastruktur sorgt dafür, dass die entsprechende Ansicht angezeigt wird, die die Choose()Funktion zum richtigen Zeitpunkt nach Wahl des Benutzers aufruft.

David Schmitt
quelle
0

Ich hatte mit dem gleichen Problem zu kämpfen. Ich habe mir eine Möglichkeit ausgedacht, zwischen View und ViewModel zu kommunizieren. Sie können das Senden einer Nachricht vom ViewModel an die Ansicht initiieren, um anzuweisen, dass eine Nachrichtenbox angezeigt werden soll, und das Ergebnis wird gemeldet. Dann kann das ViewModel auf das von der Ansicht zurückgegebene Ergebnis reagieren.

Ich demonstriere dies in meinem Blog :

Dan spielt im Feuerlicht
quelle
0

Ich habe einen ziemlich umfassenden Artikel zu diesem Thema geschrieben und auch eine Popup-Bibliothek für MVVM-Dialoge entwickelt. Die strikte Einhaltung von MVVM ist nicht nur möglich, sondern bei ordnungsgemäßer Implementierung auch sehr sauber. Sie kann problemlos auf Bibliotheken von Drittanbietern erweitert werden, die sich nicht selbst daran halten:

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM

Mark Feldman
quelle
0

Entschuldigung, aber ich muss mich einschalten. Ich habe einige der vorgeschlagenen Lösungen durchgesehen, bevor ich den Prism.Wpf.Interactivity-Namespace im Prism-Projekt gefunden habe. Sie können Interaktionsanforderungen und Popup-Fensteraktionen verwenden, um entweder ein benutzerdefiniertes Fenster zu rollen, oder für einfachere Anforderungen sind Popups für Benachrichtigung und Bestätigung integriert. Diese erstellen echte Fenster und werden als solche verwaltet. Sie können ein Kontextobjekt mit allen Abhängigkeiten übergeben, die Sie im Dialogfeld benötigen. Wir verwenden diese Lösung bei meiner Arbeit, seit ich sie gefunden habe. Wir haben hier zahlreiche leitende Entwickler und niemand hat sich etwas Besseres ausgedacht. Unsere vorherige Lösung war der Dialogdienst in ein Overlay und die Verwendung einer Präsentationsklasse, um dies zu ermöglichen. Sie mussten jedoch Fabriken für alle Dialogansichtsmodelle usw. haben.

Das ist nicht trivial, aber auch nicht sehr kompliziert. Und es ist in Prisma eingebaut und daher meiner Meinung nach die beste (oder bessere) Praxis.

Meine 2 Cent!

Jogi
quelle
-1

EDIT: Ja, ich stimme zu, dass dies kein korrekter MVVM-Ansatz ist, und ich verwende jetzt etwas Ähnliches wie das, was von Blindmeis vorgeschlagen wird.

Eine Möglichkeit, dies zu erreichen, ist

In Ihrem Hauptansichtsmodell (wo Sie das Modal öffnen):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

Und in Ihrem Modal Window View / ViewModel:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

oder ähnlich wie hier veröffentlicht WPF MVVM: So schließen Sie ein Fenster

Simone
quelle
2
Ich war nicht die Ablehnung, aber ich vermute, es liegt daran, dass das Ansichtsmodell einen direkten Bezug zur Ansicht hat.
Brian Gideon
@BrianGideon, danke für deinen Kommentar. Ich bin damit einverstanden, dass dies keine entkoppelte Lösung ist. Tatsächlich verwende ich nichts Ähnliches wie von Blindmeis vorgeschlagen. Danke noch einmal.
Simone
Es ist eine schlechte Form, in die Aussicht zu greifen, wenn es so einfach ist, dies nicht zu tun.
Chris Bordeman