Using Visual States to keep view models simple… and not a converter to be seen

I recently posted about commands in Photon and how it helps you create simple view models.  Another reason for view models getting more complicated is view logic creeping in…  View logic in the ViewModel…  It’s a view model, surely it has view logic in it?  We’ve found that view models become incredibly brittle when they contain user experience details.  It’s already considered bad form to pass in view specific objects into a view model, it’s just taking this idea a little further.  A potential reason for brittle view models comes from violating the Single Responsibility Principle.  Should changing the way the view displays data affect the view model?  No, and most the time it doesn’t, you’re free to change from a list box to a data grid without changing the view model.  So what about showing or hiding something, is that a view model decision?  Should the colour of some text be up to your view model?  No.  But all too often, there’s a TextColour property, or a IsAddressVisible property…  What should happen here is the view uses the information exposed by the view model to make such decisions.  This separation of concerns can lead to a less brittle and more maintainable solution.  It could even lead to that utopia (we’ve not got to yet), that the UX team build the views…

Visual states are a massive help (Silverlight has no StyleTriggers or Data Template Selectors like WPF) and they are made simple with Blend.  You can hook them up with a GotoState action which can be driven off triggers.  So, let’s look at our simple example from the last blog.  Let’s first hide and show the list box:

First job define the visual states…

image

What I did was make the list box Collapsed normally (because normally the check box won’t be checked) and recorded a new visual state where it’s checked.

Now we set some goto state actions, one to go to visible and one to not.  We can use DataTriggers, one set up to go to the visible state and another to go to the not visible state…

image

<Grid x:Name="LayoutRoot" Background="White">
<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="ContactMethodState">
        <VisualState x:Name="ContactMethodsRequired">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="listBox">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <Visibility>Visible</Visibility>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
        <VisualState x:Name="ContactMethodsNotRequired"/>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition/>
</Grid.RowDefinitions>
<CheckBox Content="Can we contact the customer?" IsChecked="{Binding IsContactable, Mode=TwoWay}" d:LayoutOverrides="Height">
    <i:Interaction.Triggers>
        <ei:DataTrigger Binding="{Binding IsContactable}" Value="True">
            <ei:GoToStateAction x:Name="GotoRequired" StateName="ContactMethodsRequired"/>
        </ei:DataTrigger>
        <ei:DataTrigger Binding="{Binding IsContactable}" Value="False">
            <ei:GoToStateAction x:Name="GotoNotRequired" StateName="ContactMethodsNotRequired"/>
        </ei:DataTrigger>
    </i:Interaction.Triggers>
</CheckBox>

Done…

So what when UX come along and say “we’d like users to have to confirm removing a contact method”?

We can change the remove button to go to a visual state which shows the confirmation question.  Then make the “Yes” button invoke the remove method and “No” go back to the normal state.  We can still do all this without changing the view model…

image

There is one problem with this, the CanRemove is bound to the Yes button, so you end up with this…

image

Photon has a mechanism for solving this called Feedback Presenters…  I hope to blog about these soon.

Here’s the XAML:

<ListBox x:Name="listBox" Grid.Row="2" ItemsSource="{Binding ContactMethods}" photon:Invoke.Target="{Binding}" Visibility="Collapsed">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="RemoveStates">
                        <VisualState x:Name="Normal"/>
                        <VisualState x:Name="Confirmation">
                            <Storyboard>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="grid">
                                    <DiscreteObjectKeyFrame KeyTime="0">
                                        <DiscreteObjectKeyFrame.Value>
                                            <Visibility>Visible</Visibility>
                                        </DiscreteObjectKeyFrame.Value>
                                    </DiscreteObjectKeyFrame>
                                </ObjectAnimationUsingKeyFrames>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="button">
                                    <DiscreteObjectKeyFrame KeyTime="0">
                                        <DiscreteObjectKeyFrame.Value>
                                            <Visibility>Collapsed</Visibility>
                                        </DiscreteObjectKeyFrame.Value>
                                    </DiscreteObjectKeyFrame>
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
                <VisualStateManager.CustomVisualStateManager>
                    <ei:ExtendedVisualStateManager/>
                </VisualStateManager.CustomVisualStateManager>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150" />
                    <ColumnDefinition Width="*"    />        
                </Grid.ColumnDefinitions>
                <TextBox Text="{Binding ContactMethod, Mode=TwoWay}" Grid.Column="0" Margin="0,0,10,0" />
                <Button x:Name="button" Content="Remove" Grid.Column="1" >
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <ei:GoToStateAction StateName="Confirmation"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Button>
                <Grid x:Name="grid" Grid.Column="1" Visibility="Collapsed" VerticalAlignment="Center">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                        <ColumnDefinition />            
                    </Grid.ColumnDefinitions>
                    <TextBlock TextWrapping="Wrap" Text="Are you sure?" d:LayoutOverrides="Width, Height"/>
                    <Button Content="Yes" Grid.Column="1" d:LayoutOverrides="Height" Margin="10,0">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Click">
                                <ei:GoToStateAction StateName="Normal"/>
                                <photon:InvokeMethod MethodName="RemoveContactMethod">
                                    <photon:InvokeMethod.Arguments>
                                        <photon:Argument Value="{Binding}"/>
                                    </photon:InvokeMethod.Arguments>
                                </photon:InvokeMethod>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </Button>
                    <Button Content="No" Grid.Column="2" d:LayoutOverrides="Height">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Click">
                                <ei:GoToStateAction StateName="Normal"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </Button>
                </Grid>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
    
</ListBox>
Advertisements

About Tom Peplow

C# .Net developer based in London and the South Coast
This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s