none
MVVM und MessageBox RRS feed

  • Frage

  • Hallo,
    ich setze das MVVM-Framework ein.
    Mir wird häufig erzählt, dass das Verwenden der System.Windows.MessageBox nicht ganz dem MVVM-Prinzip entsprechen würde.
    Ist der einzige Grund dafür, dass der Aufruf der MessageBox vom ViewModel aus erfolgt?
    Wie setzt ihr das Aufrufen/Anzeigen einer MessageBox um, so dass dies dem MVVM-Prinzip entspricht?

    Alexander

    Dienstag, 1. Oktober 2013 19:40

Antworten

  • Hi Alexander,

    Vielleicht noch mal kurz zur MessageBox oder anderen Dialogen: Ein Ziel von MVVM ist es, dass das ViewModel Unit-testbar ist. Wenn Du in Deinem VM eine MessageBox anzeigst, dann lässt sich die entsprechende VM-Methode nicht mehr ohne weiteres testen. Daher solltest Du alle UI-Abhängigkeiten aus dem VM bekommen. Möglichkeiten gibt es zuhauf. Für einfache Fälle empfiehlt sich ein einfaches Interface, in Deinem Fall so etwas:

        public interface IMessageBoxService
        {
            MessageBoxResult Show(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.Information, MessageBoxResult defaultResult = MessageBoxResult.None, MessageBoxOptions options = MessageBoxOptions.None);
        }

    Dein ViewModel kann jetzt im Konstruktor eine Instanz entgegen nehmen und diese nutzen:

    public class AlexanderViewModel
    {
        private IMessageBoxService _msgBox;
        public AlexanderViewModel(IMessageBoxService msgBox)
        {
            _msgBox = msgBox;
        }
    
        public void ExecuteSomething()
        {
            if(_msgBox.Show("Löschen?","",MessageBoxButton.YesNo) == MessageBoxResult.Yes)
            {
                // Delete
            }
        }
    }


    Für Deine Applikation kannst Du jetzt eine Implementierung von IMessageBoxService verwenden, die eine MessageBox anzeigt:

        public class MyMessageBoxService
        {
            public MessageBoxResult Show(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.Information, MessageBoxResult defaultResult = MessageBoxResult.None, MessageBoxOptions options = MessageBoxOptions.None)
            {
                return MessageBox.Show(messageBoxText, caption, button, icon, defaultResult, options);
            }
        }

    Für einen Unit-Test kannst Du eine Implementierung nutzen, die bspw. folgendes macht:

     public class TestMessageBoxService
        {
            public MessageBoxResult Show(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.Information, MessageBoxResult defaultResult = MessageBoxResult.None, MessageBoxOptions options = MessageBoxOptions.None)
            {
                return MessageBoxResult.Yes;
            }
        }

    Damit ist Dein ViewModel jetzt unabhängig von der MessageBox. Diesen Ansatz verwende ich in Projekten sehr oft. Dabei kommt meist noch ein Dependency Injection-Framework zum Einsatz, das zu den Interfaces die konkreten Implementierungen aufsucht und in diesem Fall zur Laufzeit bspw. ein MyMessageBoxService an den Konstruktor des AlexanderViewModels übergibt. Ich verwende oft StructureMap als DI-Framework, aber evtl. ist das auch nur eine persönliche Vorliebe. :-)

    Falls Du kein DI-Framework nutzt, kannst Du natürlich auch einfach

    new AlexanderViewModel(new MyMessageBoxService()) schreiben.

    Wie auch immer, unabhängig davon solltest Du bspw. Peters Antwort von oben überlegen. MessageBoxen bremsen den Benutzer immer leicht aus. Evtl. kommen andere Möglichkeiten für Dich in Frage, um dem Benutzer eine MessageBox zu ersparen.


    Thomas Claudius Huber

    "If you can´t make your app run faster, make it at least look & feel extremly fast"

    twitter: @thomasclaudiush
    homepage: www.thomasclaudiushuber.com
    author of: ultimate Windows Store Apps handbook | ultimate WPF handbook | ultimate Silverlight handbook



    Freitag, 4. Oktober 2013 07:36
  • Hi,
    anstelle der MessageBox kann man die zu treffende Entscheidung mit einem Häkchen (CheckBox) abfordern. Wenn das Häkchen nicht gesetzt ist und die Datei vorhanden ist, wird eine entsprechende Zustandsnachricht angezeigt. Der Anwender kann dann den Vorgang mit gesetztem Häkchen wiederholen. So wird es auch in vielen ASP.NET Anwendungen gemacht. Die Realisierung dieser Bedientechnologie ist mit MVVM sehr einfach. Außerdem kann der Anwender bereits beim ersten Mal das Häkchen setzen und so den Ablauf ohne lästige Rückfrage beschleunigen.

    --
    Peter

    • Als Antwort markiert AlexanderRi Sonntag, 6. Oktober 2013 17:07
    Freitag, 4. Oktober 2013 04:51

Alle Antworten

  • Hallo,
    grundsätzlich wiederspricht das Anzeigen einer MsgBox dem MVVM-Pattern. Eben wegen dem Aufruf von ShowDialog bzw. auch Show.

    Wie man das Problem lösen kann, ist wirklich eine interesannte Frage mit mehr Lösungsideen als ich vermutet habe:
    http://stackoverflow.com/questions/454868/handling-dialogs-in-wpf-with-mvvm

    Ich persönlich wäre für das überlagernde "Feldchen" mit der Nachricht (1. Antwort). So oder so wird die normale MsgBox kaum genutzt. Um ehrlich zu sein, das Design der Standartmeldung passt auch nicht wirklich zu WPF-Anwendungen.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Dienstag, 1. Oktober 2013 20:05
    Moderator
  • Hi,

    das Anzeigen eines Dialoges widerspricht nicht den MVVM Pattern, das Pattern macht nur keine Aussage dazu. Kleine aber wichtiger Unterschied ;).

    Die Frage ist also wie binde ich Sinnvoll einen Dialog ein.

    Meines Erachtens gibt es leider keine Standardlösung.

    Wenn du einen "Komplexen" Dialog hast, kannst du ihn kannst du ihn im Prinzip als "2. MVVM Komponente" betrachten. Da gibt es bei Prisem , N-Layerd ... Guid und MVVM Light als Beispiel schon eine Lösung. Grobe über Nachrichten und wenn ich Tom da richtig verstanden habe auch seine bevorzugte Methode. Und ich muss gestehen die Lösung finde ich auch nicht schlecht.

    Wenn du jetzt eine einfachen Dialog hast und sagst nur um einen Dialog anzuzeigen ist das jetzt zu viel Aufwand. Gibt es noch andere Lösungen. Je nachdem wie du das MVVM Pattern implementiert hast. 

    Wenn dein Dialog Logik enthält, gehört er meines erachten mehr zum VM. Die Trivial Lösung ist es den Dialog vom VM erzeugen zu lassen (Finde ich nicht so gut). Wenn du Unit Test schreiben möchtest hast du da dem Punkt ein Problem. Das kannst du umgehen dadurch das dein VM entweder ein Event schmeißt (an die Klasse die das VM erzeugt hat) um die Daten zu bekommen oder über Construktor Injektion (ein Interface bietet sich hier an) und dann die Methode der Klasse aufzurufen um die Rückgabe zu bekommen.

    Wenn es nur um eine Anzeige geht kann auch einfach die View die Aufgabe übernehmen.

    Was soll ich sagen ohne die Anforderungen/Implementierung zu kennen kann man nicht wirklich eine gute Lösung anbieten. 

    MFG

    Björn

       

         

     

    Dienstag, 1. Oktober 2013 23:11
  • Hi Alexander,
    die erste Frage, die zu beantworten ist, heißt: "Wozu benötigst Du eine MessageBox". Ich finde solche Boxes nervig und nicht notwendig. Wenn dem Anwender etwas mitzuteilen ist, dann reicht eine Statuszeile und ggf. die Verhinderung der Ausführung einer abschließenden Aktion.

    --
    Peter

    Mittwoch, 2. Oktober 2013 08:14
  • Hallo Peter,

    der Anwender soll z.B. eine Entscheidung treffen: Datei überschreiben ja oder nein. Dafür ist eine Messagebox notwendig; ansonsten nicht. Die Lösungen die ich bisher imm Sinne von MVVM gesehen habe sind für eine solche einfache Aufgabe sehr aufwendig. 

    Alexander

     

    Mittwoch, 2. Oktober 2013 15:03
  • Hi Alexander,

    erstelle dir ein Interface z.B. IOverrideDataRequest mit der Methode CanOverride (Rückgabe wert boolean).

    Den Konstruktor des ViewModels kannst du dann um den Parameter erweitern z.B. MyViewModel(IOverrideDataRequest  OverideDataRequest).

    Das ViewModel kann dann bei Bedarf die Methode aufrufen und auf das Ergebnis reagieren.

    Ob du dann in der Eigndlichen Implementierung eine MessageBox Anzeigst, den Wert aus einer Konstanten liest oder sonst was machst ist dann den ViewModel egal.

    Was mir beim schreiben aufgefallen ist, da du auf einen Datenträger zugreifst solltest du schon den Zugriff hinter ein Interface weg kapseln.

    MFG

    Björn

    Mittwoch, 2. Oktober 2013 17:07
  • Hi,
    anstelle der MessageBox kann man die zu treffende Entscheidung mit einem Häkchen (CheckBox) abfordern. Wenn das Häkchen nicht gesetzt ist und die Datei vorhanden ist, wird eine entsprechende Zustandsnachricht angezeigt. Der Anwender kann dann den Vorgang mit gesetztem Häkchen wiederholen. So wird es auch in vielen ASP.NET Anwendungen gemacht. Die Realisierung dieser Bedientechnologie ist mit MVVM sehr einfach. Außerdem kann der Anwender bereits beim ersten Mal das Häkchen setzen und so den Ablauf ohne lästige Rückfrage beschleunigen.

    --
    Peter

    • Als Antwort markiert AlexanderRi Sonntag, 6. Oktober 2013 17:07
    Freitag, 4. Oktober 2013 04:51
  • Hi Alexander,

    Vielleicht noch mal kurz zur MessageBox oder anderen Dialogen: Ein Ziel von MVVM ist es, dass das ViewModel Unit-testbar ist. Wenn Du in Deinem VM eine MessageBox anzeigst, dann lässt sich die entsprechende VM-Methode nicht mehr ohne weiteres testen. Daher solltest Du alle UI-Abhängigkeiten aus dem VM bekommen. Möglichkeiten gibt es zuhauf. Für einfache Fälle empfiehlt sich ein einfaches Interface, in Deinem Fall so etwas:

        public interface IMessageBoxService
        {
            MessageBoxResult Show(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.Information, MessageBoxResult defaultResult = MessageBoxResult.None, MessageBoxOptions options = MessageBoxOptions.None);
        }

    Dein ViewModel kann jetzt im Konstruktor eine Instanz entgegen nehmen und diese nutzen:

    public class AlexanderViewModel
    {
        private IMessageBoxService _msgBox;
        public AlexanderViewModel(IMessageBoxService msgBox)
        {
            _msgBox = msgBox;
        }
    
        public void ExecuteSomething()
        {
            if(_msgBox.Show("Löschen?","",MessageBoxButton.YesNo) == MessageBoxResult.Yes)
            {
                // Delete
            }
        }
    }


    Für Deine Applikation kannst Du jetzt eine Implementierung von IMessageBoxService verwenden, die eine MessageBox anzeigt:

        public class MyMessageBoxService
        {
            public MessageBoxResult Show(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.Information, MessageBoxResult defaultResult = MessageBoxResult.None, MessageBoxOptions options = MessageBoxOptions.None)
            {
                return MessageBox.Show(messageBoxText, caption, button, icon, defaultResult, options);
            }
        }

    Für einen Unit-Test kannst Du eine Implementierung nutzen, die bspw. folgendes macht:

     public class TestMessageBoxService
        {
            public MessageBoxResult Show(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.Information, MessageBoxResult defaultResult = MessageBoxResult.None, MessageBoxOptions options = MessageBoxOptions.None)
            {
                return MessageBoxResult.Yes;
            }
        }

    Damit ist Dein ViewModel jetzt unabhängig von der MessageBox. Diesen Ansatz verwende ich in Projekten sehr oft. Dabei kommt meist noch ein Dependency Injection-Framework zum Einsatz, das zu den Interfaces die konkreten Implementierungen aufsucht und in diesem Fall zur Laufzeit bspw. ein MyMessageBoxService an den Konstruktor des AlexanderViewModels übergibt. Ich verwende oft StructureMap als DI-Framework, aber evtl. ist das auch nur eine persönliche Vorliebe. :-)

    Falls Du kein DI-Framework nutzt, kannst Du natürlich auch einfach

    new AlexanderViewModel(new MyMessageBoxService()) schreiben.

    Wie auch immer, unabhängig davon solltest Du bspw. Peters Antwort von oben überlegen. MessageBoxen bremsen den Benutzer immer leicht aus. Evtl. kommen andere Möglichkeiten für Dich in Frage, um dem Benutzer eine MessageBox zu ersparen.


    Thomas Claudius Huber

    "If you can´t make your app run faster, make it at least look & feel extremly fast"

    twitter: @thomasclaudiush
    homepage: www.thomasclaudiushuber.com
    author of: ultimate Windows Store Apps handbook | ultimate WPF handbook | ultimate Silverlight handbook



    Freitag, 4. Oktober 2013 07:36
  • Hallo Alexander... lieber spät als nie ;-).

    das MVVM-Pattern dient nur dazu um "automatisierte" Unit-Tests durchführen zu können und die Entwicklung von UI und Logik zu trennen. Es gibt keinen weiteren Grund dafür!!!

    Wenn du "automatisierte" Unit-Tests, beispielsweise während einem Build-Prozess, der womöglich auch noch Nachts läuft, durchführen möchtest, wäre es natürlich ungünstig, wenn dabei ein Oberflächen-Element (Dialog etc.) aufgerufen wird, denn dann bleibt der Unit-Test und auch der Build-Prozess, an dieser Stelle stehen bis eine Benutzeraktion erfolgt... wäre nicht so toll oder? Ergo, versucht man solche Aufrufe im ViewModel zu umgehen, dazu werden die unterschiedlichsten Strategien verfolgt.

    Als Software-Architekt bekomme ich regelmäßig die Krätze an den Hals, wenn ein Entwickler versucht auf biegen und brechen, in der Regel mit einem wesentlich höherem Code-Aufwand Oberflächen-Elemente aus dem ViewModel heraus zu halten, indem mit Interfaces gearbeitet wird, die dann eine ganz spezielle Implementierung (Mocks) bekommen damit die Unit-Tests funktionieren. Viel schlimmer noch, wenn versucht wird mit hohem Aufwand Benachrichtigungssysteme zu implementieren, die das dann erledigen sollen. Das ist ein Witz schlechthin, denn du sorgst dafür dass die Codebasis größer wird, als wäre sie eh nicht schon groß genug und du sorgst dafür, dass der Wartungsaufwand größer wird. Natürlich gibt es da ganz tolle und interessante Ansätze um so etwas zu implementieren, ich habe selbst viele davon umgesetzt... und am Ende alle verworfen *smile*

    Das MVVM-Pattern verbietet dir nicht (wie weitläufig angenommen und auch propagiert wird) den Code-Behind zu benutzen. Du kannst getrost im CodeBehind einen/mehrere Handler implementieren, die in der Lage sind Dialoge etc. aufzurufen und die Ergebnisse mit dem aktuellen DataContext respektive ViewModel abzugleichen. All das ist auch ausschließlich in Xaml möglich. Meiner Meinung nach ist der Code-Behind die geeignetste Stelle unter Berücksichtigung der negativen Folgen (mehr Code... unnötiges aufblasen der Unit-Tests, höherer Wartungsaufwand) statt auf biegen und brechen zu versuchen solche Funktionalitäten im ViewModel zu implementieren... nebenbei erspart es dir, je nach Projektgröße, einiges an Zeit und auch Ärger ;-).

    Viele Wege führen nach Rom.. alle führen auch zum Ziel, der eine oder andere schneller und weniger Steinig.

    Viele Grüße Carl


    Carl-Christian Schaffert Bahnhofsweg 2 82008 Unterhaching mobile: 0152-33 72 79 49 email: carl-christian.schaffert@dotnet-dev-expert.de skype: carl.christian.schaffert




    Samstag, 12. Oktober 2013 08:18
  • Hi Alexander,
    den Ausführungen von Carl-Christian kann ich uneingeschränkt zustimmen. Wie so oft ist der Mittelweg die optimale Lösung. Den passenden Mittelweg zu finden ist aber nicht immer einfach. Beispielsweise fehlen bei der Anwendung von MVVM in Silverlight einige wichtige Klassen, so wird ICommand nicht wie in WPF unterstütz. Man kann natürlich Zusatzkomponenten für ICommand in Silverlight nutzen oder einfach nur im CodeBehind die Methodenaufrufe mappen. Die Nutzung von Codebehind für diesen Fall reduziert die Bedeutung von MVVM praktisch nicht.

    --
    Peter 

    Samstag, 12. Oktober 2013 11:02
  • Interessante Diskussion. :-)

    Ich sehe es etwas anders als Carl-Christian. Neben der Unterstützung von Unit-Tests und besserer Trennung von UI und Logik führt MVVM insbesondere bei grösseren Projekten zu einem wichtigen Punkt:

    bessere Wartung

    Wenn ich zwar am schnellsten in Rom bin, aber dann von Rom nach Neapel 3 Jahre brauche, dann ist das auch nicht wirklich toll (Carl-Christian: Zugegeben etwas übertrieben ausgedrückt, nimm's mir nicht übel :-), aber prinzipiell meine Erfahrung aus Projekten.)

    Klar, ein Projekt sauber strukturiert mit MVVM durchzuziehen braucht an manchen Stellen etwas mehr Aufwand. Aber aus meinen Erfahrungen hat sich dieser Aufwand im Hinblick auf die Wartung und das Implementieren neuer Features immer gelohnt. Insbesondere beim agilen Entwickeln fallen ja ständig neue Requests an und da freue ich mich, wenn die Logik zentral in einem ViewModel liegt und nicht noch teilweise Logik (Code ok, Logik nein) in der Codebehind-Datei vorhanden ist.

    Den Codeaufwand, um Dialoge aus dem ViewModel herauszuhalten, sehe ich als sehr gering an, da er zudem nur einmalig anfällt. Klar, er ist grösser, wie direkt in die Codebehind-Datei zu programmieren. Aber wird in die Codebehind-Datei programmiert, so wandert auch oft die Logik in die Codebehind-Datei: "Wenn der Dialog true zurückgibt, dann ruf diese Methode des ViewModels auf, ansonsten die andere".
    Ebenso gibt es im ViewModel oft Methoden, die einen Dialog nicht zu Beginn, sondern nach einigen Zeilen anzeigen müssen, abhängig von den Daten im ViewModel. Regel ich dies über die Codebehind-Datei, ergibt sich folgendes: In der Codebehind-Datei muss ich abhängig der Daten des ViewModels (Logik!!) etwas tun, um dann mit etwas anderem weiterzufahren (Logik!!). Zudem werden logisch zusammenhängende Teile bei dieser Vorgehensweise oft zerstückelt. Meine Meinung ist also, durchaus den kleinen Codeaufwand auf sich zu nehmen, um die Dialoge aus dem ViewModel via Interfaces herauszuhalten und dann die Logik sauber im ViewModel abzubilden.

    Der Aussage "Das MVVM-Pattern verbietet dir nicht (wie weitläufig angenommen und auch propagiert wird) den Code-Behind zu benutzen." stimme ich voll und ganz zu. Bei der Codebehind-Datei sollte man einfach darauf achten, dass die Logik wirklich weiterhin im ViewModel bleibt. Man sollte zwar versuchen, alles ins ViewModel reinzupacken, aber wenn's eben deutlich einfacher erscheint, etwas über die Codebehind-Datei zu regeln, dann schreibe ich es dort hin.

    Und jetzt komme ich vielleicht zu einem Punkt, bei dem ich bei Carl-Christians Post ankomme und seine Meinung nachvollziehen kann. :-) Ein interessanter Fall ist ein Doppelklick auf eine Zeile eines DataGrids. Bei diesem Doppelklick soll der Datensatz in einem neuen Detail-Dialog angezeigt werden, wo ihn der Benutzer editieren kann. Solche Szenarien habe ich in vielen Projekten in der Codebehind-Datei. Es bringt mir nichts, beim Doppelklick ein Command im ViewModel auszuführen, damit dieses Command dann wieder über ein Interface den Detail-Dialog anzeigt. Da brauche ich auch nichts testen. Folglich kann ich dies über die Codebehind-Datei einfach regeln und ein Event Handler sehe ich an dieser Stelle durchaus als legitim an.

    Prinzipiell sind wir uns sicherlich alle darüber einig: Ein Pattern, ob MVVM oder sonst eines, ist lediglich ein Pattern und keine Regel. Jeder Entwickler und Architekt sollte stets seine grauen Zellen anfeuern nicht auf Teufel komm raus an einem Pattern festhalten. In manchen Fällen sind andere Lösungen sinnvoller. Patterns nehmen einem folglich nicht das Denken über schlaue und vor allem gute Lösungen ab.

    Um nochmals auf MVVM zurückzukommen: Je nach Projektgrösse und -komplexität kann es natürlich ein Overhead sein, alles zu abstrahieren und ggf. über einen Event Mechanismus noch verschiedene ViewModels miteinander sprechen zu lassen. Aber ich glaube, es gibt eine Art Break-Even-Point, bei dem sich der Mehraufwand für ein sauberes MVVM eben in einfacherer Wartung/Erweiterbarkeit ausbezahlt.






     

    Thomas Claudius Huber

    "If you can´t make your app run faster, make it at least look & feel extremly fast"

    twitter: @thomasclaudiush
    homepage: www.thomasclaudiushuber.com
    author of: ultimate Windows Store Apps handbook | ultimate WPF handbook | ultimate Silverlight handbook

    Montag, 14. Oktober 2013 13:41