1

The question is: How to get/set the VisualState of a Control (with more than two Visual States) on the View through my ViewModel in MVVM pattern (with zero-view-code-behind)?

I've seen similar questions who's answers didn't work for me:

Note: below I'll be explaining what was wrong with the answers in the mentioned questions. If you know a better approach, you can dismiss reading the rest of this question.

As for the first question, the accepted answer's approach doesn't work for me. Once I type the mentioned XAML code

<Window .. xmlns:local="clr-namespace:mynamespace" ..>
    <TextBox Text="{Binding Path=Name, Mode=TwoWay}"
             local:StateHelper.State="{Binding Path=State, Mode=TwoWay}" />
</Window>

It shows a design-time error that says: The attachable property 'State' was not found in type 'StateHelper'., I tried to get over this by renaming StateHelper.StateProperty to StateHelper.State, ending up with two errors.. 1: The attachable property 'State' was not found in type 'StateHelper'. and 2: The local property "State" can only be applied to types that are derived from "StateHelper".

As for the second question, the accepted answer's approach doesn't work for me. After fixing VisualStateSettingBehavior's syntax errors to be:

public class VisualStateSettingBehavior : Behavior<Control>
{

    private string sts;
    public string StateToSet
    {
        get { return sts; }
        set
        {
            sts = value;
            LoadState();
        }
    }

    void LoadState()
    {
        VisualStateManager.GoToState(AssociatedObject, sts, false);
    }
}

I got a design-time error on the line

    <local:VisualStateSettingBehavior StateToSet="{Binding State}"/>

that says: A 'Binding' cannot be set on the 'StateToSet' property of type 'VisualStateSettingBehavior'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

I tried to merge the two solutions by making VisualStateSettingBehavior.StateToSet a dependency property, but I got other design-time errors in the View.

Any suggestions?

Community
  • 1
  • 1
Aly Elhaddad
  • 1,913
  • 1
  • 15
  • 31

1 Answers1

2

At last, I could solve this. The solution was similar to the first question's best answer. I found out that in my case there are some constraints on the View.xaml to use an attached property:

  1. It has to be registered via DependencyProperty.RegisterAttached.
  2. It has to be static.
  3. It must has a property instance (getter/setter).

I got through that with this coding-style in mind, and the final approach was:

VisualStateApplier:

public class VisualStateApplier
{

    public static string GetVisualState(DependencyObject target)
    {
        return target.GetValue(VisualStateProperty) as string;
    }
    public static void SetVisualState(DependencyObject target, string value)
    {
        target.SetValue(VisualStateProperty, value);
    }

    public static readonly DependencyProperty VisualStateProperty =
        DependencyProperty.RegisterAttached("VisualState", typeof(string), typeof(VisualStateApplier), new PropertyMetadata(VisualStatePropertyChangedCallback));

    private static void VisualStatePropertyChangedCallback(DependencyObject target, DependencyPropertyChangedEventArgs args)
    {
        VisualStateManager.GoToElementState((FrameworkElement)target, args.NewValue as string, true); // <- for UIElements, OR:
        //VisualStateManager.GoToState((FrameworkElement)target, args.NewValue as string, true); // <- for Controls
    }
}

View:

<!--A property inside the object that owns the states.-->
<local:VisualStateApplier.VisualState>
    <Binding Path="State"/>
</local:VisualStateApplier.VisualState>

ViewModel:

private string _state;
public string State
{
    get { return _state; }
    set
    {
        _state = value;
        RaisePropertyChanged("State");
    }
}
Community
  • 1
  • 1
Aly Elhaddad
  • 1,913
  • 1
  • 15
  • 31