Created
May 15, 2010 23:53
-
-
Save dezfowler/402514 to your computer and use it in GitHub Desktop.
Allows Silverlight UI and code operations to be executed sequentially.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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