wake-up-neo.com

Dialoge in WPF mit MVVM behandeln

In dem 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 einen ICommand verfügbar machen, der darauf hinweist, dass ein Dialog angezeigt wird.

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 Dialogfeld erforderlich war.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Dies ist in Ordnung, aber das bedeutet, dass die Ansicht Code erfordert, von dem ich gerne Abstand nehmen würde.

222
Ray Booysen

Ich schlage vor, auf die modalen Dialoge der 1990er zu verzichten und stattdessen ein Steuerelement als Überlagerung (Canvas + absolute Positionierung) zu implementieren, wobei die Sichtbarkeit an einen booleschen Hintergrund 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}"/>

So habe ich eine als Benutzersteuerung implementiert. Durch Klicken auf das 'x' wird das Steuerelement in einer Codezeile im Code des Benutzersteuerelements geschlossen. (Da ich meine Ansichten in einer EXE-Datei und ViewModels in einer DLL habe, fühle ich mich nicht schlecht mit Code, der die Benutzeroberfläche manipuliert.)

Wpf dialog

132
Jeffrey Knight

Sie sollten hierfür einen Mediator verwenden. _ Mediator ist ein bekanntes Entwurfsmuster, das in einigen Implementierungen auch Messenger genannt wird .. Ein Paradigma des Typs "Register/Notify", mit dem ViewModel und Views miteinander kommunizieren können durch einen Low-Coupled-Messaging-Mechanismus.

Sie sollten sich die google WPF Disciples-Gruppe ansehen und einfach nach Mediator suchen. Sie werden mit den Antworten 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

51
Roubachof

Ein guter MVVM-Dialog sollte:

  1. Nur mit XAML deklarieren.
  2. Holen Sie sich das gesamte Verhalten von Datenbindungen.

Leider bietet WPF diese Funktionen nicht. Das Anzeigen eines Dialogs erfordert einen Code-Behind-Aufruf von ShowDialog (). Die Window-Klasse, die Dialoge unterstützt, kann in XAML nicht deklariert werden. Sie kann also nicht ohne weiteres mit DataContext verbunden werden.

Um dieses Problem zu lösen, habe ich ein XAML-Stub-Steuerelement geschrieben, das sich in der logischen Baumstruktur befindet und die Datenbindung an ein Fenster weiterleitet und das Anzeigen und Ausblenden des Dialogs behandelt. Sie finden es hier: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Es ist wirklich einfach zu verwenden und erfordert keine seltsamen Änderungen an Ihrem ViewModel und keine Ereignisse oder Meldungen. Der grundlegende Aufruf sieht folgendermaßen aus: 

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

Möglicherweise möchten Sie einen Stil hinzufügen, der die Option Anzeigen setzt. Ich erkläre es in meinem Artikel. Ich hoffe das hilft dir.

28
user92541

Ich verwende this 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);
23
blindmeis

Meine derzeitige Lösung löst die meisten Probleme, die Sie angesprochen haben, ist jedoch vollständig von plattformspezifischen Dingen abstrahiert und kann wiederverwendet werden .. Außerdem verwendete ich keine Code-behind-Bindung, die nur mit DelegateCommands verknüpft wurde, die ICommand implementieren. Dialog ist im Grunde eine Ansicht - ein separates Steuerelement, das über ein eigenes ViewModel verfügt, das vom ViewModel des Hauptbildschirms aus angezeigt wird, aber ü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

16
Roboblob

Mit diesem Konzept habe ich beim Lernen (noch lernen) der MVVM eine Zeit lang Probleme gehabt. Was ich entschieden habe und was ich denke, andere haben sich bereits entschieden, was mir aber nicht klar war:

Mein ursprünglicher Gedanke war, dass ein ViewModel ein Dialogfeld nicht direkt aufrufen darf, da es keinen Einfluss darauf hat, wie ein Dialogfeld angezeigt werden soll. Aufgrund dessen begann ich darüber nachzudenken, wie ich Nachrichten weitergeben könnte, wie ich es in MVP hätte (d. H. View.ShowSaveFileDialog ()). Ich denke jedoch, dass dies der falsche Ansatz ist.

Ein ViewModel kann einen Dialog direkt aufrufen. Wenn Sie jedoch ein ViewModel testen, bedeutet dies, dass das Dialogfeld während des Tests angezeigt wird oder alle zusammen fehlschlagen (dies nie wirklich versucht haben). 

Während des Testens muss also eine Testversion des Dialogs verwendet werden. Dies bedeutet, dass Sie für alle Dialogfelder, die Sie haben, eine Schnittstelle erstellen und entweder die Dialogantwort ausspielen oder einen Test-Mock erstellen müssen, der ein Standardverhalten aufweist.

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

Mit diesem Ansatz ist Ihr ViewModel noch testfähig und abhängig davon, wie Sie Ihre Dialoge simulieren, können Sie das Verhalten steuern.

Hoffe das hilft.

6
Mike Rowley

Dafür gibt es zwei Möglichkeiten: 1) einen Dialogdienst (easy, clean) und 2) unterstützte Ansichten. Die Ansicht unterstützt bietet einige nette Funktionen, lohnt sich aber normalerweise nicht.

DIALOG SERVICE

a) eine Dialog-Service-Schnittstelle wie via constructor oder einen Abhängigkeitsbehälter:

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 dem Typ zugeordneter DataTemplates). ShowDialogAsync sollte eine TaskCompletionSource erstellen und ihre .Task-Eigenschaft zurückgeben. Die DialogViewModel-Klasse selbst benötigt ein Ereignis, das Sie in der abgeleiteten Klasse aufrufen können, wenn Sie sie schließen möchten, und in der Dialogansicht beobachten, um das Dialogfeld tatsächlich zu schließen/verbergen und die TaskCompletionSource abzuschließen. 

b) Zur Verwendung rufen Sie waitDeatService.ShowDialog (myDlgVm) für Ihre Instanz einer von DialogViewModel abgeleiteten Klasse einfach auf. Überprüfen Sie nach der Rückgabe von waitit die Eigenschaften, die Sie in Ihrem Dialogfeld VM hinzugefügt haben, um festzustellen, was passiert ist. Sie brauchen nicht einmal einen Rückruf.

ANSICHT UNTERSTÜTZT

Dadurch kann Ihre Ansicht ein Ereignis im Ansichtsmodell abhören. Dies könnte alles in ein Mischverhalten verwickelt sein, um Code hinterherzugehen und die Ressourcennutzung zu vermeiden, wenn Sie dazu neigen (FMI, Unterklasse der "Behavior" -Klasse, um eine Art Blendable-Eigenschaft für Steroide zu sehen). Im Moment machen wir dies manuell für jede Ansicht:

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

b) Lassen Sie die Ansicht das Ereignis in seinem OnDataContextChanged-Ereignis abonnieren. Vergewissern Sie sich, dass der alte Wert! = Null und im Windows-Ereignis Unloaded ausgeblendet und der Newsletter abbestellt wird.

c) Wenn das Ereignis ausgelöst wird, lassen Sie die Ansicht Ihre Ansicht öffnen. Diese befindet sich möglicherweise in einer Ressource auf Ihrer Seite, oder Sie können sie nach Konventionen an anderer Stelle suchen (wie in der Dialogdienstmethode).

Dieser Ansatz ist flexibler, erfordert jedoch mehr Arbeit. Ich benutze es nicht viel. Der einzige Vorteil von Nice ist die Möglichkeit, die Ansicht beispielsweise physisch in einem Tab zu platzieren. Ich habe einen Algorithmus verwendet, um ihn innerhalb der Grenzen des aktuellen Benutzersteuerelements zu platzieren. Wenn er nicht groß genug ist, durchlaufen Sie den visuellen Baum nach oben, bis ein ausreichend großer Container gefunden wird. 

Dadurch können sich Dialoge in der Nähe des Ortes befinden, an dem sie tatsächlich verwendet werden. Sie dimmen nur den Teil der App, der sich auf die aktuelle Aktivität bezieht, und lassen den Benutzer sich innerhalb der App bewegen, ohne manuell Dialoge wegschieben zu müssen. modale Dialoge werden auf verschiedenen Registerkarten oder Unteransichten geöffnet.

5
Chris Bordeman

Verwenden Sie einen Freez-Befehl

<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();
    }
}
4
Maxm007

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

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

3
jbe

Ich habe ein Verhalten implementiert, das auf eine Nachricht aus dem ViewModel wartet. Es basiert auf der Lösung von Laurent Bugnion, aber da es keinen Code verwendet und wiederverwendbar ist, denke ich, dass es eleganter ist.

Wie kann man WPF so einstellen, als würde MVVM standardmäßig unterstützt

3
Elad Katz

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 dennoch ermöglichen, ein untergeordnetes Fenster für Dialoge zu verwenden.

3
Eric Grover

Ich denke, dass die Behandlung eines Dialogs in der Verantwortung der Ansicht liegen sollte, und die Ansicht muss über Code verfügen, der dies unterstützt.

Wenn Sie die ViewModel-View-Interaktion so ändern, dass Dialogfelder verarbeitet werden, ist das ViewModel von dieser Implementierung abhängig. Die einfachste Möglichkeit, 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, es könnte sich jedoch auch um eine Statusmeldung in der Statusleiste handeln.

Mein Punkt ist, dass es beim MVVM-Muster darauf ankommt, die Geschäftslogik von der GUI zu trennen. Sie sollten also keine GUI-Logik (zur Anzeige eines Dialogfelds) in der Business-Schicht (ViewModel) verwenden.

3

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

Je nach Ereignis/Szenario kann es auch einen Ereignisauslöser geben, der die Anzeige von Modellereignissen abonniert, und eine oder mehrere Aktionen, die als Antwort aufgerufen werden sollen.

2
Nikhil Kothari

Ich hatte die gleiche Situation und wickelte die MessageBox in ein Designer-unsichtbares Steuerelement ein. Die Details sind in meinem Blog

http://geekswithblogs.net/mukapu/archive/2010/03/12/user-prompts-messagebox-with-mvvm.aspx

Dasselbe kann auf beliebige modale Dialoge, Dateisuchsteuerung usw. erweitert werden.

2
mukapu

Der Standardansatz

Nachdem ich mich jahrelang mit diesem Problem in WPF beschäftigt hatte, fand ich endlich heraus, wie man Dialoge in WPF nach dem Standard umsetzt. Hier sind die Vorteile dieses Ansatzes:

  1. SAUBER
  2. Verstößt nicht gegen das MVVM-Entwurfsmuster
  3. ViewModal verweist nie 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 ausgeweitet 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 den NuGet-Verweis auf MVVM Light zu beiden Projekten hinzu . Ich verwende derzeit MVVM Light Standard , aber Sie sind auch mit der Vollversion von Framework einverstanden.
  5. Fügen Sie dem VM-Projekt ein Interface 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 von IDialogService bereit, und geben Sie ViewModelLocator ein. Lassen Sie jedoch den Registrierungsteil für die Ansichtsebene ausgeführt werden. Dies ist der Schlüssel .:

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

    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, AskBooleanQuestion usw.), sind andere projektspezifisch und verwenden benutzerdefinierte Windows. Sie können auf dieselbe Weise weitere benutzerdefinierte Fenster hinzufügen. Der Schlüssel besteht darin, die UI-spezifischen Elemente in der Ansichtsebene beizubehalten und die zurückgegebenen Daten mithilfe von POCOs in der Ebene VM 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 Aufruf von InitializeComponent()):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
    
  10. Es geht los. Sie haben jetzt Zugriff auf alle Dialogfunktionen auf den Ebenen VM und View. Ihre Ebene VM kann diese Funktionen folgendermaßen aufrufen:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
    
  11. Du siehst so sauber. Die Ebene VM weiß nichts darüber, wie eine Ja/Nein-Frage dem Benutzer von der Benutzeroberflächenebene angezeigt 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 von IDialogService in Ihrem Testprojekt bereitstellen und diese Klasse in IoC im Konstruktor Ihrer Testklasse registrieren.
  2. Sie müssen einige Namespaces wie Microsoft.Win32 importieren, um auf die Dialogfelder Öffnen und Speichern zuzugreifen. Ich habe sie weggelassen, weil es auch eine WinForms-Version dieser Dialoge gibt und jemand seine eigene Version erstellen möchte. Beachten Sie auch, dass einige der in DialogPresenter verwendeten Bezeichner Namen meiner eigenen Fenster sind (z. B. SettingsWindow). Sie müssen sie entweder von der Benutzeroberfläche und der Implementierung entfernen oder Ihre eigenen Fenster bereitstellen.
  3. Wenn Ihr VM Multithreading ausführt, rufen Sie die Funktion DispatcherHelper.Initialize() von MVVM Light zu einem frühen Zeitpunkt im Lebenszyklus Ihrer Anwendung auf.
  4. Mit Ausnahme von DialogPresenter, das in die View-Ebene eingefügt wird, sollten andere ViewModals in ViewModelLocator registriert und anschließend eine öffentliche statische Eigenschaft dieses Typs für die View-Ebene verfügbar gemacht werden. Etwas wie das:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
    
  5. In den meisten Dialogen sollte kein Code-Behind für das Binden oder Festlegen von DataContext usw. vorhanden sein. Sie sollten nicht einmal Dinge als Konstruktorparameter übergeben. XAML kann das alles für Sie erledigen:

    <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 DataContext auf diese Weise festlegen, erhalten Sie alle möglichen Vorteile für die Entwurfszeit, z. B. Intellisense und automatische Vervollständigung.

Hoffe das hilft allen.

1
dotNET

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 Dialoge, dann ist InteractionRequest ein besserer Weg.

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

1
surfen

Ich weiß, dass es eine alte Frage ist, aber als ich diese Suche durchgeführt habe, finde ich eine Menge verwandter Fragen, aber ich habe keine wirklich klare Antwort gefunden. Also erstelle ich meine eigene Implementierung einer Dialogbox/Messagebox/Popin und teile sie mit!
Ich denke, es ist "MVVM-Beweis", und ich versuche, es einfach und ordentlich zu machen, aber ich bin neu in WPF.

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 ausgeklügelteres Popin wollen:

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

Und es zeigt Dinge wie diese:

2

1
Xav987

Nachdem ich einige Zeit damit verbracht hatte, kam ich schließlich zu der folgenden Lösung. Einige wichtige Vorteile dieses Ansatzes sind:

  1. Implementiert die eigene IDialogService von MVVM Light.
  2. Die Ansicht muss nicht die Referenz von MVVM Light hinzufügen.
  3. Die VM muss keine Aktivitäten auf Präsentationsebene durchführen. Benötigt nicht einmal PresentationFramework.
  4. Verwendet den MVVM Light-eigenen Messenger-Kanal, so dass die Präsentationsebene und die VM -Layer entkoppelt sind. 
  5. Unterstützt Dialoge mit einem Rückgabewert wie Ja/Nein-Fragen oder OK/Abbrechen-Situationen.
  6. Unterstützt benutzerdefinierte Dialoge.

Hier ist die Implementierung von IDialogService (geht in ViewModel project):

using System;
using System.Linq;
using System.Threading.Tasks;

namespace VM
{
  public enum MessageBoxButtonVM
  {
    OK,
    OKCancel,
    YesNo
  }

  public enum MessageBoxImageVM
  {
    None,
    Information,
    Question,
    Error
  }

  public class MessageBoxArgs
  {
    public MessageBoxButtonVM Buttons { get; set; }
    public MessageBoxImageVM Icon { get; set; }
    public string Title { get; set; }
    public string Message { get; set; }
  }

  //For custom dialogs that return a value
  public class MessageBoxNotificationWithAction<T>
  {
    private readonly Action<T> _callback;

    public MessageBoxArgs Notification { get; set; }

    public MessageBoxNotificationWithAction(MessageBoxArgs notification, Action<T> callback)
    {
      Notification = notification;

      CheckCallback(callback);
      _callback = callback;
    }

    public virtual void Execute(T argument)
    {
      _callback.Invoke(argument);
    }

    private static void CheckCallback(Delegate callback)
    {
      if (callback == null)
      {
        throw new ArgumentNullException(nameof(callback), "Callback must not be null");
      }
    }
  }

  /// <summary>
  /// Provides an implementation-agnostic way of communicating with the user through dialog boxes. Clients must register for communication messages using
  /// MVVM Light messaging system.
  /// </summary>
  public class DialogService : GalaSoft.MvvmLight.Views.IDialogService
  {
    private static GalaSoft.MvvmLight.Messaging.IMessenger Messenger = GalaSoft.MvvmLight.Messaging.Messenger.Default;

    private string _ProductName = "";

    public string ProductName
    {
      get
      {
        if (_ProductName == "")
        {
          //The following statement returns the Title attribute of the current Assembly, as defined in project properties (Assembly Information dialog).
          var TitleAttrib = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributesData().First(x => x.AttributeType.Name == "AssemblyTitleAttribute");

          if (TitleAttrib != null)
          {
            _ProductName = TitleAttrib.ConstructorArguments[0].Value.ToString();
          }
          else
          {
            _ProductName = "Default Application Name";
          }
        }

        return _ProductName;
      }
    }

    public Task ShowError(Exception error, string title, string buttonText, Action afterHideCallback)
    {
      return ShowError(error.Message, title, buttonText, afterHideCallback);
    }

    public Task ShowMessage(string message, string title)
    {
      return Task.Run(() => MessengerSend(message, title, MessageBoxButtonVM.OK, MessageBoxImageVM.Error));
    }

    public Task ShowError(string message, string title, string buttonText, Action afterHideCallback)
    {
      return Task.Run(() =>
      {
        MessengerSend(message, title, MessageBoxButtonVM.OK, MessageBoxImageVM.Error);
        afterHideCallback?.Invoke();
      });
    }

    public Task ShowMessage(string message, string title, string buttonText, Action afterHideCallback)
    {
      return Task.Run(() =>
      {
        MessengerSend(message, title);
        afterHideCallback?.Invoke();
      });
    }

    public Task<bool> ShowMessage(string message, string title, string buttonConfirmText, string buttonCancelText, Action<bool> afterHideCallback)
    {
      if ((buttonConfirmText == "OK" && buttonCancelText == "Cancel") ||
        (buttonConfirmText == "Yes" && buttonCancelText == "No"))
      {
        return Task.Run<bool>(() =>
        {
          MessageBoxButtonVM btn;
          if (buttonConfirmText == "OK")
            btn = MessageBoxButtonVM.OKCancel;
          else
            btn = MessageBoxButtonVM.YesNo;


          bool Response = false;
          Messenger.Send(new MessageBoxNotificationWithAction<bool>(
                                                      new MessageBoxArgs()
                                                      {
                                                        Buttons = btn,
                                                        Icon = MessageBoxImageVM.Question,
                                                        Title = (string.IsNullOrEmpty(title) ? _ProductName : title),
                                                        Message = message
                                                      },
                                                      (result) => Response = result
                                                        ));

          afterHideCallback?.Invoke(Response);

          return Response;
        });
      }
      else
        throw new ArgumentException($"{nameof(buttonConfirmText)} and {nameof(buttonCancelText)} must either be OK/Cancel or Yes/No.");
    }

    /// <summary>
    /// For debugging purpose only
    /// </summary>
    /// <param name="message"></param>
    /// <param name="title"></param>
    /// <returns></returns>
    public Task ShowMessageBox(string message, string title) => ShowMessage(message, title);

    private void MessengerSend(string msg, string title = "", MessageBoxButtonVM btn = MessageBoxButtonVM.OK, MessageBoxImageVM icon = MessageBoxImageVM.Information)
    {
      Messenger.Send(new MessageBoxArgs()
      {
        Buttons = MessageBoxButtonVM.OK,
        Icon = MessageBoxImageVM.Information,
        Title = (string.IsNullOrEmpty(title) ? _ProductName : title),
        Message = msg
      });
    }
  }
}

Hier ist die Präsentationsschicht (geht in View project)

using System.Windows;
using VM;

namespace View
{
  class DialogPresenter
  {
    private Window _Parent;

    public DialogPresenter()
    {
      //For simple information boxes
      GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxArgs>(this, (arg) => ShowDialog(arg));

      //For Yes/No or OK/Cancel dialog boxes.
      GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxNotificationWithAction<bool>>(this, (arg) => arg.Execute(ShowDialog(arg.Notification)));

      //For notifications that require a string response (such as Manual Timeslot Description)
      GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxNotificationWithAction<string>>(this,
        (arg) => arg.Execute(ShowStringInputDialog(arg.Notification.Title, arg.Notification.Message)));
    }

    private bool ShowDialog(MessageBoxArgs arg)
    {
      MessageBoxButton btn = MessageBoxButton.OK;
      MessageBoxImage ico = MessageBoxImage.None;

      switch (arg.Buttons)
      {
        case MessageBoxButtonVM.OK: btn = MessageBoxButton.OK; break;
        case MessageBoxButtonVM.OKCancel: btn = MessageBoxButton.OKCancel; break;
        case MessageBoxButtonVM.YesNo: btn = MessageBoxButton.YesNo; break;
      }

      switch (arg.Icon)
      {
        case MessageBoxImageVM.Error: ico = MessageBoxImage.Error; break;
        case MessageBoxImageVM.Information: ico = MessageBoxImage.Information; break;
        case MessageBoxImageVM.None: ico = MessageBoxImage.None; break;
        case MessageBoxImageVM.Question: ico = MessageBoxImage.Question; break;
      }

      bool Result = false;
      _Parent.Dispatcher.Invoke(() =>
      {
        var Res = MessageBox.Show(arg.Message, arg.Title, btn, ico);
        Result = (Res == MessageBoxResult.OK || Res == MessageBoxResult.Yes);
      });

      return Result;
    }

    private string ShowStringInputDialog(string title, string description, string value = "", int maxLength = 100)
    {
      string Result = null;

      _Parent.Dispatcher.Invoke(() =>
      {
        //InputBox is a WPF Window I created for taking simple
        //string values from the user. This also shows that you can
        //any custom dialog using this approach.

        InputBox input = new InputBox();
        input.Title = title;
        input.Owner = _Parent;
        if (input.ShowDialog(description, value, maxLength).Value)
          Result=input.Value;
        else
          Result=null;
      });

      return Result;
    }

    //Call this somewhere at application startup so that the dialog boxes
    //appear as child windows.
    public void SetParentWindow(Window parent)
    {
      _Parent = parent;
    }
  }
}
1
dotNET

Ich habe meinen eigenen Fensterlader gerollt, der in einer Antwort auf diese Frage beschrieben wurde:

Mehrere WPF-Ansichten in einer Anwendung verwalten

1
Mark Bostleman

Ich habe zu diesem Thema einen ziemlich umfassenden Artikel geschrieben und auch eine Pop-In-Bibliothek für MVVM-Dialoge entwickelt. Die strikte Einhaltung von MVVM ist nicht nur möglich, sondern auch sehr sauber, wenn sie ordnungsgemäß implementiert wird, und sie kann leicht auf Bibliotheken von Drittanbietern erweitert werden, die sich nicht selbst daran halten:

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

0
Mark Feldman

Tut mir leid, aber ich muss reinkommen. Ich habe einige der vorgeschlagenen Lösungen durchlaufen, 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 gibt es integrierte Benachrichtigungs- und Bestätigungs-Popups. 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 Senior-Entwickler und niemand hat sich etwas Besseres einfallen lassen. Unsere bisherige Lösung bestand aus dem Dialog-Service in einer Überlagerung und der Verwendung einer Presenter-Klasse, um dies zu ermöglichen, aber Sie mussten Fabriken für alle Dialog-Ansichtsmodelle usw. haben. 

Das ist nicht trivial, aber auch nicht sehr kompliziert. Und es ist in Prism integriert und wird deshalb am besten (oder besser) praktiziert.

Meine 2 Cent!

0
jogi

Ich habe über ein ähnliches Problem nachgedacht, als ich fragte, wie das Ansichtsmodell für eine Aufgabe oder ein Dialogfeld wie aussehen sollte.

Meine aktuelle Lösung sieht so 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 eine Benutzereingabe erforderlich ist, wird eine Instanz von SelectionTaskModel mit den möglichen Auswahlmöglichkeiten für den Benutzer aufgerufen. Die Infrastruktur sorgt dafür, dass die entsprechende Ansicht aufgerufen wird, die in der richtigen Zeit die Funktion Choose() mit der Wahl des Benutzers aufruft.

0
David Schmitt

Ich hatte mit dem gleichen Problem zu kämpfen. Ich habe einen Weg gefunden, um zwischen der View und dem ViewModel zu kommunizieren. Sie können das Senden einer Nachricht vom ViewModel an die View veranlassen, um eine Messagebox anzuzeigen, und das Ergebnis wird zurückgemeldet. Dann kann das ViewModel auf das von der View zurückgegebene Ergebnis reagieren.

Ich zeige das in meinem Blog :

0
Jaco Karsten