none
Change VisualState in MVVM RRS feed

Answers

  • Two mistakes:
    1, if you specify the StateManager.VisualStateProperty attached property on the Window element, your <VisualStateManager.VisualStateGroups> should be specified on the Window element directly.
    <Window x:Class="WpfApplication9.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Width="922" Height="547"
            xmlns:common="clr-namespace:......" common:StateManager.VisualStateProperty="{Binding TimeState}">
    
      <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="TimeNormalStateGroup">
          <VisualState x:Name="TimeNormalState">
          ......
    
       <Grid>
          ......
    

    2, you could use VisualStateManager.GoToElementState method if you do not go to the state in the cotnrol template.
    And I suggest you to change the Control class derived limilation as:
      public class StateManager : DependencyObject
      {
        public static string GetVisualStateProperty(DependencyObject obj)
        {
          return (string)obj.GetValue(VisualStatePropertyProperty);
        }
    
        public static void SetVisualStateProperty(DependencyObject obj, string value)
        {
          obj.SetValue(VisualStatePropertyProperty, value);
        }
    
        public static readonly DependencyProperty VisualStatePropertyProperty =
          DependencyProperty.RegisterAttached(
          "VisualStateProperty",
          typeof(string),
          typeof(StateManager),
          new PropertyMetadata((s, e) =>
          {
            var propertyName = (string)e.NewValue;
            var ctrl = s as FrameworkElement;  // Change to FrameworkElement
            if (ctrl == null)
              throw new InvalidOperationException("This attached property only supports types derived from FrameworkElement.");
            System.Windows.VisualStateManager.GoToElementState(ctrl, (string)e.NewValue, false);  // Use GoToElementState method
          }));
      }
    
    Sincerely, 

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, October 17, 2011 8:47 AM
    Moderator

All replies

  • Could you provide a code sample of how you're trying to use it?

    What does you view model look like and how are you setting up bindings to use it?

     

    Sunday, October 16, 2011 3:16 PM
  • Hopefully this will help. This is very basic, but it should recreate my issue.

    I have moved the definition of the VisualStates everywhere possible with no success. Do I define the the states wrong or is it something else? Would I then have a diffirent property for each state group if I got more than one state group defined, because I would need a couple of diffirent states depending on various situation in the app. Here follows the code samples.

    The View:

    <Window x:Class="WpfMvvmVisualStates.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:common="clr-namespace:WpfMvvmVisualStates.Common" common:StateManager.VisualStateProperty="{Binding TimeState}"
            Title="MainWindow" Width="922" Height="547">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="48" />
                <RowDefinition Height="97" />
                <RowDefinition Height="65" />
                <RowDefinition Height="297" />
            </Grid.RowDefinitions>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="TimeNormalStateGroup">
                    <VisualState x:Name="TimeNormalState">
                        <Storyboard>
                            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="lblPlayingTime">
                                <EasingColorKeyFrame KeyTime="0" Value="Black"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="TimeFinishedState">
                        <Storyboard>
                            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="lblPlayingTime">
                                <EasingColorKeyFrame KeyTime="0" Value="#FFBE0505"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Grid Name="timeGrid" Grid.Row="0" Grid.ColumnSpan="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="383*" />
                    <ColumnDefinition Width="383*" />
                </Grid.ColumnDefinitions>
                <StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0,0,15,0" HorizontalAlignment="Right" VerticalAlignment="Center">
                    <Label FontSize="24" Content="Playing Time:" Margin="0,-5,0,0" VerticalAlignment="Center" />
                    <Label Name="lblPlayingTime" FontSize="36" Margin="10,-5,0,0" Content="00:40:00">
                    </Label>
                </StackPanel>
                <StackPanel Grid.Column="1" Orientation="Horizontal" Margin="15,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center">
                    <TextBlock FontSize="24" Text="Running Time:" Margin="0,-5,0,0" VerticalAlignment="Center"/>
                    <TextBlock FontSize="36" Margin="10,-5,0,0" Text="00:40:00" />
                </StackPanel>
            </Grid>
            <Button x:Name="btnStart" Content="Red State" Height="77" HorizontalAlignment="Center" Margin="365,8,450,0" VerticalAlignment="Top" Width="85" Command="{Binding StartStopCommand}" Grid.Row="1" />
            <Button x:Name="btnEnd" Content="Normal State" Height="77" HorizontalAlignment="Center" Margin="465,8,354,0" VerticalAlignment="Top" Width="81" Command="{Binding StopHalfCommand}" Grid.Row="1" />
        </Grid>
    </Window>
    
    

    The ViewModel:

    public class MainWindowViewModel : INotifyPropertyChanged
        {
            /// <summary></summary>
            private RelayCommand startStopCommand;
            /// <summary></summary>
            private RelayCommand stopHalfCommand;
            /// <summary></summary>
            private string timeState;
    
            public ICommand StartStopCommand
            {
                get
                {
                    return this.startStopCommand ?? (this.startStopCommand = new RelayCommand((s) => this.OnStartStop()));
                }
            }
    
            public ICommand StopHalfCommand
            {
                get
                {
                    return this.stopHalfCommand ?? (this.stopHalfCommand = new RelayCommand(a => this.OnStopHalf()));
                }
            }
    
            public string TimeState
            {
                get
                {
                    return this.timeState;
                }
                set
                {
                    if (this.timeState != value)
                    {
                        this.timeState = value;
                        this.OnPropertyChanged("TimeState");
                    }
                }
            }
    
            private void OnStopHalf()
            {
                this.TimeState = "TimeNormalState";
            }
    
            private void OnStartStop()
            {
                this.TimeState = "TimeFinishedState";
            }
    
            #region INotifyPropertyChanged Members
    
            /// <summary>
            /// Raised when a property on this object has a new value.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// <summary>
            /// Raises this object's PropertyChanged event.
            /// </summary>
            /// <param name="propertyName">The property that has a new value.</param>
            protected virtual void OnPropertyChanged(string propertyName)
            {
                PropertyChangedEventHandler handler = this.PropertyChanged;
                if (handler != null)
                {
                    var e = new PropertyChangedEventArgs(propertyName);
                    handler(this, e);
                }
            }
    
            #endregion // INotifyPropertyChanged Members
        }
    

    Sunday, October 16, 2011 7:54 PM
  • Two mistakes:
    1, if you specify the StateManager.VisualStateProperty attached property on the Window element, your <VisualStateManager.VisualStateGroups> should be specified on the Window element directly.
    <Window x:Class="WpfApplication9.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Width="922" Height="547"
            xmlns:common="clr-namespace:......" common:StateManager.VisualStateProperty="{Binding TimeState}">
    
      <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="TimeNormalStateGroup">
          <VisualState x:Name="TimeNormalState">
          ......
    
       <Grid>
          ......
    

    2, you could use VisualStateManager.GoToElementState method if you do not go to the state in the cotnrol template.
    And I suggest you to change the Control class derived limilation as:
      public class StateManager : DependencyObject
      {
        public static string GetVisualStateProperty(DependencyObject obj)
        {
          return (string)obj.GetValue(VisualStatePropertyProperty);
        }
    
        public static void SetVisualStateProperty(DependencyObject obj, string value)
        {
          obj.SetValue(VisualStatePropertyProperty, value);
        }
    
        public static readonly DependencyProperty VisualStatePropertyProperty =
          DependencyProperty.RegisterAttached(
          "VisualStateProperty",
          typeof(string),
          typeof(StateManager),
          new PropertyMetadata((s, e) =>
          {
            var propertyName = (string)e.NewValue;
            var ctrl = s as FrameworkElement;  // Change to FrameworkElement
            if (ctrl == null)
              throw new InvalidOperationException("This attached property only supports types derived from FrameworkElement.");
            System.Windows.VisualStateManager.GoToElementState(ctrl, (string)e.NewValue, false);  // Use GoToElementState method
          }));
      }
    
    Sincerely, 

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, October 17, 2011 8:47 AM
    Moderator
  • Bob,

    Thanks for the reply. I implimented this and it works on the Button click. I have however tried this when using a DispatcherTimer. When I change the VisualState using the DispatcherTimer, nothing happens?

    I tried to do a

    Dispatcher.CurrentDispatcher.Invoke(new Action(() => { this.TimeState = "TimeFinisehdState"; }));
    

     to see if that will make a diffirence, but that didn't change anything.

    Why is it that the VisualState will change in a click event or a CommandBinding but not in the TickEvent of the DispatcherTimer?

    Tuesday, October 18, 2011 8:30 AM
  • Call the current App dispacther to change the property value, and do not forget to start the Timer:

          timer.Interval = new TimeSpan(0, 0, 5);
          timer.Tick += new EventHandler(timer_Tick);
          timer.Start();
        
        ......
    
        void timer_Tick(object sender, EventArgs e)
        {
          App.Current.Dispatcher.Invoke(new Action(() => { this.TimeState = "TimeFinishedState"; }));
        }
    

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, October 19, 2011 2:35 AM
    Moderator
  • I thought I would add my experience to this thread in case anyone find it useful.
    I wanted to use this technique to change the state of a WPF Visual State Manager from a MVVM based application.
    For some time I could not change thre state from the viewmodel however I solved my problem as below:

    I noticed the comment about

     > if you specify the StateManager.VisualStateProperty attached property on the Window element, your <VisualStateManager.VisualStateGroups>
     > should be specified on the Window element directly.

    I had indeed added the attached property to the window element however
    I noticed that Expression blend 4 had created the Visual States within a grid element located within the windows element.
    See below:

     <window
      ....
      mvvm:StateManager.VisualStateProperty="{Binding VisualStateName}">
    ...

      <Grid x:Name="LayoutRoot">
       <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="WorkFlow">
                      <VisualState x:Name="Start">   

    When I moved the <VisualStateManager.VisualStateGroups> up to the window level as suggested in this thread then the code all worked.
    However I noticed that Expression Blend stopped being able to see the states.

    To allow Expression blend to continue working (and allow the ViewModel to change the state) I moved the attached property to the grid element.
    See below:

     <window
      ....

      <Grid x:Name="LayoutRoot" mvvm:StateManager.VisualStateProperty="{Binding VisualStateName}">
       <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="WorkFlow">
                      <VisualState x:Name="Start">

    Perhaps this is obvious to others but it took me a moment to work this out.

    Hope it helps.

    Tuesday, April 24, 2012 11:27 AM