views:

13

answers:

1

I'm wanting to ensure a control remains in a certain state for a minimum amount of time. The control has two states: Loading and Loaded. The Loading state displays an animation and the Loaded state displays the data. There is a fade in/out transition between the two states.

However, the problem is sometimes the data loads so quickly that the animation flicks up on the screen momentarily and then immediately fades out. This looks poor, so what I want to do is impose a minimum amount of time (e.g. half a second) spent in the Loading state. That way, even if the data loads quickly, the loading animation will at least display for long enough not to have a jarring effect.

I've managed to achieve this with a custom VisualStateManager that I imaginitively called MinimumTimeVisualStateManager. However, I'm wondering whether there's a built-in way to achieve what I want without the need for this extra code.

Thanks

+1  A: 

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

Kent Boogaart