Here's my solution:
// a VisualStateManager that can impose minimum times that a control is in a particular state
public class MinimumTimeVisualStateManager : VisualStateManager
{
public static readonly DependencyProperty MinimumTimeProperty = DependencyProperty.RegisterAttached("MinimumTime",
typeof(TimeSpan),
typeof(MinimumTimeVisualStateManager),
new PropertyMetadata(TimeSpan.Zero));
private static readonly DependencyProperty StateChangeMinimumTimeProperty = DependencyProperty.RegisterAttached("StateChangeMinimumTime",
typeof(DateTime),
typeof(MinimumTimeVisualStateManager),
new PropertyMetadata(DateTime.MinValue));
public static TimeSpan GetMinimumTime(VisualState visualState)
{
visualState.AssertNotNull("visualState");
return (TimeSpan)visualState.GetValue(MinimumTimeProperty);
}
public static void SetMinimumTime(VisualState visualState, TimeSpan minimumTime)
{
visualState.AssertNotNull("visualState");
visualState.SetValue(MinimumTimeProperty, minimumTime);
}
private static DateTime GetStateChangeMinimumTime(Control control)
{
control.AssertNotNull("control");
return (DateTime)control.GetValue(StateChangeMinimumTimeProperty);
}
private static void SetStateChangeMinimumTime(Control control, DateTime stateChangeMinimumTime)
{
control.AssertNotNull("control");
control.SetValue(StateChangeMinimumTimeProperty, stateChangeMinimumTime);
}
protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)
{
var minimumTimeToStateChange = GetStateChangeMinimumTime(control);
if (DateTime.UtcNow < minimumTimeToStateChange)
{
// can't transition yet so reschedule for later
var dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = minimumTimeToStateChange - DateTime.UtcNow;
dispatcherTimer.Tick += delegate
{
dispatcherTimer.Stop();
this.DoStateChange(control, templateRoot, stateName, group, state, useTransitions);
};
dispatcherTimer.Start();
return false;
}
return this.DoStateChange(control, templateRoot, stateName, group, state, useTransitions);
}
private bool DoStateChange(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)
{
var succeeded = base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);
if (succeeded)
{
SetStateChangeMinimumTime(control, DateTime.MinValue);
var minimumTimeInState = GetMinimumTime(state);
if (minimumTimeInState > TimeSpan.Zero)
{
SetStateChangeMinimumTime(control, DateTime.UtcNow + minimumTimeInState);
}
}
return succeeded;
}
}
And it is used like this:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Modes">
<VisualState x:Name="Loading" local:MinimumTimeVisualStateManager.MinimumTime="0:00:0.5"/>
<VisualState x:Name="Loaded">
<Storyboard>
<!-- omitted for clarity -->
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
It seems to work just fine, but am hoping this is code I can remove.
Kent