• Blog
  • Info Support
  • Career
  • Training
  • International Group
  • Info Support
  • Blog
  • Career
  • Training
  • International Group
  • Search
logo InfoSupport
  • Latest blogs
  • Popular blogs
  • Experts
      • All
      • Bloggers
      • Speakers
  • Meet us
  • About us
    • nl
    • en
    • .NET
    • 3D printing
    • Advanced Analytics
    • Agile
    • Akka
    • Alexa
    • Algorithms
    • Api's
    • Architectuur
    • Artificial Intelligence
    • ATDD
    • Augmented Reality
    • AWS
    • Azure
    • Big Data
    • Blockchain
    • Business Intelligence
    • Chatbots
    • Cloud
    • Code Combat
    • Cognitive Services
    • Communicatie
    • Containers
    • Continuous Delivery
    • CQRS
    • Cyber Security
    • Dapr
    • Data
    • Data & Analystics
    • Data Science
    • Data Warehousing
    • Databricks
    • DataOps
    • Developers life
    • DevOps
    • Digital Days
    • Digital Twin
    • Docker
    • eHealth
    • Enterprise Architecture
    • Event Sourcing
    • Hacking
    • Infrastructure & Hosting
    • Innovatie
    • Integration
    • Internet of Things
    • Java
    • Machine Learning
    • Microservices
    • Microsoft
    • Microsoft Bot Framework
    • Microsoft Data Platform
    • Mobile Development
    • Mutation Testing
    • Open source
    • Pepper
    • Power BI
    • Privacy & Ethiek
    • Python
    • Quality Assistance & Test
    • Quality Assurance & Test
    • Requirements Management
    • Scala
    • Scratch
    • Security
    • SharePoint
    • Software Architecture
    • Software development
    • Software Factory
    • SQL Server
    • SSL
    • Start-up
    • Startup thinking
    • Stryker
    • Test Quality
    • Testing
    • TLS
    • TypeScript
    • Various
    • Web Development
    • Web-scale IT
    • Xamarin
    • All
    • Bloggers
    • Speakers
Home » Silverlight 4: using the VisualStateManager for state animations with MVVM
  • Silverlight 4: using the VisualStateManager for state animations with MVVM

    • By Alex van Beek
    • Various 12 years ago
    • Various 0 comments
    • Various Various
    Silverlight 4: using the VisualStateManager for state animations with MVVM

    A recurring problem with MVVM is how to incorporate animations in a nice MVVM way. The problem is that StoryBoards need to be started from the view, since they are usually configured in XAML. This means that the ViewModel needs a reference to the view, in order to tell it when the animation should start. Keeping a reference from the ViewModel to the View is obviously “not done” in the MVVM pattern. In this post I’m going to show how you can use the VisualStateManager to start animations and incorporate it in the MVVM pattern, without keeping a reference from the ViewModel to the View. 

    The VisualStateManager

    When using animations in business applications, it’s usually to tell your user that the application is going from one state to another. For example, going from the “NotLoggedIn” state to the “LoggedIn” state. States in Silverlight are handled by the VisualStateManager. The VisualStateManager is probably well known under Silverlight developers; it’s used to go from state to state in controls. What a lot of developers don’t know, is that the VisualStateManager can also be used in other situations than custom control development or templating controls. For example, you can use the VisualStateManager to manually add states to a UserControl, let Blend record the user interface in the different states and use the “VisualStateManager.GoToState()” method to let your UserControl switch states. This way, you can declaratively tell in XAML, how your UserControl should look in each state. Blend handles the animations and the XAML part, all you have to do is tell the VisualStateManager when to switch states. I’m going to demonstrate this with a very simple application:

    image

    The idea is that when the user moves the mouse over the image, it becomes slightly enlarged, using an animation in the process. Obviously, MVVM is overkill for this very simple application, but it demonstrates the point quite well. We can define two states in this application, namely “Enlarged” and “Normal”.

    In order to make this work in a MVVM way, we have to do a couple of things:

    • Create a ViewModel, named “MainPageViewModel”.
    • Use a command for the “MouseEnter” event of the image, to switch to the “Enlarged” state.
    • Use a command for the “MouseLeave” event of the image, to switch to the “Normal” state.
    • Bind a “CurrentState” property of the main UserControl, to a “CurrentState” property of the ViewModel. Every time the “CurrentState” property of the ViewModel changes, the View should switch states. The two commands for the “MouseLeave” and “MouseEnter” events should modify the “CurrentState” property of the ViewModel and the View should update itself through data binding. This way, the ViewModel doesn’t need to have a reference to the View.

    When implementing the scenario outlined above you’ll encounter a couple of problems:

    • The Image component doesn’t have support for any ICommand objects.
    • A UserControl class doesn’t have a “CurrentState” property which you can bind to the ViewModel.
    • There isn’t a generic ICommand implementation in Silverlight.

    To tackle these problems I’ve created a separate reusable Silverlight class library, named “MVVMSupport”. 

    The MVVMSupport Library

    This library contains three classes, one to fix each problem outlined above. First up is the DelegateCommand<T> class:

       1: using System;

       2: using System.Net;

       3: using System.Windows;

       4: using System.Windows.Controls;

       5: using System.Windows.Documents;

       6: using System.Windows.Ink;

       7: using System.Windows.Input;

       8: using System.Windows.Media;

       9: using System.Windows.Media.Animation;

      10: using System.Windows.Shapes;

      11:  

      12: namespace MVVMSupport

      13: {

      14:     public class DelegateCommand<T> : ICommand

      15:     {

      16:         public event EventHandler CanExecuteChanged;

      17:         private Action<T> _toExecute;

      18:         private Func<T, bool> _condition;

      19:  

      20:  

      21:         public DelegateCommand(Action<T> toExecute) : this(toExecute, (param)=>true)

      22:         {

      23:  

      24:         }

      25:  

      26:         public DelegateCommand(Action<T> toExecute, Func<T, bool> condition)

      27:         {

      28:             if (toExecute == null)

      29:             {

      30:                 throw new ArgumentException("There must be an Action to execute");

      31:             }

      32:             if (condition == null)

      33:             {

      34:                 throw new ArgumentException("Condition must have a value, or use the other constructor instead");

      35:             }

      36:             _toExecute = toExecute;

      37:             _condition = condition;

      38:         }

      39:  

      40:  

      41:         protected virtual void OnCanExecuteChanged(EventArgs args)

      42:         {

      43:             EventHandler temp = CanExecuteChanged;

      44:             if (temp != null)

      45:             {

      46:                 CanExecuteChanged(this, args);

      47:             }

      48:         }

      49:  

      50:         public void FireCanExecuteChanged(EventArgs args)

      51:         {

      52:             OnCanExecuteChanged(args);

      53:         }

      54:  

      55:         public bool CanExecute(object parameter)

      56:         {

      57:             return _condition((T)parameter);

      58:         }

      59:  

      60:  

      61:  

      62:         public void Execute(object parameter)

      63:         {

      64:             _toExecute((T)parameter);

      65:         }

      66:     }

      67: }

    This is a slightly modified implementation of the DelegateCommand class wandering around on the Internet. It’s a generic ICommand implementation with two constructors, one which supports a condition that determines whether the command can be executed at the current time (to let a Button disable itself for example), the other supplies a condition that always returns true, so the command can always be executed. Because the implementation of the action and the condition to execute is supplied by Action and Func delegates coming from outside this class, the outside (usually the ViewModel) must also determine when the command can be executed. This means that the ViewModel is responsible for firing the “CanExecuteChanged” event. This is where the “FireCanExecuteChanged” method comes in. I don’t use these features in the sample application above, but a generic ICommand implementation should support them. This class tackled the “No generic command implementation in Silverlight” problem outlined above.

    Next up is the static “Commands” class:

       1: using System;

       2: using System.Net;

       3: using System.Windows;

       4: using System.Windows.Controls;

       5: using System.Windows.Documents;

       6: using System.Windows.Ink;

       7: using System.Windows.Input;

       8: using System.Windows.Media;

       9: using System.Windows.Media.Animation;

      10: using System.Windows.Shapes;

      11:  

      12: namespace MVVMSupport

      13: {

      14:     public static class Commands

      15:     {

      16:  

      17:         public static readonly DependencyProperty MouseEnterCommandProperty =

      18:          DependencyProperty.RegisterAttached("MouseEnterCommand", typeof(ICommand), typeof(Commands),

      19:          new PropertyMetadata(new PropertyChangedCallback(AttachOrRemoveMouseEnterEvent)));

      20:  

      21:         public static readonly DependencyProperty MouseLeaveCommandProperty =

      22:             DependencyProperty.RegisterAttached("MouseLeaveCommand", typeof(ICommand), typeof(Commands),

      23:             new PropertyMetadata(new PropertyChangedCallback(AttachOrRemoveMouseLeaveEvent)));

      24:  

      25:  

      26:  

      27:         public static ICommand GetMouseEnterCommand(DependencyObject obj)

      28:         {

      29:             return (ICommand)obj.GetValue(MouseEnterCommandProperty);

      30:         }

      31:  

      32:         public static void SetMouseEnterCommand(DependencyObject obj, ICommand value)

      33:         {

      34:             obj.SetValue(MouseEnterCommandProperty, value);

      35:         }

      36:  

      37:         public static ICommand GetMouseLeaveCommand(DependencyObject obj)

      38:         {

      39:             return (ICommand)obj.GetValue(MouseLeaveCommandProperty);

      40:         }

      41:  

      42:         public static void SetMouseLeaveCommand(DependencyObject obj, ICommand value)

      43:         {

      44:             obj.SetValue(MouseLeaveCommandProperty, value);

      45:         }

      46:  

      47:         private static void AttachOrRemoveMouseEnterEvent(DependencyObject obj, DependencyPropertyChangedEventArgs args)

      48:         {

      49:  

      50:             FrameworkElement element = obj as FrameworkElement;

      51:             if(element != null)

      52:             {

      53:                 ICommand command = (ICommand)args.NewValue;

      54:  

      55:                 if (args.OldValue == null && args.NewValue != null)

      56:                 {

      57:                     element.MouseEnter += ExecuteMouseEnterCommand;

      58:                 }

      59:                 else if(args.NewValue == null && args.OldValue != null)

      60:                 {

      61:                     element.MouseEnter -= ExecuteMouseEnterCommand;

      62:                 }

      63:             }

      64:             else

      65:             {

      66:                 throw new ArgumentException("MouseEnterCommand is only supported on FrameworkElement");

      67:             }

      68:         }

      69:  

      70:         private static void AttachOrRemoveMouseLeaveEvent(DependencyObject obj, DependencyPropertyChangedEventArgs args)

      71:         {

      72:             FrameworkElement element = obj as FrameworkElement;

      73:             if (element != null)

      74:             {

      75:                 ICommand command = (ICommand)args.NewValue;

      76:                 if (args.OldValue == null && args.NewValue != null)

      77:                 {

      78:                     element.MouseLeave += ExecuteMouseLeaveCommand;

      79:                 }

      80:                 else if (args.NewValue == null && args.OldValue != null)

      81:                 {

      82:                     element.MouseLeave -= ExecuteMouseLeaveCommand;

      83:                 }

      84:             }

      85:             else

      86:             {

      87:                 throw new ArgumentException("MouseLeaveCommand is only supported on FrameworkElement");

      88:             }

      89:           

      90:         }

      91:  

      92:         private static void ExecuteMouseEnterCommand(object sender, MouseEventArgs args)

      93:         {

      94:             DependencyObject dSender = (DependencyObject)sender;

      95:             ICommand toExecute = (ICommand)dSender.GetValue(MouseEnterCommandProperty);

      96:             if (toExecute.CanExecute(args))

      97:             {

      98:                 toExecute.Execute(args);

      99:             }

     100:         }

     101:  

     102:         private static void ExecuteMouseLeaveCommand(object sender, MouseEventArgs args)

     103:         {

     104:             DependencyObject dSender = (DependencyObject)sender;

     105:             ICommand toExecute = (ICommand)dSender.GetValue(MouseLeaveCommandProperty);

     106:             if (toExecute.CanExecute(args))

     107:             {

     108:                 toExecute.Execute(args);

     109:             }

     110:         }

     111:         

     112:     }

     113: }

    I’m not covering every bit of code in this class. The idea is that the lack of ICommand support of most controls in Silverlight, can easily be solved by using attached properties. I define two attached properties in the class above, a “MouseEnterCommand” and a “MouseLeaveCommand”. When set, this class registers event listeners for the “MouseEnter” and “MouseLeave” events, when these events occur, the correct command is executed, supplying the MouseEvent for extra information to the command. For every command you miss in Silverlight, you can use an attached property to provide support for it. This class obviously tackles the “No command support” problem outlined above. 

    And finally, the static “VisualStates” class:

       1: using System;

       2: using System.Net;

       3: using System.Windows;

       4: using System.Windows.Controls;

       5: using System.Windows.Documents;

       6: using System.Windows.Ink;

       7: using System.Windows.Input;

       8: using System.Windows.Media;

       9: using System.Windows.Media.Animation;

      10: using System.Windows.Shapes;

      11:  

      12: namespace MVVMSupport

      13: {

      14:     public static class VisualStates

      15:     {

      16:  

      17:        public static readonly DependencyProperty CurrentStateProperty =

      18:            DependencyProperty.RegisterAttached("CurrentState", typeof(String), typeof(VisualStates), new PropertyMetadata(TransitionToState));

      19:  

      20:         public static string GetCurrentState(DependencyObject obj)

      21:         {

      22:             return (string)obj.GetValue(CurrentStateProperty);

      23:         }

      24:  

      25:         public static void SetCurrentState(DependencyObject obj, string value)

      26:         {

      27:             obj.SetValue(CurrentStateProperty, value);

      28:         }

      29:  

      30:         private static void TransitionToState(object sender, DependencyPropertyChangedEventArgs args)

      31:         {

      32:  

      33:             Control c = sender as Control;

      34:             if(c != null)

      35:             {

      36:                 VisualStateManager.GoToState(c, (string)args.NewValue, true);

      37:             }

      38:             else

      39:             {

      40:                 throw new ArgumentException("CurrentState is only supported on the Control type");

      41:             }

      42:         }

      43:     }

      44: }

    This class provides an attached property for every Control. when this property is set, the control transitions to the new state. Because transitioning to states can now be done by setting properties instead of calling the “VisualStateManager.GoToState()” method, you can easily bind this property to a property in the ViewModel.

    The ViewModel

    Let’s take look at the ViewModel for the sample application:

       1: using System;

       2: using System.Net;

       3: using System.Windows;

       4: using System.Windows.Controls;

       5: using System.Windows.Documents;

       6: using System.Windows.Ink;

       7: using System.Windows.Input;

       8: using System.Windows.Media;

       9: using System.Windows.Media.Animation;

      10: using System.Windows.Shapes;

      11: using MVVMSupport;

      12: using System.ComponentModel;

      13:  

      14: namespace ImageApp

      15: {

      16:     public class MainPageViewModel : INotifyPropertyChanged

      17:     {

      18:         private string _currentState;

      19:  

      20:         public event PropertyChangedEventHandler PropertyChanged;

      21:         public ICommand ToLargeState { get; private set; }

      22:         public ICommand ToNormalState { get; private set; }

      23:         public string CurrentState

      24:         {

      25:             get

      26:             {

      27:                 return _currentState;

      28:             }

      29:             set

      30:             {

      31:                 if (value != _currentState)

      32:                 {

      33:                     _currentState = value;

      34:                     OnPropertyChanged(new PropertyChangedEventArgs("CurrentState"));

      35:                 }

      36:             }

      37:  

      38:         }

      39:  

      40:         public MainPageViewModel()

      41:         {

      42:             ToLargeState = new DelegateCommand<MouseEventArgs>((args) => CurrentState = "Enlarged");

      43:             ToNormalState = new DelegateCommand<MouseEventArgs>((args) => CurrentState = "Normal");

      44:         }

      45:  

      46:         protected virtual void OnPropertyChanged(PropertyChangedEventArgs propertyChangedEventArgs)

      47:         {

      48:             PropertyChangedEventHandler temp = PropertyChanged;

      49:             if (temp != null)

      50:             {

      51:                 temp(this, propertyChangedEventArgs);

      52:             }

      53:         }

      54:        

      55:     }

      56: }

    The ViewModel is actually quite small. This is because all it has to do is, set the current state to the correct state when the “ToLargeState” command or the “ToNormalState” command is executed. The ViewModel uses the DelegateCommand<T> class covered earlier. The commands are bound to the “MouseEnterCommand” and the “MouseLeaveCommand” attached properties (also covered earlier) in the view, that’s why the lambda’s accept a “MouseEventArgs”. When the “CurrentState” property in the ViewModel changes, the View is automatically notified because of the INotifyPropertyChanged implementation.

     

    The View

    The whole View is implemented in XAML:

       1: <UserControl

       2:     >="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       3:     >="http://schemas.microsoft.com/winfx/2006/xaml"

       4:     >="http://schemas.microsoft.com/expression/blend/2008"

       5:     >="http://schemas.openxmlformats.org/markup-compatibility/2006"

       6:     >="clr-namespace:ImageApp"

       7:     >="clr-namespace:MVVMSupport;assembly=MVVMSupport"

       8:     >="http://schemas.microsoft.com/expression/2010/interactions" >="http://schemas.microsoft.com/expression/2010/effects" x:Class="ImageApp.MainPage"

       9:     mc:Ignorable="d"

      10:     d:DesignHeight="300" d:DesignWidth="400"

      11:     DataContext="{StaticResource mainPageViewModel}"

      12:     mvvm:VisualStates.CurrentState="{Binding CurrentState}"

      13:     >

      14:  

      15:     <Grid x:Name="LayoutRoot" Background="White">

      16:         <VisualStateManager.VisualStateGroups>

      17:             <VisualStateGroup x:Name="States">

      18:                 <VisualStateGroup.Transitions>

      19:                     <VisualTransition GeneratedDuration="0:0:0.3">

      20:                         <ei:ExtendedVisualStateManager.TransitionEffect>

      21:                             <ee:CircleRevealTransitionEffect/>

      22:                         </ei:ExtendedVisualStateManager.TransitionEffect>

      23:                     </VisualTransition>

      24:                 </VisualStateGroup.Transitions>

      25:                 <VisualState x:Name="Enlarged">

      26:                     <Storyboard>

      27:                         <DoubleAnimation Duration="0" To="1.25" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="image" d:IsOptimized="True"/>

      28:                         <DoubleAnimation Duration="0" To="1.25" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="image" d:IsOptimized="True"/>

      29:                     </Storyboard>

      30:                 </VisualState>

      31:                 <VisualState x:Name="Normal"/>

      32:             </VisualStateGroup>

      33:         </VisualStateManager.VisualStateGroups>

      34:           <VisualStateManager.CustomVisualStateManager>

      35:             <ei:ExtendedVisualStateManager/>

      36:         </VisualStateManager.CustomVisualStateManager>

      37:         

      38:         

      39:         <Image x:Name="image" Source="parrot.jpg" Height="300" Width="300"

      40:                 mvvm:Commands.MouseEnterCommand="{Binding ToLargeState}"

      41:                 mvvm:Commands.MouseLeaveCommand="{Binding ToNormalState}" RenderTransformOrigin="0.5,0.5">

      42:             <Image.RenderTransform>

      43:                 <CompositeTransform/>

      44:             </Image.RenderTransform>

      45:         </Image>

      46:  

      47:     </Grid>

      48: </UserControl>

    I’ll cover the different parts:

    • Line 11: “The DataContext” property is bound to the ViewModel, as usual with MVVM.
    • Line 12: The attached “CurrentState” property is bound to the “CurrentState” property of the ViewModel. This means that when the “CurrentState” property in the ViewModel changes, the UserControl also transitions to another state.
    • Line 16 – 36: This XAML is all generated by Blend 4, and tells the VisualStateManager how the control should look in each state. You can generate this XAML in Blend by first opening your UserControl and then switching to the “States” panel:

    image

    You can add states as much as you like and define transitions and animations by clicking on a state and then simply modify the properties in the designer. You only have to make sure that the added states have the same names as the states in the ViewModel.

    • Line 40 – 41: Here the attached “MouseEnterCommand” and the “MouseLeaveCommand” properties are bound to the commands in the ViewModel. They’ll execute when the mouse enters or leaves the image.

    Conclusion

    That’s it! This is all that is needed to wire everything up. By splitting the application up in states, using attached properties for the missing commands and using the VisualStateManager to transition between states in a declarative way, all responsibilities are neatly where they belong, without needing a reference from the ViewModel to the View. The ViewModel determines to which state is switched and remains testable and the View knows how it should look in each state. The beauty of the View is that all that it needs to know is in XAML, and you can easily tweak the appearance of the different states with Blend, without ever touching the ViewModel. The solution above also works for DataTemplates in an ItemsControl, DataTemplates also support the manually adding of states in Blend. You can download the whole working solution here. Creating nice interactive states with Blend is easy, especially with Blend’s fluid layout features. Those features have been expanded in Blend 4. Enjoy!

    Share this

Alex van Beek

View profile

IT Training at Info Support

Which training fits you?

Consultancy

Consultancy

Related blogs

  • Video Conferencing en OBS Studio koppelen: online prese…

    Video Conferencing en OBS Studio koppelen: online prese… Maaike Brouwer - 2 years ago

  • Verantwoordelijkheid pakken in jouw eigen persoonlijke …

    Verantwoordelijkheid pakken in jouw eigen persoonlijke … Stephan Versteegh - 2 years ago

  • Tips voor als je gaat afstuderen

    Tips voor als je gaat afstuderen Bart Renders - 3 years ago

Data Discovery Channel

  • Explainable AI - Break open the blackbox

  • Toekomstvaste microservice data architecturen

  • Modern Data Platform

Nieuwsbrief

* verplichte velden

Contact

  • Head office NL
  • Kruisboog 42
  • 3905 TG Veenendaal
  • T +31 318 552020
  • Call
  • Mail
  • Directions
  • Head office BE
  • Generaal De Wittelaan 17
  • bus 30 2800 Mechelen
  • T +32 15 286370
  • Call
  • Mail
  • Directions

Follow us

  • Twitter
  • Facebook
  • Linkedin
  • Youtube

Newsletter

Sign in

Extra

  • Media Library
  • Disclaimer
  • Algemene voorwaarden
  • ISHBS Webmail
  • Extranet
Beheer cookie toestemming
Deze website maakt gebruik van Functionele en Analytische cookies voor website optimalisatie en statistieken.
Functioneel Always active
De technische opslag of toegang is strikt noodzakelijk voor het legitieme doel het gebruik mogelijk te maken van een specifieke dienst waarom de abonnee of gebruiker uitdrukkelijk heeft gevraagd, of met als enig doel de uitvoering van de transmissie van een communicatie over een elektronisch communicatienetwerk.
Voorkeuren
De technische opslag of toegang is noodzakelijk voor het legitieme doel voorkeuren op te slaan die niet door de abonnee of gebruiker zijn aangevraagd.
Statistieken
De technische opslag of toegang die uitsluitend voor statistische doeleinden wordt gebruikt. De technische opslag of toegang die uitsluitend wordt gebruikt voor anonieme statistische doeleinden. Zonder dagvaarding, vrijwillige naleving door uw Internet Service Provider, of aanvullende gegevens van een derde partij, kan informatie die alleen voor dit doel wordt opgeslagen of opgehaald gewoonlijk niet worden gebruikt om je te identificeren.
Marketing
De technische opslag of toegang is nodig om gebruikersprofielen op te stellen voor het verzenden van reclame, of om de gebruiker op een website of over verschillende websites te volgen voor soortgelijke marketingdoeleinden.
Manage options Manage services Manage vendors Read more about these purposes
Voorkeuren
{title} {title} {title}