Das Codebehind unter Benutzung des MVVMs

    • WPF

    Es gibt 5 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      Das Codebehind unter Benutzung des MVVMs

      Hallo,
      ich bin mir völlig im klaren darüber, dass ich jetzt wohl ein paar Leute verärgern werden, die fest der Ansicht sind, dass die Codebehind-Datei bei der Benutzung des MVVM-Patterns verboten gehört. Aber ich (und sehr, sehr viele andere) sind der festen Ansicht, dass das Codebehind in manchen Fällen sogar benutzt werden sollte, um das MVVM-Prinzip beizubehalten, was ich im Nachfolgenden erläutern werde. Aber zuerst einmal möchte ich euch noch einmal klar machen, was das MVVM eigentlich ist:

      View <-> ViewModel <-> Model

      Was sehen wir? Der View und das ViewModel kommunizieren, meistens über DataBinding. Zusätzlich dazu kommunizieren das ViewModel und das Model.

      Was ist der View?
      Alle durch die Grafische Benutzeroberfläche (GUI) angezeigten Elemente. Es bindet sich an Eigenschaften des ViewModel, um Inhalte darzustellen und zu manipulieren sowie Benutzereingaben weiterzuleiten.

      Was ist das ViewModel?
      Das ViewModel beinhaltet die UI-Logik (Model der View) und dient als Bindeglied zwischen View und obigem Model. Einerseits tauscht es Information mit dem Model aus, ruft also Methoden oder Dienste auf. Andererseits stellt es der View öffentliche Eigenschaftenund Befehle zur Verfügung.

      Was ist das Model?
      Datenzugriffsschicht für die Inhalte, die dem Benutzer angezeigt und von ihm manipuliert werden. Dazu benachrichtigt es über Datenänderungen und führt eine Validierung der vom Benutzer übergebenen Daten durch. Es beinhaltet die gesamteGeschäftslogik und ist für sich alleine durch Unit Tests überprüfbar.


      Zusammengefasst bedeutet das folgendes: Der View ist alles was wir sehen, das ViewModel verarbeitet Benutzerangaben und stellt Informationen zur Verfügung. Das Model definiert die eigentliche Logik des Programms.



      Wo steht jetzt, dass die Codebehind-Datei unter Anwendung des MVVMs verboten ist?

      Richtig, nirgendwo. Oben steht sogar, dass sie erlaubt ist. Wieso? Gucken wir uns mal an, wie unser Codebehind-Datei von dem MainWindow definiert ist:

      C#-Quellcode

      1. public partial class MainWindow


      Alle, die nicht wissen, was partial bedeutet, klicken hier.

      Also haben wir gerade bewiesen, dass der View nicht nur aus der xaml-Datei besteht, sondern auch aus der xaml.cs-Datei. Versteht ihr, worauf ich hinaus will? Der View besteht auch aus der Codebehind-Datei.

      Ich möchte nicht, dass ihr das falsch versteht: MVVM besagt, dass der View von der Logik getrennt werden soll. Das werden wir auch beibehalten. Es ist auch nun mal so, dass das Codebehind meistens ziemlich leer bleibt, weil eben das meiste durch das ViewModel geregelt wird. Aber so lange es keine Logik ist, sondern ausschließlich zu dem View gehört, solltet ihr die Codebehind-Datei benutzten. Sonst würdet ihr gegen das MVVM-Prinzip verstoßen. Das sagt nämlich, dass der View und das ViewModel getrennt sein sollen und das gilt für beide Richtungen.


      Was gehört jetzt in die Codebehind-Datei, und was nicht?
      • Alles, was irgendwie mit Logik zu tun hat, gehört nicht in die Codebehind-Datei
      • Events können behandelt werden, von wo aus wir Methoden im ViewModel aufrufen können
      • Alles was ausschließlich mit dem View zu tun hat, Beispiele folgen

      Konkrete Beispiele für die Benutzung des Codebehinds:
      • Wir wollen auf das Closing-Event reagieren. Wir reagieren einfach in der Codebehind-Datei auf das Event und rufen dann im ViewModel ein paar Methoden auf, die zB. Objekte verwerfen oder Dienste beenden
      • Wir wollen, dass beim Klick in eine TextBox der komplette Text markiert wird. Einfach auf das Event reagieren und den Text markieren.
      • Wir wollen eine Dragbare-ListView hinbekommen. Dazu können wir einfach auf das DragEnter-Event reagieren und den Effekt setzten (e.Effects = DragDropEffects.WhatEver). Das gleiche gilt für das Drop-Event
      • Wir möchten ein eigenes Fenster-Design. Dazu müssen wir den Close-Button irgendwie dazu bringen, damit sich das View schließt -> Close-Event, worin wir das Fenster schließen

      https://stackoverflow.com/questions/20883199/wpf-mvvm-code-behind schrieb:

      MVVM is not here for you to write thousands of ugly lines of code in ViewModels, it's here to make the code testable and to introduce a separation of concerns.

      http://blog.jerrynixon.com/2012/08/most-people-are-doing-mvvm-all-wrong.html schrieb:

      MVVM is not an exercise to remove all the code from your XAML code behind files. Instead, MVVM intends to separate the logic you need to access and interact with your data from the logic you need to interact with your UI. For example, loading your data, validating your data, manipulating your data, and saving your data – that’s all part of the View Model. Conversely, running animations, responding to gestures, and adding the bling that makes your app stand apart – that’s part of the View.

      (Das hat immerhin ein Microsoft-Entwickler geschrieben)

      Und nochmal ein schönes Zitat, was mich ein bisschen zum lächeln bringt:

      https://stackoverflow.com/questions/6421372/why-to-avoid-the-codebehind-in-wpf-mvvm-pattern schrieb:

      One thing to remember though is that MVVM is a pattern, and a pattern is simply a recipe or prescriptionfor achieving a certain result in a certain situation. It shouldn't be treated as a religion, where non-believers or non-conformers are going to go to some purgatory (although adherence to the pattern is good if you want to avoid the purgatory of maintenance hell and code smell).

      - Ihr solltet euch immer im klaren darüber sein, dass MVVM nur ein Pattern ist. Nichts weiter. Es ist ein einfaches Rezept, um ein bestimmtes Ergebnis in einer bestimmten Situation zu erhalten. Es sollte nicht wie eine Religion behandelt werden, in der Ungläubige oder Leute, die nicht mitmachen, im Fegefeuer landen (obwohl die Einhaltung des Patterns natürlich nicht schlecht ist).


      Quellen:
      de.wikipedia.org/wiki/Model_View_ViewModel
      codekicker.de/fragen/wpf-verwe…vvm-codebehind-diskussion
      stackoverflow.com/questions/20883199/wpf-mvvm-code-behind
      stackoverflow.com/questions/16…ode-behind-best-practices
      programmers.stackexchange.com/…nsidered-part-of-the-view
      stackoverflow.com/questions/64…ehind-in-wpf-mvvm-pattern
      dofactory.com/topic/1279/wpf-mvvm-code-behind-code.aspx
      blog.jerrynixon.com/2012/08/mo…doing-mvvm-all-wrong.html
      Dateien
      Mfg
      Vincent

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „VincentTB“ ()

      Jo, vielen Dank - deine Code-Beispiele finde ich einsichtig und überzeugend
      Code-Zitat aus obigem Upload

      C#-Quellcode

      1. //...
      2. private void ListView_DragEnter(object sender, DragEventArgs e) {
      3. e.Effects = DragDropEffects.Move;
      4. }
      5. private void ListView_Drop(object sender, DragEventArgs e) {
      6. if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
      7. ViewModels.MainViewModel.Instance.Drop((string[])e.Data.GetData(DataFormats.FileDrop));
      8. }
      9. }
      10. private void TextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
      11. ((TextBox)sender).SelectAll();
      12. }
      Bestimmte User-Aktionen spiegeln sich halt nicht in geänderten Properties wieder, auch nicht in Commands, sondern nur in Events, und an Events kann man nicht binden.
      Besonders das mit der Textbox ist plausibel, weil die .SelectAll - Aktion hat mit den Daten auch gar nichts zu tun, sondern die Wirkung verbleibt da, wo sie ausgelöst wurde: im View.

      Mit dem DragDrop ist nicht ganz so fein: Da muss über eine global zugreifbare Viewmodel-Instanz ins Viewmodel gegrabscht werden - also Kapselung ist eigentlich was anneres - aber was will man machen?

      Manche MVVM-Puristen programmieren nun aufwändig Behavior-Klassen, die als AttachedProperty bindebar sind - und die dann oft auch nix anneres machen als global ins Viewmodel zu grabschen 8| .

      Oder ich zB hole mir ein Control auch mal ins Viewmodel rein, wenn ich seine Events brauche - krasser Verstoß gegen die reine Lehre - aber was will man machen?

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

      ErfinderDesRades schrieb:

      Bestimmte User-Aktionen spiegeln sich halt nicht in geänderten Properties wieder, auch nicht in Commands, sondern nur in Events, und an Events kann man nicht binden.

      Da hast du recht und das ist auch recht nervig. Jedoch gibt es tolle Patterns welche tolle Lösungen anbieten um generisch für jede Situation ein Event an einen Command binden zu können. Einfach mal googeln nach "wpf mvvm event binding" (oder ähnlichem).

      ErfinderDesRades schrieb:

      Oder ich zB hole mir ein Control auch mal ins Viewmodel rein, wenn ich seine Events brauche - krasser Verstoß gegen die reine Lehre - aber was will man machen?

      Da würde ich das Codebehind dann aber doch deutlich bevorzugen. Eigentlich sollte es immer so sein, dass das ViewModel wirklich nichts von den Controls weiß. Man kann das auch wirklich immer durchziehen. Bei manchen ist es auch gang und gebe, dass man die ViewModels, die Models und Views in seperate Assemblies steckt. Beispiel: Views sind für EINEN Client. ViewModels können über mehrere Clients hinweg verwendet werden und die Models z.B. auch auf Serverseite, bei WebServices,...
      Also wenn schon EventHandler, dann wenigstens ins Codebehind und nicht direkt ins ViewModel. Finde ich dann wirklich äußerst unschön :( . PS: Schnell einen EventHandler ins Codebehind bietet sich vor allem auch bei kleineren Projekten sehr gut an, da sich der Aufwand Pattern für Event-Binding zu implementieren nicht lohnt.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.

      thefiloe schrieb:

      ein Event an einen Command binden zu können. Einfach mal googeln nach "wpf mvvm event binding"
      Ich bin diesem Hinweis in einiger Umständlichkeit gefolgt, und komme zu folgendem Ergebnis:
      Man braucht einen Verweis auf System.Windows.Interactivity.
      Falls diese dll nicht installiert ist, kann man mit
      Menu-Extras-PaketManager-PaketeVerwalten (mag auch abweichend bezeichnet sein) sich
      Blend Interactivity for WPF v4.0 nach-installieren.
      Dieses Paket enthält u.a. die System.Windows.Interactivity.dll - steht auch inne Paket-Beschreibung.

      Anschließend kann im Xaml i:InterAction.Triggers attached werden (#23 - #27):

      XML-Quellcode

      1. <Window x:Class="Anwendungen_des_Codebehinds.MainWindow"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:vm="clr-namespace:Anwendungen_des_Codebehinds.ViewModels"
      5. xmlns:converter="clr-namespace:Anwendungen_des_Codebehinds.Converter"
      6. xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
      7. Title="MainWindow" Height="350" Width="600" DataContext="{x:Static vm:MainViewModel.Instance}">
      8. <Window.Resources>
      9. <converter:RoundSizeConverter x:Key="RoundSizeConverter"/>
      10. <converter:FalseAtNullValue x:Key="FalseAtNullValue"/>
      11. </Window.Resources>
      12. <Grid>
      13. <Grid.ColumnDefinitions>
      14. <ColumnDefinition Width="*"/>
      15. <ColumnDefinition Width="2*"/>
      16. </Grid.ColumnDefinitions>
      17. <ListView ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}" DragEnter="ListView_DragEnter" Drop="ListView_Drop" SelectionMode="Single" AllowDrop="True">
      18. <ListView.ItemTemplate>
      19. <DataTemplate>
      20. <TextBlock Text="{Binding Name}"/>
      21. </DataTemplate>
      22. </ListView.ItemTemplate>
      23. <i:Interaction.Triggers>
      24. <i:EventTrigger EventName="Drop">
      25. <i:InvokeCommandAction Command="{Binding ItemsDropCommand}" />
      26. </i:EventTrigger>
      27. </i:Interaction.Triggers>
      28. </ListView>
      29. <--usw-->
      Und das Drop-Event ruft richtig das Command im Viewmodel auf - ohne Codebehind :thumbsup:
      Nur folgendes Problem: Ich muss ja auch die EventArgs des Drop-Events ins Viewmodel bringen - wie geht das?
      Ich kann dem Command auch einen CommandParameter mitgeben, aber ich komm nicht drauf, wie ich im Xaml die Eventargs des DropEvents als CommandParameter festlege ;(

      Anmerkung zu VisualStudio 2010
      Der PaketManager ist erst ab 2012 verfügbar, ob die Interaction.dll oder eine gleichartige auf anderem Wege downloadbar ist (und 2010-kompatibel) ist möglich, weiß ich aber nicht.

      Dateien

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „ErfinderDesRades“ ()

      ErfinderDesRades schrieb:

      Der PaketManager ist erst ab 2012 verfügbar, ob die Interaction.dll oder eine gleichartige auf anderem Wege downloadbar ist (und 2010-kompatibel) ist möglich, weiß ich aber nicht.

      NuGet ist erst ab 2012 fix in Visual Studio integriert. NuGet selbst kannst du jedoch schon lange als Erweiterung nutzen (unter anderem auch in VS 2010). Außerdem kann NuGet auch außerhalb von Visual Studio verwendet werden und ist sogar in andere IDE's wie z.B. SharpDevelop integriert.

      ErfinderDesRades schrieb:

      Ich kann dem Command auch einen CommandParameter mitgeben, aber ich komm nicht drauf, wie ich im Xaml die Eventargs des DropEvents als CommandParameter festlege

      Naja. Das weiß ich auch nicht. Jedoch schlägt mir Google Chrome selbst bei der Suche schon alles vor. Die ersten 3 Ergebnisse schauen recht interessant aus:
      stackoverflow.com/questions/13…-a-command-using-triggers
      stackoverflow.com/questions/62…args-as-command-parameter
      wpfplayground.com/2014/01/18/i…rgs-as-command-parameter/

      Wie gesagt. Für sehr kleine Projekte lohnt es sich nicht wirklich. Für größere Projekte finde ich es jedoch recht angenehm.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Also der 3. Link hat mich gelehrt, dass man statt der i:InvokeCommandAction eine eigene TriggerAction schreiben muss, sogar ziemlich einfach gestrickt:

      C#-Quellcode

      1. using System.Windows;
      2. using System.Windows.Input;
      3. using System.Windows.Interactivity;
      4. namespace Helpers {
      5. public class EventCommandAction : TriggerAction<DependencyObject> {
      6. public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
      7. "Command", typeof(ICommand), typeof(EventCommandAction));
      8. public ICommand Command {
      9. get { return (ICommand)this.GetValue(CommandProperty); }
      10. set { this.SetValue(CommandProperty, value); }
      11. }
      12. protected override void Invoke(object parameter) {
      13. if (AssociatedObject == null || cmd == null) {
      14. MessageBox.Show("EventCommandAction fehlerhaft initialisiert");
      15. return;
      16. }
      17. var cmd = Command;
      18. if (cmd.CanExecute(parameter)) cmd.Execute(parameter);
      19. }
      20. }
      21. }
      Also anne Basisklasse TriggerAction<DependencyObject> wird eine DependancyProperty drangemacht, und die Invoke-Methode überschrieben.
      Eigentümlich nur, dass sowas notwendiges wie einfaches nicht gleich in der Interactivity-Dll mit drinne ist ?(

      Im Xaml siehts dann so aus:

      XML-Quellcode

      1. <ListView ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}" DragEnter="ListView_DragEnter" SelectionMode="Single" AllowDrop="True">
      2. <ListView.ItemTemplate>
      3. <DataTemplate>
      4. <TextBlock Text="{Binding Name}"/>
      5. </DataTemplate>
      6. </ListView.ItemTemplate>
      7. <i:Interaction.Triggers>
      8. <i:EventTrigger EventName="Drop">
      9. <hlp:EventCommandAction Command="{Binding ItemsDropCommand}" />
      10. </i:EventTrigger>
      11. </i:Interaction.Triggers>
      12. </ListView>
      Und im MainViewmodel kommt das Command inklusive EventArgs an:

      C#-Quellcode

      1. private DelegateCommand<DragEventArgs> _ItemsDropCommand;
      2. public DelegateCommand<DragEventArgs> ItemsDropCommand {
      3. get {
      4. return _ItemsDropCommand ?? (_ItemsDropCommand = new DelegateCommand<DragEventArgs>(ItemsDropped));
      5. }
      6. }
      7. private void ItemsDropped(DragEventArgs e) {
      8. foreach (string file in (string[])e.Data.GetData(DataFormats.FileDrop)) Files.Add(new FileInfo(file));
      9. }
      10. private ObservableCollection<FileInfo> files = new ObservableCollection<FileInfo>();
      11. public ObservableCollection<FileInfo> Files { get { return files; } }
      Hat den Vorzug, dass kein Codebehind vonnöten, und insbesondere dass nicht vom Codebehind aus ins MainViewmodel reingegrabscht werden muss.
      Dateien

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()