Freigeben über


Finding the Variables in Scope Within the WF Designer

In this thread, one of our forum customers asked the question:

“how do I find all of the variables that are in scope for a given activity in the designer?”

We do not have a public API to do this.  Internally we have a helper type called VariableHelper that we use to display the list of variables in the variable designer, as well as passed into the expression text box intellisense control.

image

image

One thing that I would like to point out is that if you are implementing your expression editor and want to be able to enumerate the variables (to highlight the tokens that are in scope), your implementation of IExpressionEditorService.CreateExpressionEditor() will get a list of model items that correspond to the inscope variables. 

Now, if you are not building an expression editor, and want to be able to enumerate all of the in scope variables, you will need to do a little more work.  Here are the basic steps

  1. Go to parent container
  2. Does it contain variables*
  3. Does it have a parent?
  4. Go to parent’s parent
  5. Repeat from step 2

* This is basically the tricky part, because it is not something we can cleanly determine (and it can also be altered at runtime by adding some additional ones via the CacheMetadata() method.  I’ll describe what the designer is currently doing to accumulate this list, and then talk about designs we could have in a future release.

Current Approach

Currently, there are two things that we look for when we navigate up the model item tree.

  1. Collections of Variables name Variables
  2. Variables injected by the use of ActivityAction

As we walk up the tree, we need to find these.  Let’s first consider the following workflow we will use for our tests:

    1:  wd.Load(new Sequence
    2:  {
    3:      Activities =
    4:      {
    5:          new Sequence
    6:          {
    7:              Activities = 
    8:              {
    9:                  new ForEach<string>
   10:                  {
   11:                      Body = new ActivityAction<string>
   12:                      {
   13:                           Argument = new DelegateInArgument<string> { Name="foo" },
   14:                           Handler = 
   15:                              new Sequence
   16:                              {
   17:                                  Activities = 
   18:                                  {
   19:                                      new WriteLine()
   20:                                  }
   21:                              }
   22:                      }
   23:                  }
   24:              }
   25:          }
   26:      }
   27:  });

Line 13 is the only one that might look a little different here, I’ll eventually have a post up about ActivityAction that talks in more detail what’s going on there.

So, this loaded in a rehosted designer looks like the following:

image  

Now, let’s look at the code to figure out what is in scope of the selected element.  First, let’s get the selected element :-)

  Selection  sel = wd.Context.Items.GetValue<Selection>();
 if (sel != null)
 {
        ModelItem mi = sel.PrimarySelection;

mi is now the model item of my selected item.  Let’s write some code to walk up the tree and add to a collection called variables

    1:  while (mi.Parent != null)
    2:  {
    3:      Type parentType = mi.Parent.ItemType;
    4:      if (typeof(Activity).IsAssignableFrom(parentType))
    5:      {
    6:          // we have encountered an activity derived type
    7:          // look for variable collection
    8:          ModelProperty mp = mi.Parent.Properties["Variables"];
    9:          if (null != mp && mp.PropertyType == typeof(Collection<Variable>))
   10:          {
   11:              mp.Collection.ToList().ForEach(item => variables.Add(item));
   12:          }
   13:      }
   14:      // now we need to look action handlers 
   15:      // this will ideally return a bunch of DelegateArguments
   16:      var dels = mi.Properties.Where(p => typeof(ActivityDelegate).IsAssignableFrom(p.PropertyType));
   17:      foreach (var actdel in dels)
   18:      {
   19:          if (actdel.Value != null)
   20:          {
   21:              foreach (var innerProp in actdel.Value.Properties)
   22:              {
   23:                  if (typeof(DelegateArgument).IsAssignableFrom(innerProp.PropertyType) && null != innerProp.Value)
   24:                  {
   25:                      variables.Add(innerProp.Value);
   26:                  }
   27:              }
   28:          }
   29:      }
   30:   
   31:   
   32:      mi = mi.Parent;
   33:  }

Lines 4-13 handle the case where I just encounter an activity.  Lines 16-29 handle the slightly more tricky case where I need to handle ActivityAction (which ultimately derives from ActivityDelegate).  There are a few loops there but basically I look through all of the properties of the item which inherit from ActivityDelegate.  For each one of those, and for each property on that, I look for properties assignable from DelegateArgument.  As I find them I add them to my collection of model items for variables. 

In my WPF app, I have a simple list box that shows all of these.  Because of the loosely typed data template I use in my WPF app, I get a pretty decent display since they share common things like ItemType from ModelItem, and a Name that routes through to the underlying Name property both elements share.

image

 <ListBox Name="listBox1" Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0">
     <ListBox.ItemTemplate>
         <DataTemplate>
             <StackPanel Orientation="Horizontal">
                 <TextBlock>Foo:</TextBlock>
                 <TextBlock FontWeight="Bold" Text="{Binding Path=ItemType}"/>
                 <TextBlock Text="{Binding Path=Name}"/>
             </StackPanel>
         </DataTemplate>
     </ListBox.ItemTemplate>
 </ListBox>

 

Potential Future Approach

Our approach outlined above is generally correct.  That said, you can imagine a deeply nested data structure on an activity that contain variables that get injected into the scope at runtime.  We need a way for an activity to provide a way to declare how to get all of its variables.  The current approach is one of inspection, which works for all of our activities. In the future we may need to add an extensibility point to allow an activity author to specify how to find the variables that are available to its children.  The way that we could do that is to introduce an “VariableResolver” attribute which points to a type (or maybe a Func<>) that operates on the activity and returns the list of variables.  Actually today you could likely introduce a custom type descriptor that lifted the variables out of the activity and surfaces them in such a way that they would be discovered by the inspection above. 

Disclaimer: the Potential Future Approach is simply something that we’ve discussed that we could do, it does not represent something that we will do.  If you think that is an interesting extensibility point that you might want to take advantage of down the road, let me know.