Navigator


Graph notation is a great way of describing the nonlinear execution of statements within a program. But we do not need to limit ourselves to a single general-purpose Graph activity. For example, it is vital in some scenarios to use a graph-based composite activity that only allows a single child activity to execute at a time. The transitions between activities can be viewed as jumps (like goto) and can be used to model a wide range of dynamic control flow patterns such as nonlinear UI navigation among a set of UI elements. Consider the composite activity called Navigator that is shown in Listing B.3.

Listing B.3. Navigator Activity

 using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class Navigator : CompositeActivity   {     public static readonly DependencyProperty       StartWithProperty = DependencyProperty.Register(         "StartWith",         typeof(string),         typeof(Navigator)       );     public string StartWith     {       get { return (string) GetValue(StartWithProperty); }       set { SetValue(StartWithProperty, value); }     }     public static readonly DependencyProperty       NavigateToProperty = DependencyProperty.RegisterAttached(         "NavigateTo",         typeof(string),         typeof(Navigator)       );     public static object GetNavigateTo(object dependencyObject)     {       DependencyObject o = dependencyObject as DependencyObject;       return o.GetValue(NavigateToProperty);     }     public static void SetNavigateTo(object dependencyObject, object value)     {       DependencyObject o = dependencyObject as DependencyObject;       o.SetValue(Navigator.NavigateToProperty, value);     }     public static readonly DependencyProperty       NavigatingEvent = DependencyProperty.Register(         "Navigating",         typeof(EventHandler<NavigatorEventArgs>),         typeof(Navigator)       );     public event EventHandler<NavigatorEventArgs> Navigating     {       add { base.AddHandler(NavigatingEvent, value); }       remove { base.RemoveHandler(NavigatingEvent, value); }     }     protected override ActivityExecutionStatus Execute(       ActivityExecutionContext context)     {       if (this.TryNavigatingTo(context, this.StartWith))         return ActivityExecutionStatus.Executing;       return ActivityExecutionStatus.Closed;     }     private bool TryNavigatingTo(ActivityExecutionContext context,       string nextActivityName)     {       ActivityExecutionContextManager manager =         context.ExecutionContextManager;       //populate the history       List<Activity> history = new List<Activity>();       foreach (Guid ctxid in manager.PersistedExecutionContexts)       {         ActivityExecutionContext serializedContext =           manager.GetPersistedExecutionContext(ctxid);         history.Add(serializedContext.Activity);         // GetPersistedExecutionContext above removed the context         // so we need to explicitly add it back         context.ExecutionContextManager.           CompleteExecutionContext(serializedContext, true);       }       //raise the event       NavigatorEventArgs args =         new NavigatorEventArgs(history.AsReadOnly());       RaiseGenericEvent(Navigator.NavigatingEvent, this, args);       Activity nextActivity = null;       if (args.NavigateTo != null)         nextActivity = args.NavigateTo;       else if (!string.IsNullOrEmpty(nextActivityName))         nextActivity = this.GetActivityByName(nextActivityName);      if (nextActivity != null)      {        ActivityExecutionContext innerContext =          manager.CreateExecutionContext(nextActivity);        innerContext.Activity.Closed += this.ContinueAt;        innerContext.ExecuteActivity(innerContext.Activity);        return true;      }      return false;    }    private void ContinueAt(Object sender,      ActivityExecutionStatusChangedEventArgs e)    {      ActivityExecutionContext context =        sender as ActivityExecutionContext;      ActivityExecutionContextManager manager =        context.ExecutionContextManager;      ActivityExecutionContext innerContext =        manager.GetExecutionContext(e.Activity);      //first, unsubscribe to the inner context's activity      innerContext.Activity.Closed -= this.ContinueAt;      //remove the inner context and serialize it      manager.CompleteExecutionContext(innerContext, true);      string nextActivityName = Navigator.GetNavigateTo(        innerContext.Activity) as string;      if (!this.TryNavigatingTo(context, nextActivityName))        context.CloseActivity();    }      // Cancellation logic      ...  }    public class NavigatorEventArgs : EventArgs    {      private ReadOnlyCollection<Activity> history = null;      private Activity navigateTo = null;      internal NavigatorEventArgs(ReadOnlyCollection<Activity> history)      {        this.history = history;      }      public Activity NavigateTo      {        get { return navigateTo; }        set { navigateTo = value; }      }      public ReadOnlyCollection<Activity> History      {        get { return this.history; }      }   } } 


The Navigator activity has a set of child activities, but its execution logic will only allow a single child activity to execute at a time. Each child activity represents a named destination for navigation. Navigator maintains a history of all destinations previously visited (executed) and allows for revisiting of destinations. It is as if each child activity of the Navigator is a logical bookmark within the Navigator.

The Navigator activity applies an attached property named NavigateTo to each of its child activities. The value of the NavigateTo property can be the Name of any other child activity within the parent Navigator. You can think of the value of the NavigateTo property as the name of the next bookmark to jump to, or a label for a jmp instruction. A value of the empty string or null for the NavigateTo property is used to indicate the end of navigation and completes the execution of the Navigator activity. Navigator also has a StartWith property that names the child activity that will be the first to execute.

The following WF program puts our Navigator activity to use by capturing a simple, nonlinear control flow using Navigator and a set of WriteLine activities:

 <Navigator x:Name="navigator1" StartWith="w3" xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   <WriteLine x:Name="w1" Text="One" Navigator.NavigateTo="w4"/>   <WriteLine x:Name="w2" Text="Two" Navigator.NavigateTo=""/>   <WriteLine x:Name="w3" Text="Three" Navigator.NavigateTo="w1"/>   <WriteLine x:Name="w4" Text="Four" Navigator.NavigateTo="w2"/> </Navigator> 


Figure B.2 shows the preceding program visually.

Figure B.2. A Navigator-based WF program


The output of this program is

 Three One Four Two 


The preceding program defines the path of execution statically (at design-time) by specifying the value of the NavigateTo property of each child of the Navigator. In some cases this may be desirable; however, many scenarios demand a more flexible approach to deciding the next destination to navigate to. For this reason, we've chosen to not make NavigateTo a metadata property. Thus, the value of NavigateTo can be set dynamically during activity execution. And to accommodate this common pattern, the Navigator defines a Navigating event, which, when raised, is a convenient place for indicating the next activity that should execute.

The Execute method calls the method tryNavigatingTo, passing the value of the StartWith property. tryNavigatingTo returns TRue if it successfully schedules a child activity for execution. The implementation of tryNavigatingTo first raises the Navigating event, giving the user of the activity a chance to decide the next activity that should execute. Navigator reloads previously completed execution contexts and provides this history (of previously visited activities) to the subscriber of the Navigating event via the NavigatorEventArgs.

When the next activity to execute has been determined, Navigator creates a new AEC, passing the chosen activity (next destination) as the template activity. The template activity can be statically determined (as in the case of StartWith), or dynamically selected from the history (provided via NavigatorEventArgs). Navigator, of course, subscribes for the Closed event of the dynamically created activity instance and then schedules it for execution.

The event handler of the Closed event (ContinueAt), serializes the just-completed AEC by invoking AECManager.CompleteExecutionContext, passing TRue as the value for the forcePersist parameter (second argument to the method).

As an example of the usage of navigation using the history of previously executed (serialized) execution contexts, consider the following program that executes a single WriteLine activity. The program replays the execution of a previously executed WriteLine activity instance from the history, with a modified value of the Text property, five times:

 <Navigator x:Name="navigator1" x: StartWith="w1" Navigating="OnNavigating" xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   <WriteLine x:Name="w1" Text="1" />   <x:Code>   <![CDATA[       void OnNavigating(object sender, NavigatorEventArgs e)       {        if (e.History != null && e.History.Count > 0)        {          if (e.History.Count == 5)            e.NavigateTo = null;          else          {            // The last WriteLine in the history            WriteLine w = (WriteLine)              e.History[e.History.Count - 1];            // Increment the Writeline's Text property by one            w.Text = (Int32.Parse(w.Text) + 1).ToString();            // This is now the new future, go there!            e.NavigateTo = w;          }        }      }   ]]>   </x:Code> </Navigator> 


The output of this program is

 1 2 3 4 5 





Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net