C# – Preserve MVVM While Using XAML Resources

cdesign-patternsmvvmwpf

Context:

I'm creating a WPF application using MVVM. I have a Page which displays a status informing what task the app is performing on Background.

I have a container, and bind its Content to an property on the ViewModel.

For an illustration, take a look at the following code:

<StackPanel x:Key="Status_Success" Orientation="Horizontal">
    <iconPacks:PackIconMaterial Kind="Check" />
    <TextBlock>Success!</TextBlock>
</StackPanel>

<StackPanel x:Key="Status_Error" Orientation="Horizontal">
    <iconPacks:PackIconMaterial Kind="Exclamation" />
    <TextBlock>Error!</TextBlock>
</StackPanel>

If the background task succeeds, then I'd set the content property to Status_Success StackPanel. Otherwise, I'd set the content to Status_Error.

Here's the binding:

<Controls:TransitioningContentControl [...] Content="{Binding CurrentStatusElement}">

Problem:

Well, I firstly created all the StackPanels as Resources in my Page. But as I said, I'm using MVVM, so I don't have direct access to page resources from the ViewModel.

Approaches:

Here's some possible approaches (these are not the only possibilities, I'm taking sugestions):

1. Create the StackPanels on the ViewModel:

StackPanel _StatusSuccessElement = new StackPanel();

_StatusSuccessElement.Children.Add([...];

[...]

2. Create a new Resource Dictionary and import it in the ViewModel:

var resourceDictionary = new ResourceDictionary()
{
    Source = new Uri("SymbolTemplates.xaml", UriKind.Relative)
};

StackPanel _StatusSuccessElement = resourceDictionary["Status_Success"] as StackPanel;

3. Create an element (Page/UserControl/whatever) and create a new instance of it on the View Model

var _StatusSuccessElement = new StatusSuccessElement();

Question:

  1. Which, if any, of these approaches fit better with MVVM and why?

  2. If none, what's the best approach to prevent pattern violations?

Best Answer

You don't want any UI elements in the ViewModel. The ViewModel should know nothing about StackPanels or Resources. The whole point of MVVM is to separate the UI controls from the underlying state management.

This is why you are running into the problem in the first place.

Have the ViewModel expose a state and the view react to that state.

In this case, I would create an enumeration for your state:

public enum State
{
    Success,
    Error
}

Expose the current state as a property of the ViewModel. The View can then react to that state and display the icon and text you want (or something else entirely!). In this case, I would use a DataTrigger. You could also use a Converter.

Example using a DataTrigger:

<StackPanel>
    <iconPacks:PackIconMaterial>
        <iconPacks:PackIconMaterial.Style>
            <Style TargetType="{x:Type iconPacks:PackIconMaterial}">
                <Setter Property="Kind" Value="Check" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=CurrentStatus}" Value="{x:Static local:State.Error}">
                        <Setter Property="Kind" Value="Exclamation" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </iconPacks:PackIconMaterial.Style>
    </iconPacks:PackIconMaterial>
    <TextBlock>
        <TextBlock.Style>
            <Style TargetType="TextBlock">
                <Setter Property="Text" Value="Success!" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=CurrentStatus}" Value="{x:Static local:State.Error}">
                        <Setter Property="Text" Value="Error!" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</StackPanel>
Related Topic