1

How can I change between UserControls in the View, based on a property of the ViewModel, in a Windows 8 store app?

Say my ViewModel has a property which looks something like this:

    class MyViewModel
    {
        public string CurrentStatus
        {
            get { return (string)GetValue(CurrentStatusProperty); }
            set { SetValue(CurrentStatusProperty, value); }
        }

        public static readonly DependencyProperty CurrentStatusProperty =
            DependencyProperty.Register("CurrentStatus", typeof(string), typeof(MyViewModel), new PropertyMetadata("default", CurrentStatusChanged));


        ...
     }

How do I make my View change an UserControl according to the value of the CurrentStatus from the ViewModel?

The strightforward solution for me would have been to create a binding between the CurrentStatus from the ViewModel and another string from the View, but apparently data binding can only be used for a DependencyObject (which a string isn't).

Edit: The xaml file contains just a StackPanel in which I want to put a UserControl, based on the CurrentStatus. So, if the CurrentStatus is "one" I want the StackPanel to contain UserControlOne and so on...

Any ideas or nice solutions to this problem?

Thanks a lot!

alex_and_ra
  • 219
  • 1
  • 16

3 Answers3

2

I usually use a ContentControl and set it's ContentTemplate based on a DataTrigger

I have an example in my blog post Switching between Views/UserControls using MVVM, but here's a sample of what that may look like using your scenario:

<DataTemplate x:Key="DefaultTemplate">
     <local:DefaultUserControl />
</DataTemplate> 

<DataTemplate x:Key="ClosedTemplate">
     <local:ClosedUserControl />
 </DataTemplate>

<Style x:Key="MyContentControlStyle" TargetType="{x:Type ContentControl}">
    <Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding CurrentStatus}" Value="Closed">
            <Setter Property="ContentTemplate" Value="{StaticResource ClosedTemplate}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

...

<ContentControl Style="{StaticResource MyContentControlStyle}" />

EDIT

Apparently DataTriggers are not supported in WinRT, and have been replaced with the VisualStateManager. I haven't had the chance to use this yet, however from what I'm reading they used the same approach for WinRT as they did Silverlight (which also didn't support DataTriggers until v5), and my solution for Silverlight was to use a DataTemplateSelector

I hope that can point you in the right direction :)

Rachel
  • 130,264
  • 66
  • 304
  • 490
  • I'm sorry, your code does not work in a Windows 8 App project. Visual Studio complains that "Type is not supported in a Windows App project." for `x:Type` and if I fix that it has a problem with ``. – alex_and_ra Feb 28 '13 at 17:01
  • @alex_and_ra I learn something new every day :) According to [this answer](http://stackoverflow.com/a/7446841/302677), DataTriggers are replaced by the [VisualStateManager](http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.visualstatemanager.aspx) in WinRT – Rachel Feb 28 '13 at 17:05
  • I never used DataTriggers or the VisualStateManager before.. I'm kind of new at this :) I'm going to give it a try. Thanks! – alex_and_ra Feb 28 '13 at 17:19
  • @alex_and_ra Sorry I can't be of more help. I haven't worked with Windows 8 or WinRT before. I'd delete my answer, but I think it may be better to leave it to show why this common WPF solution won't work with WinRT. From what I can tell just from reading, triggers are usually done in WinRT with the `VisualStateManager`, the code behind, or a `Converter` like [SteveL suggests](http://stackoverflow.com/a/15139476/302677). Perhaps take another look at his answer to see if it will work. :) – Rachel Feb 28 '13 at 17:24
1

Not sure I fully understand what you are trying to do. Can you post the xaml?

If what you are wanting is to present the controls differently based on the status, then use a converter and you can present a different template based upon the status:

public class StatusToTemplateConverter : IValueConverter
{
    #region IValueConverter Members


    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        switch ((string) value)
        {
            case "Status1":
                return Application.Current.Resources["Status1Template"];
            case "Status2":
                return Application.Current.Resources["Status2Template"];

            default:
                return Application.Current.Resources["Status3Template"];
        }
    }


    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }

    #endregion
}

the above assumes you have your templates defined in a resource file

If all you ae wanting to do is something simpler such as red text for Status1 or Green text for status2 you could have a converter that just converts the status to a colour and bind the FontColor to the status and use the converter.

But like I said, without more code being posted, I'm not 100% clear on what you are trying to achieve

SteveL
  • 857
  • 10
  • 18
1

While I've used the DataTemplateSelector and IValueConverter in such cases before - now my favorite approach is using the VisualStateManager. I have created a most basic attached property that implements the attached behavior pattern in WinRT XAML Toolkit here - that looks like this:

/// <summary>
/// Defines an attached property that controls the visual state of the element based on the value.
/// </summary>
public static class VisualStateExtensions
{
    #region State
    /// <summary>
    /// State Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty StateProperty =
        DependencyProperty.RegisterAttached(
            "State",
            typeof(string),
            typeof(VisualStateExtensions),
            new PropertyMetadata(null, OnStateChanged));

    /// <summary>
    /// Gets the State property. This dependency property 
    /// indicates the VisualState that the associated control should be set to.
    /// </summary>
    public static string GetState(DependencyObject d)
    {
        return (string)d.GetValue(StateProperty);
    }

    /// <summary>
    /// Sets the State property. This dependency property 
    /// indicates the VisualState that the associated control should be set to.
    /// </summary>
    public static void SetState(DependencyObject d, string value)
    {
        d.SetValue(StateProperty, value);
    }

    /// <summary>
    /// Handles changes to the State property.
    /// </summary>
    /// <param name="d">
    /// The <see cref="DependencyObject"/> on which
    /// the property has changed value.
    /// </param>
    /// <param name="e">
    /// Event data that is issued by any event that
    /// tracks changes to the effective value of this property.
    /// </param>
    private static void OnStateChanged(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var stateName = (string)e.NewValue;
        var ctrl = (Control)d;
        VisualStateManager.GoToState(ctrl, stateName, true);
    }
    #endregion
}

You should define an enum-style class like the VisualStates class in Silverlight Toolkit that lists all your visual states (so you don't have duplicates), e.g.

internal static class VisualStates
{
    #region GroupCommon
    /// <summary>
    /// Common state group.
    /// </summary>
    public const string GroupCommon = "CommonStates";

    /// <summary>
    /// Normal state of the Common state group.
    /// </summary>
    public const string StateNormal = "Normal";

    /// <summary>
    /// Normal state of the Common state group.
    /// </summary>
    public const string StateReadOnly = "ReadOnly";

    /// <summary>
    /// MouseOver state of the Common state group.
    /// </summary>
    public const string StateMouseOver = "MouseOver";

    /// <summary>
    /// Pressed state of the Common state group.
    /// </summary>
    public const string StatePressed = "Pressed";

    /// <summary>
    /// Disabled state of the Common state group.
    /// </summary>
    public const string StateDisabled = "Disabled";
    #endregion GroupCommon

    #region GroupFocus
    /// <summary>
    /// Focus state group.
    /// </summary>
    public const string GroupFocus = "FocusStates";

    /// <summary>
    /// Unfocused state of the Focus state group.
    /// </summary>
    public const string StateUnfocused = "Unfocused";

    /// <summary>
    /// Focused state of the Focus state group.
    /// </summary>
    public const string StateFocused = "Focused";
    #endregion GroupFocus
}

Once you have these - you can have a property in your view model like

public string VisualState { get; set; /* Raise PropertyChange event your preferred way here */ }

And in your view say

<Page
    ...
    xmlns:extensions="using:WinRTXamlToolkit.Controls.Extensions"
    extensions:VisualStateExtensions.State="{Binding VisualState}">...

Then your view model can easily drive the change of the visual state and you can use Blend to define what that state looks like - e.g. change the Content or ContentTemplate or simply visibility of various elements in the layout. I think this method has best support of the designer tools since you can flip between views with a click of a button and make updates to these views in Blend.

Filip Skakun
  • 31,624
  • 6
  • 74
  • 100
  • For this line `extensions:VisualStateExtensions.State="{Binding State}">` it says "The attachable property 'State' was not found in type 'VisualStateExtensions'". Also, who should be 'State' from the binding? – alex_and_ra Feb 28 '13 at 19:59
  • The `VisualStateExtensions` class in my answer has the State property. Just make sure you add `xmlns:extensions="using:WinRTXamlToolkit.Controls.Extensions"` to your root XML element in your XAML. Sorry, I missed the State property in the binding should actually be called `VisualState` and it comes from your view model property I quoted in the 3rd code block. Let me update it. – Filip Skakun Feb 28 '13 at 21:03