Enum driving a Visual State change via the ViewModel

Exciting title eh?

So, here’s the problem, I want to use my ViewModel to drive my Visual State, I’ve used the ‘DataStateBehavior’ before, but the trouble with it is that it only works for bool values, and the minute you jump to more than 2 Visual States, you’re kind of screwed.

A quick search has shown up a couple of points of interest, first, the DataStateSwitchBehavior, which is part of the Expression Samples (on Codeplex), and also available via Pete Blois’ blog. The second interest is to use a DataTrigger with GoToStateAction (from the Silverlight forums).

So, onwards… first let’s create a basic switch Visual State, so, a DataObj with one property: IsAce…

public class DataObj : NotifyPropertyChanger
{
    private bool _isAce;

    public bool IsAce
    {
        get { return _isAce; }
        set
        {
            _isAce = value;
            RaisePropertyChanged("IsAce");
        }
    }
}

The ‘NotifyPropertyChanger’ is literally a base class with RaisePropertyChanged, implementing INotifyPropertyChanged.

OK, so we then create a ViewModel:

public class MainPageViewModel : NotifyPropertyChanger
{
    private DataObj _dataObj;

    public MainPageViewModel()
    {
        DataObj = new DataObj {IsAce = true};
        ChangeAcenessCommand = new RelayCommand(() => DataObj.IsAce = !DataObj.IsAce);
    }

    public ICommand ChangeAcenessCommand { get; private set; }

    public DataObj DataObj
    {
        get { return _dataObj; }
        set
        {
            _dataObj = value;
            RaisePropertyChanged("DataObj");
        }
    }
}

Aaaand finally – hook it all up to the XAML, which is a very simple UI:

image

A Rectangle, a TextBlock and a Button. The Button is hooked up to ChangeAcenessCommand, the TextBlock is bound to the ‘DataObj.IsAce’ property and the Rectangle has 2 visual states: IsAce and NotAce.

To make the Rectangle change it’s visual state I’ve used a DataStateBehavior inside the Layout Root Grid:

<i:Interaction.Behaviors>
    <ei:DataStateBehavior Binding="{Binding DataObj.IsAce}" Value="true" TrueState="IsAce" FalseState="NotAce"/>
</i:Interaction.Behaviors>

So now we have the button changing the ‘IsAce’ property and giving us the other visual state:

image Great! So – the next stage is to get that to work inside a DataTemplate… Which (thankfully) is easy money. All we do is add a ListBox to the View and an ObservableCollection to the ViewModel.

Well – ok, a little bit more than that. Once we’ve got the ListBox with it’s ItemsSource property set, it’s time to add the DataTemplate itself. Again, this isn’t exactly taxing, and is purely going to be a Grid with a Textblock and a Rectangle (again, I’m nothing if not consistent). Though, to be a little jazzy I’ve swapped the rectangle to the other side (living the dream).

So, all that’s left is to add some States to the template.. (Yes – you can do that), these can be the same names as the others, or indeed, something else, I have chosen to stick with the same names and take the extra confusion hit right on the nose.

Once again, I add the DataStateBehavior to the root Grid element:

<i:Interaction.Behaviors>
    <ei:DataStateBehavior Binding="{Binding IsAce}" Value="true" TrueState="IsAce" FalseState="NotAce"/>
</i:Interaction.Behaviors>

The key difference here is the ‘Binding’ attribute, where I’m now binding to the IsAce property directly, and boom! It’s all gravy!

image

 

So far, so good. We can use boolean values to change the visual states, and (crucially) it works in a DataTemplate, bingo!

Now. Onwards to the Enum part of this (finally!). Obviously we can’t use the DataStateBehavior, it' only gives us true/false options. So, let’s give the GoToStateAction a go.

Now, I warn you, things get a bit complex from here, instead of a bool with 2 values, I’m gonna max it out and bring in an Enum with 3 (count ‘em) 3 values: Red, Amber and Green (those of you with exceptionally sharp minds will be reminded of traffic lights).

We’re gonna have a rectangle which also has 3 visual states – cunningly called ‘Red’, ‘Amber’ and ‘Green’. A new class called DataObj2:

public class DataObj2 : NotifyPropertyChanger
{
    private Status _statusValue;

    public DataObj2(Status status)
    {
        StatusValue = status;
    }

    public Status StatusValue
    {
        get { return _statusValue; }
        set
        {
            _statusValue = value;
            RaisePropertyChanged("StatusValue");
        }
    }
}

Where ‘Status’ is my enum. Good times are here! Ok, so let’s get to the beefy stuff. So, we’ll start off in the same manner as the last time, we will have a single DataObj2 instance available to the Page and bind to that. Let’s add some Triggers (these are in the LayoutRoot again).
<i:Interaction.Triggers>
    <ei:DataTrigger Binding="{Binding DataObject2.StatusValue}"
                    Value="Amber">
        <ei:GoToStateAction StateName="Amber"
                            UseTransitions="False" />
    </ei:DataTrigger>
    <ei:DataTrigger Binding="{Binding DataObject2.StatusValue}"
                    Value="Green">
        <ei:GoToStateAction StateName="Green"
                            UseTransitions="False" />
    </ei:DataTrigger>
    <ei:DataTrigger Binding="{Binding DataObject2.StatusValue}"
                    Value="Red">
        <ei:GoToStateAction StateName="Red"
                            UseTransitions="False" />
    </ei:DataTrigger>
</i:Interaction.Triggers>

So what we’re saying here is that when the DataObject2.StatusValue is equal to ‘Red’ then we’ll go to the ‘Red’ state. Same deal for Green and Amber (but you knew that already). Hook it all up and start teh project.

image

Hmm. Just grey. Not what I wanted.

Ok, let’s add a ‘ChangeStatusCommand’, hook that up to a button and give it a whirl:

image Right, so the DataTrigger isn’t picking up the data on load. On the plus side, changing the status is making the visual states change.

So. We’ll cross the ‘Grey’ hurdle in a bit, what about doing the same in the DataTemplate?

<Codey Codey/>

image

Grey again, but if we press the button:

image

(I should mention, pressing the button sets the StatusValue property on the DataObj2 being represented to the next colour).

Right. Let’s look at this ‘Grey’ issue.

First ‘fix’ (and I use the term ‘fix’ in a very loose way):

The Dispatcher Fix

This involves using the Dispatcher on the View to call something like ‘RefreshProperties’ on the ViewModel, which will in turn raise all the appropriate ‘PropertyChanged’ events on the data objects being represented. So, here goes, into turdcode-ville – population – me:

First, add the ‘RefreshProperties’ method to the DataObj2:

internal void RefreshProperties()
{
    RaisePropertyChanged("StatusValue");
}

(shudder)

Now, add it to the hosting ViewModel:

public void RefreshProperties()
{
    DataObject2.RefreshProperties();
    if (DataObjects != null && DataObjects.Count > 0)
    {
        foreach (DataObj2 dataObject in DataObjects)
            dataObject.RefreshProperties();
    }
}

(double shudder)

and now for the cream on the cake, adding the following line to the code behind of the View:

Dispatcher.BeginInvoke(() => ((MoreVisualStatesViewModel)DataContext).RefreshProperties());

So, what does this *ahem* code give us:

image

Awesome, it makes the single bound data object show the colour, but frankly ignores the DataTemplate items.

This (by the way) is the same output you get from:

Dispatcher.BeginInvoke(() => ((MoreVisualStatesViewModel)DataContext).ChangeStatusCommand.Execute(null));

So… Where does that leave me?

What about adding a button to the Page to refresh the properties – maybe it’s a timer thing?

image Yes, that works.

Right, what about using the Loaded event then eh?

Loaded += (s, e) => ((MoreVisualStatesViewModel) DataContext).RefreshProperties();

Ahhh No.

What about converting the DataTemplate into a UserControl? Anything is worth a shot.. Though – I still suspect I’m going to have to ‘RefreshProperties’ if I want the rectangles to update.

Still. No. This DataTemplate DataTrigger binding is becoming a bit of a pain… I can’t add a ‘refresh’ button to the actual code base, it’s not exactly user friendly.

I’m going to end this one now, and put some investigating into the use of the DataStateSwitchBehavior (all the ones I’ve found, well, all 2 of them are working in SL3, but not 4…)

Print | posted @ Wednesday, December 8, 2010 4:50 AM

Comments on this entry:

No comments posted yet.

Post A Comment
Title:
Name:
Email:
Comment:
Verification: