WPF und MVVM richtig einsetzen – Teil 4

Im Teil 3, der ausgehend vom jetzigen Zeitpunkt definitiv zu lange her ist (sorry dafür), hatte ich angekündigt, dass wir uns nun in die eigentlichen Spezialitäten von MVVM Light vorwagen. Hierzu sehen wir uns in diesem Teil Converter und den Messenger an.

Links

Screencast

Vorbemerkungen

Genau genommen suggeriert die Einleitung, dass Converter etwas wären, das es in MVVM ohne „Light“ nicht gäbe. Das will ich gleich korrigieren. Converter sind bereits tief in MVVM eingebaut und sind einfach ein weiterer Baustein, den wir kennen sollten, wenn wir wirklich tiefer in die Materie einsteigen wollen. Sie kommen deshalb erst in diesem Teil der Serie vor, weil sie in Bezug auf Bindings oft essentiell sind.

Das Thema Messenger ist wiederum extrem wichtig, wenn wir durchgehend vermeiden wollen, das MVVM-Paradigma zu vermeiden. Insbesondere die Tatsache, dass das ein ViewModel nichts von einem View wissen darf, ist ohne Technologien, wie den Messenger nicht durchzuhalten.

Converter

Ein Converter entsteht, indem man eine Klasse schreibt (also einen Typ definiert), der das Interface IValueConverter implementiert. Diese ebenso richtige, wie im Moment wahrscheinlich wenig hilfreiche Aussage sollte man immer im Hinterkopf behalten, wenn man mal wieder denkt, dass Converter etwas Schwieriges wären. Der Teufel steckt natürlich wie immer im Detail.

Wozu braucht man Converter? Am besten lässt sich diese Frage oft mit einer Eigenschaft beantworten, die die meisten WPF-Elemente kennen: Visibility. In den guten alten Windows-Forms-Zeiten war das Leben noch einfach. Wollte man ein Element unsichtbar machen, setzte man seine Visible-Eigenschaft vom Typ bool einfach auf false. Visibility in WPF dient dem gleichen Zweck, kenn aber 3 Zustände: Collapsed, Hidden und Visible. Trotz dieser gesteigerten Komplexität haben wir in ViewModels oftmals trotzdem noch eine simple Boolesche Eigenschaft, gegen die Visibility gebunden werden soll. Nehmen wir z.B. unser PersonViewModel aus unserem Beispiel-Projekt. Dieses erbt von BaseViewModel die Boolesche Eigenschaft ValidationOk. Nun wollen wir unseren OK-Button unsichtbar machen, wenn IsOk nicht true liefert. Das folgende XML funktioniert NICHT:

<Button Name="btnTest" Visibility="{Binding ValidationOk}" />

Das wird nicht funktionieren, weil wir dieser Ausdruck in C# folgendes bedeuten würde:

btnTest.Visibility = model.ValidationOk;

Auch Bindings müssen natürlich typsicher sein und hinter einem XAML-Binding steht letztlich eine Klasse, die Code ausführt.

Was wir also brauchen, ist etwas, dass aus einem Boolean das richtige Visibility macht. Und genau dafür sind Converter da. Ein Converter für unseren Fall könnte z.B. so aussehen:

/// <summary>
/// Converts a boolean into the appopriate visibility.
/// </summary>
public class BooleanToVisibilityConverter : IValueConverter
{
    /// <inheritdoc />
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
        {
            throw new ArgumentNullException(nameof(value));
        }
        bool transformed;
        if (!bool.TryParse(value.ToString(), out transformed))
        {
            throw new ArgumentException("Value is not a bool.");                
        }
        return transformed ? Visibility.Visible : Visibility.Collapsed;
    }

    /// <inheritdoc />
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Ein Converter ist also zunächst einmal ein Typ, der IValueConverter implementiert. Dieses Interface fordert uns auf, 2 Methoden zu implementieren: Convert und ConvertBack. Da wir in Listing 2 nur Convert implementiert haben, können wir mit diesem Typ nur von bool nach Visiblity konvertieren. Das reicht für das oben beschriebene Binding völlig aus. Bei aufwendigeren Convertern wird auch gern ConvertBack implementiert.

Sobald wir diesen Typ in userem Projekt haben und zur Sicherheit einmal neu bauen, können wir den Converter im Binding-Dialog auswählen:

Abb. 1: Binding-Dialog

Abb. 1 zeigt den Zustand des Binding-Dialoges, wenn noch kein Converter in einer der verfügbaren Ressourcen zur Verfügung gestellt wird (App.xaml, Window.Ressources usw.). Ein Click auf „Add value converter…“ bringt uns zu:

Abb. 2: ValueConverter aussuchen

Ich habe bewusst am unteren Rand den Haken bei „Show all assemblies“ gesetzt. Das führt dazu, dass wir auch Converter aus den aktuell referenzierten Bibliotheken zu sehen bekommen. Wir man sieht, hätten wir uns also die Arbeit für einen eigenen BooleanToVisibilityConverter sparen können. Da er aber das Prinzip sehr anschaulich macht, werden wir erstmal unseren eigenen nutzen.

Das Binding aus Listing 1 sieht nach der Bestätigung des Dialoges nun so aus:

<Button Name="btnTest" Visibility="{Binding ValidationOk, Converter={StaticResource BooleanToVisibilityConverter}} />

Damit das auch funktioniert, braucht man die statische Ressource mit dem Key BooleanToVisibilityConverter. Den hat der Dialog aus Abb. 1 gleich in den Window-Ressources des Fensters abgelegt:

<Window.Resources>
    <Converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>

In unserem aktuellen Beispiel hätten wir nun auch den Converter aus `System.Windows.Controls‘ nehmen können. Daher werde ich den in Listing 3 dargestellten Converter nicht mit in den Sample-Source bringen. Sehen wir uns stattdessen einen anderen typischen Converter an:

public class AgeToBrushConverter : IValueConverter
{
    #region explicit interfaces

    /// <inheritdoc />
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
        {
            return new SolidColorBrush(Colors.Transparent);
        }
        var transformed = 0;
        if (int.TryParse(value.ToString(), out transformed))
        {
            return transformed < 18 ? new SolidColorBrush(Color.FromRgb(255, 0, 0)) : new SolidColorBrush(Color.FromRgb(0, 255, 0));
        }
        throw new ArgumentException("Value is not valid.");
    }

    /// <inheritdoc />
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Dies ist ein durchaus typisches Szenario für einen benutzerdefinierten Converter. Hier wird festgelegt, dass ein roter Brush zurück gegeben werden soll, wenn eine Person jünger als 18 Jahre ist, ein grüner, wenn sie älter oder gleich 18 Jahre als ist und ein transparenter Brush, wenn die Person kein Alter hat.

Ich habe nun das XAML für das MainWindow im Sample ein wenig erweitert (hier nur der Bereich für die Darstellung einer Person aus den vorherigen Teilen der Serie):

<GroupBox Grid.Column="0" Grid.Row="1" Header="Binding &amp; IDataErrorInfo">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <!-- Firstname -->
        <Label Content="Firstname:" Grid.Column="0" Grid.Row="0" />
        <TextBox
            Text="{Binding PersonModel.Firstname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
            Grid.Column="1" Grid.Row="0" />
        <!-- Lastname -->
        <Label Content="Lastname:" Grid.Column="0" Grid.Row="1" />
        <TextBox
            Text="{Binding PersonModel.Lastname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
            Grid.Column="1" Grid.Row="1" />
        <!-- Age -->
        <Label Content="Birthday:" Grid.Column="0" Grid.Row="2" />
        <DatePicker Grid.Column="1" Grid.Row="2"
                    SelectedDate="{Binding PersonModel.Birthday, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
        <!-- Age -->
        <Label Content="Age:" Grid.Column="0" Grid.Row="3" />
        <Label Content="{Binding PersonModel.Age}" Grid.Column="1" Grid.Row="3"
               Background="{Binding PersonModel.Age, Converter={StaticResource AgeToBrushConverter}, Mode=OneWay}" />
        <!-- OK-Button -->
        <Button Grid.Row="4"
                Grid.Column="0"
                Grid.ColumnSpan="2"
                HorizontalAlignment="Right"
                Margin="5"
                Content="OK"
                Width="100"
                Command="{Binding PersonModel.OkCommand}" />
    </Grid>
</GroupBox>

Das Ergebnis sieht dann im MainWindow ungefähr so aus:

Abb. 3: Verhalten des MainWindow

Grenzen der Converter

Scheinbar sind also Converter ein bequemes Mittel, um schnell zwischen der Business- und View-Logik switchen zu können. Aber Vorsicht! Converter sind nicht ganz billig und können schnell falsch eingesetzt werden. In Listing 6 z.B. benutze ich immer wieder eine neue Instanz eines SolidColorBrush, was gelinde gesagt dämlich ist. Besser wäre es, diesen Brush aus den Ressourcen zu holen oder als Singleton zu implementieren, weil Brushes aus WPF-Sicht recht teuer sind. Man muss sich nur mal vorstellen, dass man eine Liste mit 1000 PersonModel-Instanzen an ein Grid bindet und dass dann für jede Zeile des Grid der Converter ausgewertet wird.

Ich habe mir zur Regel gemacht, dass Converter immer dann sinnvoll sind, wenn ich relativ „billige“ Logik umsetzen möchte und wenn WPF-spezifische Typen, wie halt Brushes zum Einsatz kommen. Alles andere regele ich über Eigenschaften in den ViewModels selbst.

Messenger

Ein PRoblem, das immer wieder auftaucht besteht darin, das MVVM-Pattern sauber einzuhalten. So ganz gelingt das in komplexeren Projekten nicht wirklich. Ein schönes Beispiel für ein Pattern-Problem ist das Öffnen eines neuen Dialoges aus einem ViewModel heraus.

Lasst uns also mal ein kleines Beispiel konstruieren. Nehmen wir mal an, ich wollte aus dem MainWindow heraus ein neues ChildWindow öffnen, wenn man auf einen Button klickt. Das schreit förmlich nach einem entsprechenden Command im MainViewModel. Also los:

/// <summary>
/// Opens a new child window.
/// </summary>
public RelayCommand OpenChildCommand { get; private set; }

Man sollte darauf achten, dass Commands eines ViewModels immer einen privaten Setter haben, weil es einfach unlogisch wäre, wenn etwas von außen die Logik des Commands ändert. Das Command muss nun noch im Constructor des MainViewModels instantiiert werden.

Hier beginnen aber die Probleme! Folgendes geht in MVVM nicht:

/// <summary>
/// Opens a new child window.
/// </summary>
OpenChildCommand = new RelayCommand(() => new ChildWindow().ShowDialog());

Das funktioniert nicht, weil das MainVieWModel i`m Projekt Logic.Ui ist und dieses keine Referenz auf unser View-Projekt hat und haben soll. Das würde nämlich bedeuten, dass:

  1. Die ViewModels an die Views gebunden wären, was das Pattern verbietet.
  2. Eine Zirkel-Referenz entsteht.

Was aber nun? Wenn man etwas anders an die Sache rangeht, wird das Leben hier erheblich leichter. Dazu müssen wir ein wenig ausholen.

Ein Ring sie alle zu binden

Um es gleich vorweg zu nehmen. Wir werden nicht ohne Logic im View-Projekt auskommen. Es geht im Folgenden nur darum, keine binären Bindungen und Reference-Locks aufzubauen, sondern die beiden Welten ViewModel und View weiterhin getrennt voneinander zu lassen.

Wenn man sich klar macht, dass das ViewModel nicht einfach einen View öffnen kann, der sich dann über den DataContext wiederum sein ViewModel „besorgt“, dann bleibt als Option nur eine dritte Komponente, die diese Aufgabe erfüllt. Genau so etwas ist der Messenger. Er agiert als eine Art ServiceBus aus Sicht unseres Projektes und ist in MvvmLight bereits enthalten. Da sowohl das ViewLogic- als auch das View-Projekt Referenzen auf MvvmLight halten, können wir ihn an beiden Stellen nutzen.

Einen ServiceBus kann man sich vereinfacht als eine Art ringförmiges Förderband vorstellen.

Abb. 4: Service Bus

Ein System A (z.B. das ViewModel) sendet eine Nachricht an den ServiceBus. System B (z.B. der View) erhält wirgendwann diese Nachricht und versteht sie. Daraufhin löst das System B die von System A gewünschte Aktion aus.

Es gibt komplexe Service Busses (Enterprise Service Bus), die es ermöglichen diverse Parameter einzustellen (Zustellsicherheit, Ausfallsicherheit usw.). In unserem Fall brauchen wir das aber nicht. Wichtig ist nur, dass alle teilnehmenden System ein und denselben ServiceBus nutzen.

IMessenger

MvvmLight bringt ein Interface (wass sonst) mit. IMessenger kann von Typen implementiert werden, die von sich behaupten, ein Messenger (also ein MVVM-ServiceBus) zu sein. MvvmLight hat naürlich auch schon eine eigene Implementierung dabei und wartet innerhalb eines ViewModelBase mit einer Eigenschaften MessengerInstance auf, die den fix und fertig verdrahteten ServiceBus zurück gibt.

IMessenger hat nun im wesentlichen Send- und Register-Methoden. Send ist dafür da, dass ein Teilnehmer etwas auf den ServiceBus packen kann und Register kann genutzt werden, um mitzuteilen, dass man sich für einen bestimmten Typ von Nachricht interessiert.

Nachrichtentypen

Es empfiehlt sich, jedem Nachrichtentyp eine eigene Klasse zu verpassen. Sehen wir uns das mal genauer an:

public class OpenChildWindowMessage
{
}

Senden

Ich füge dem UI-Logic-Projekt einfach eine simple Klasse OpenChildMessage hinzu. Jetzt kann ich die eine Seite der Kommunikation bereits fertig stellen. Dazu korrigiere ich Listing 8:

OpenChildCommand = new RelayCommand(() => MessengerInstance.Send(new OpenChildWindowMessage()));

Das bedeutet, dass mein MainViewModel einfach eine Nachricht sendet, dass doch bitte jemand ein ChildWindow aufmachen soll. Mehr nicht.

Empfangen und Umsetzen

Gehen wir nun hinüber auf die UI-Projektseite. Im Konstruktor des CodeBehind des MainWindow wird nun die Nachricht registriert und definiert, was bei Eintreffen einer entsprechenden Nachricht geschehen soll:

public MainWindow()
{
    InitializeComponent();
    Messenger.Default.Register<OpenChildWindowMessage>(
        this,
        msg =>
        {
            new ChildWindow().ShowDialog();
        });
}

Hier weichen wir nun natürlich von unseren zuvor gefassten Vorsätzen (keine Logic in Views ab). Ich werde später zeigen, wie man hier vielleicht ein wenig mehr Eleganz hinein bekommt.

Nachdem nun Listing 11 umgesetzt ist, muss nur noch das MainView.xaml ergänzt werden (das ChildWindow und sein ViewModel müssen natürlich irgendwie auch schon im Projekt vorhanden sein! – siehe GitHub).

<Button Visibility="{Binding ValidationOk, Converter={StaticResource BooleanToVisibilityConverter}}"
        Content="ShowChild" 
        Command="{Binding OpenChildCommand, Mode=OneWay}" />

Ein wenig mehr Ordnung im View-Projekt

Achtung! Das Folgende wird etwas verwirrend werden und ist nur für Leute gedacht, die gespannt sind, wie man das Problem mit dem CodeBehind etwas abmildern kann.

Um unsere CodeBehind-Logik nicht ständig bemühen zu müssen, können wir den Code aus Listing 11 auch an eine andere Stelle verlagern. Dazu machen wir uns den Umstand zu Nutze, dass die meisten Anwendungen ein Fenster haben, dass so lange geöffnet bleibt, wie die Anwendung läuft. In unserem Fall ist das das MainWindow.

Wir erstellen nun zunächst eine neue simple Klasse im View-Projekt:

public class MessageListener
{
    #region constructors and destructors

    public MessageListener()
    {
        InitMessenger();
    }

    #endregion

    #region methods

    private void InitMessenger()
    {
        // Hook to the message that states that some caller wants to open a ChildWindow.
        Messenger.Default.Register<OpenChildWindowMessage>(
            this,
            msg =>
            {
                new ChildWindow().ShowDialog();
            });
    }

    #endregion

    #region properties

    public bool BindableProperty => true;

    #endregion
}

Wie man sieht, habe ich in der Methode InitMessenger bereits die Logik aus Listing 11 eingebaut. Die Logik im CodeBehind des MainWindow kann nun wieder raus. Natürlich bleibt das Problem, dass nun niemals der Contructor der neuen Klasse aufgerufen wird. Für dieses Problem machen wir uns die Ressourcen von WPF zu Nutze.

In der App.xaml wird zunächst erstmal ein Eintrag mit dem Verweis auf unsere neue Klasse generiert:

<Application x:Class="codingfreaks.blogsamples.MvvmSample.Ui.Desktop.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         StartupUri="MainWindow.xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         d1p1:Ignorable="d"
         xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:UiLogic="clr-namespace:codingfreaks.blogsamples.MvvmSample.Logic.Ui;assembly=MvvmSample.Logic.Ui"
         xmlns:Desktop="clr-namespace:codingfreaks.blogsamples.MvvmSample.Ui.Desktop">
    <Application.Resources>
        <ResourceDictionary>
            <UiLogic:ViewModelLocator x:Key="Locator"
                                      d:IsDataSource="True" />
            <Desktop:MessageListener x:Key="MessageListener"  />
        </ResourceDictionary>
    </Application.Resources>
</Application>

Die neuen Zeilen sind hervorgehoben. Wir importieren also unseren Namespace und legen einfach einen neuen Ressourcen-Eintrag mit dem Key MessageListener (kann irgendein string sein) an. Das reicht aber noch nicht, weil WPF Ressourcen erst dann initialisert, wenn sie das erste Mal genutzt werden. Genau das regeln wir, indem wir eine Eigenschaft des MainWindow an die seltsame BindableProperty-Eigenschaft aus Listing 13 binden. Dazu ergänzen wir das MainWindow.xaml um folgenden Teil:

<Window.IsEnabled>
    <Binding Path="BindableProperty" Source="{StaticResource MessageListener}"/>
</Window.IsEnabled>

Wie man in Listing 13 sehen kann, ist BindableProperty aus C#-Sicht völlig nutzlos, weil sie einfach immer true zurück gibt. Sie dient einzig und allein dem Zweck, eine Eigenschaft im MessageListener zu haben, an die wir das MainWindow binden können, damit beim Programmstart eine Instanz dieser Klasse entsteht.

Auch ohne das CodeBehind im MainWindow funktioniert unser Service Bus immer noch mit dem Vorteil, dass wir nun wieder ein wenig mehr Ordnung im Pattern haben.

Die Message kann mehr

Jetzt, wo wir also zwischen ViewModel und View Nachrichten austauschen, können wir natürlich übergebem was auch immer wir wollen. Eine simple Erweiterung unserer OpenChildWindowMessage könnte sein:

public class OpenChildWindowMessage
{
    #region constructors and destructors

    public OpenChildWindowMessage(string someText)
    {
        SomeText = someText;
    }

    #endregion

    #region properties

    public string SomeText { get; private set; }

    #endregion
}

entsprechend ändern wir Listing 10 ab:

OpenChildCommand = new RelayCommand(() => MessengerInstance.Send(new OpenChildWindowMessage("Hello Child!")));

und ergänzen eine Eigenschaft im ChildViewModel:

public string MessageFromParent { get; set; }

Wenn wir nun noch das ChildWindow überarbeiten:

<Window x:Class="codingfreaks.blogsamples.MvvmSample.Ui.Desktop.ChildWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:codingfreaks.blogsamples.MvvmSample.Ui.Desktop"
    mc:Ignorable="d"
    Title="ChildWindow" Height="300" Width="300">
    <Window.DataContext>
        <Binding Path="Child" Source="{StaticResource Locator}" />
    </Window.DataContext>
    <Grid>
        <TextBlock Text="{Binding MessageFromParent}" />
    </Grid>
</Window>

brauchen wir eigentlich nur noch den MessageListener anpassen:

Messenger.Default.Register<OpenChildWindowMessage>(
    this,
    msg =>
    {
        var window = new ChildWindow();
        var model = window.DataContext as ChildViewModel;
        if (model != null)
        {
            model.MessageFromParent = msg.SomeText;
        }
        window.ShowDialog();
    });

Das Ergebnis sieht nach einem Klick auf den Button dann ca. so aus:

Abb. 5: Nachricht kommt an

Bewertung

Natürlich ist das alles nicht wirklich optimal. In Listing 19 könnte man nun anmängeln, dass dieser Code „wissen“ muss, dass das ChildViewModel zum ChildWindow gehört und dieses „Wissen“ ja eigentlich über das DataContext-Bindung abgebildet werden soll. Ja stimmt! Aber wir haben das Pattern MVVM logisch gesehen nicht gebrochen. Darum geht es, weil wir nur dann die Vorteile des Patterns, wie z.B. Testbarkeit wirklich ausnutzen können.

Das Internet ist voll von Menschen, die immer wieder Fragen zu MVVM stellen, weil sie völlig in dem Pattern verloren sind. Meist wissen sie nicht, wie sie eine spezielle Logik, wie halt das öffnen von anderen Views einbauen sollen und verdammen nach ein paar Versuchen das ganze MVVM-Thema. Ich denke, mit ein wenig Kompromißbereitschaft und sauberer Programmierung kommt man mit MVVM sehr weit und erhält zum Lohn mehr Spaß beim Entwicklen.

Ausblick

Ich hoffe, der nächste Teil lässt nicht so lange auf sich warten, wie dieser. Ich möchte mich um das Thema der Listen und den Herausforderungen damit kümmern. Wir werden Grids und ListViews binden und uns ansehen, wie so etwas mit MVVM gemacht werden kann.

7 Antworten auf „WPF und MVVM richtig einsetzen – Teil 4“

  1. Hallo,

    ich wollt mich an dieser Stelle für diese Serie bedanken. Das ganze ist sehr gut beschrieben und gibt doch nochmal den ein oder anderen Eindruck wie man etwas umsetzen kann, auch wenn man schon ein weilchen mit MVVM arbeitet.

    Weiter so!
    Gruß Stefan

  2. Hallo,

    ich suche gerade Antworten auf die Frage, ob man bei MVVM überhaupt Converter verwenden soll. Es gibt im Internet einige Meinungen dazu, dass Converter dem MVVM Gedanken widersprechen. Bei mir geht es darum, Zahlen für die UI zu formatieren: die Anzahl der Nachkommastellen in Abhängigkeit der Vorkommastellen. Ich fände es übersichtlicher das mit einem Converter zu regeln als irgendwo im ViewModel durchzuführen. Was sagst Du dazu?

    1. Ich sehe das so dass bei einem Converter der UI Entwickler schuld ist wenn es nicht geht, beim ViewModel der Programmierer. Wo es gemacht wird ist Geschmackssache. Wenn du als Entwickler dem UI Entwickler die Arbeit leichter machen willst dann mach die Konvertierung im ViewModel, vorteil hierbei ist du kannst einen Unittest dafür schreiben (geht im Umkehrschluss natürlich auch für den Converter) und jeder View der auf den Wert zugreift bekommt es schon richtig ohne im XAML an den converter zu denken.

      Verfechter der No-Code in Code behind ist zuzustimmen wenn sie sagen dass ein Konverter ehr dem Codebehind zuzuordnen ist statt dem ViewModel da dafür im XAML etwas eingefügt werden muss.

      Aber wie so oft es gibt kein richtig und kein falsch.
      Wünsche ein schönes Wochenende.

      Gruß Stefan

      1. Hi Stefan, sehe ich ähnlich. Für mich ist der Entscheidungsweg immer, wie oft eine Konvertierung wahrscheinlich ausgeführt wird. In Listen versuche ich immer, gar keine zu verwenden.

  3. >>> … wie halt das öffnen von anderen Views
    Bereits als ich begann, diese Artikelserie zu lesen, hatte ich durch die Auflistung (Teil 4 zu Converter und Messenger) gehofft, dass das Thema besprochen werden würde.

    Danke dafür! Jetzt habe ich es auch verstanden.

    Sollte ich mir ein Thema wünschend dürften: „Füllen einer View mit Daten aus einer Datenhaltung“. Somit könnte man die immer wieder zitierten Business- und Data-Access-Layer ebenfalls noch mit diesem hervorragenden Erklärstil abdecken.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.