Skip to content

Instantly share code, notes, and snippets.

@dezfowler
Created May 15, 2010 23:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dezfowler/402514 to your computer and use it in GitHub Desktop.
Save dezfowler/402514 to your computer and use it in GitHub Desktop.
Allows Silverlight UI and code operations to be executed sequentially.
/// <summary>
/// Allows UI and code operations to be executed sequentially.
/// </summary>
public class Sequence
{
private Sequence _start;
private Sequence _next;
private bool _alreadyRun = false;
public Sequence()
{
_start = this;
}
private Sequence(Sequence prev)
{
_start = prev._start;
prev._next = this;
}
/// <summary>
/// Play the Storyboard.
/// </summary>
/// <param name="story"></param>
/// <returns></returns>
public Sequence Play(Storyboard story)
{
return new StoryboardSequenceStep(this, story);
}
/// <summary>
/// Execute the Action.
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public Sequence Execute(Action action)
{
return new ActionSequenceStep(this, action);
}
/// <summary>
/// Transition the control to the specified state.
/// </summary>
/// <param name="control"></param>
/// <param name="templateRoot"></param>
/// <param name="groupName"></param>
/// <param name="stateName"></param>
/// <returns></returns>
public Sequence GoTo(Control control, FrameworkElement templateRoot, string groupName, string stateName)
{
return new StateChangeSequenceStep(this, control, templateRoot, groupName, stateName);
}
/// <summary>
/// Run the sequence.
/// </summary>
public void Run()
{
if (!_alreadyRun)
{
_alreadyRun = true;
_start.ExecuteStep();
}
else
{
throw new Exception("Sequence can only be run once.");
}
}
/// <summary>
/// If there is one, execute the next step.
/// </summary>
protected virtual void ExecuteStep()
{
if (_next != null) _next.ExecuteStep();
}
#region Step classes
class StoryboardSequenceStep : Sequence
{
private Storyboard _story;
public StoryboardSequenceStep(Sequence prev, Storyboard story)
: base(prev)
{
_story = story;
_story.Completed += _story_Completed;
}
~StoryboardSequenceStep()
{
// if _story isn't null then we haven't executed for some reason
if (_story != null)
{
CleanUpAndExecuteNext();
}
}
private void _story_Completed(object sender, EventArgs e)
{
CleanUpAndExecuteNext();
}
private void CleanUpAndExecuteNext()
{
Storyboard story = _story;
story.Dispatcher.BeginInvoke(() =>
{
story.Completed -= _story_Completed;
});
_story = null;
base.ExecuteStep();
}
protected override void ExecuteStep()
{
_story.Begin();
}
}
class ActionSequenceStep : Sequence
{
private Action _action;
public ActionSequenceStep(Sequence prev, Action action)
: base(prev)
{
_action = action;
}
protected override void ExecuteStep()
{
_action();
base.ExecuteStep();
}
}
class StateChangeSequenceStep : Sequence
{
private Control _control;
private FrameworkElement _templateRoot;
private string _groupName, _stateName;
private VisualStateGroup _group = null;
private bool _canExecute = false;
private bool _willTransition = false;
public StateChangeSequenceStep(Sequence prev, Control control, FrameworkElement templateRoot, string groupName, string stateName)
: base(prev)
{
_control = control;
_templateRoot = templateRoot;
_groupName = groupName;
_stateName = stateName;
Init();
if (!_canExecute)
{
NullifyRefs();
}
}
/// <summary>
/// Sets up the values for _group, _canExecute and _willTransition.
/// </summary>
private void Init()
{
var result = VisualStateManager
.GetVisualStateGroups(_templateRoot)
.Cast<VisualStateGroup>()
.Where(g => g.Name == _groupName)
.Take(1)
.Select(g => new {
Group = g,
CanExecute = g.States.Cast<VisualState>().Any(s => s.Name == _stateName),
WillTransition = g.Transitions.Cast<VisualTransition>().Any(t => t.To == _stateName)
}).FirstOrDefault();
if (result != null)
{
_group = result.Group;
_canExecute = result.CanExecute;
_willTransition = result.WillTransition;
}
}
/// <summary>
/// Release references.
/// </summary>
private void NullifyRefs()
{
_control = null;
_templateRoot = null;
_group = null;
}
~StateChangeSequenceStep()
{
// Check whether we got executed, if not, do it now!
// ?? This may occur if another GoToState was called before we reach
// ?? the state we're expecting.
if (_canExecute)
{
CleanUpAndExecuteNext();
}
}
protected override void ExecuteStep()
{
if (_canExecute)
{
if (_willTransition)
{
_group.CurrentStateChanged += _group_CurrentStateChanged;
DoStateChange();
return;
}
else
{
DoStateChange();
_canExecute = false;
NullifyRefs();
}
}
base.ExecuteStep();
}
private void DoStateChange()
{
bool succeeded = VisualStateManager.GoToState(_control, _stateName, true);
if (!succeeded)
{
throw new Exception("GoToState failed - check templateRoot is the layout root for control.");
}
}
private void _group_CurrentStateChanged(object sender, VisualStateChangedEventArgs e)
{
if (_canExecute && e.NewState.Name == _stateName)
{
CleanUpAndExecuteNext();
}
}
/// <summary>
/// Clean up after the async operation - unhook event handler etc.
/// </summary>
private void CleanUpAndExecuteNext()
{
VisualStateGroup group = _group;
group.Dispatcher.BeginInvoke( () =>
{
group.CurrentStateChanged -= _group_CurrentStateChanged;
});
_canExecute = false;
NullifyRefs();
base.ExecuteStep();
}
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment