1

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?

  • 1
    I don't see where the binding is done. Also, design patterns are not supposed to be The One True Way of programming. They're supposed to be a simple way of expressing common coding patterns between developers. – CHendrix Jan 20 '17 at 12:50
  • @CHendrix I added the container (and it's binding) to the question. It is done like this: <Controls:TransitioningContentControl [...] Content="{Binding CurrentStatusElement}">, so at the ViewModel I change CurrentStatusElement (it is an UIElement), and it refreshes at the UI. Although design patters are not a The One True Way, there are certainly some best approaches, which does not violate some main principles, and this is what I intended to ask with this question. Do you think I should edit the question and be more specific? – appa yip yip Jan 20 '17 at 13:27
1

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>
  • Well, as the status is not always a TextBlock and a PackIconMaterial, I created them as Resources and create a Style for my container setting it's content based on the CurrentDistributorStatus. Thanks – appa yip yip Jan 20 '17 at 15:34
2

Use Implicit DataTemplates.

Create a different ViewModel class for each state you want to map and set the correct type as CurrentStatusElement. Setup your page resources as

<DataTemplate DataType={x:type viewModels:CheckVM}>
    <StackPanel Orientation="Horizontal">
        <iconPacks:PackIconMaterial Kind="Check" />
        <TextBlock>Success!</TextBlock>
    </StackPanel>
</Datetemplate

<DataTemplate DataType={x:type viewModels:ExclamationVM}>
    <StackPanel Orientation="Horizontal">
        <iconPacks:PackIconMaterial Kind="Exclamation" />
        <TextBlock>Error!</TextBlock>
    </StackPanel>
</Datetemplate

Every time CurrentStatusElement changes the correct template will be used based on the type of the bound property.

This is preferred to the other solutions because you can unit test the VM without instantiating ui elements, you can have different views all sharing the same view model and you can change the views without changing the tests or the viewmodels.

  • In this case, the DataType class would it be empty? If so, the class must necessarily be a View Model or can it be just a common class? – appa yip yip Jan 20 '17 at 13:59
  • Yes it can be an empty class and a POCO object as far as the view is concerned. – Dtex Jan 20 '17 at 14:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.