Dynamic activities in WF 4.0

8 באפריל 2010

no comments

In WF 3.5 we all remember the the feature called dynamic binding parameters which allowed to dynamically create new dependency properties. Guy Burstein wrote an excellent blog describing dynamic binding parameters in WF 3.5.


With dynamic binding parameters the developer could set a certain property and the designer would dynamically create new properties according to some logic.
For example: An activity can have a property in which the workflow developer enters a name of a type. The activity explores the type using reflection and create new properties per each property in the type.


The question is how to accomplish the same experience in WF 4.0.


WF 4.0 uses a new model. In WF 4.0 there are no dependency properties. Activities use a much simpler model in which data is written to variables and arguments. So the question is how to create variables or arguments dynamically.


In WF 4.0 activities design is split to two separate dimensions:
1. The activity logic
2. The activity designer.


The activity designer is a simple WPF canvas. (WPF provides endless opportunities) but if we dig a little we find out that it is actually a WorkflowViewElement implementation. This class provides the gap between the activity implementation and the activity designer.


WorkflowViewElement Has a property called ModelItem that will do the job.


Looking at the definition of the Clsss ModelItem we can see how it describes each element in the activity tree.


The ModelItem class represents a single item in the editing model. An item can be anything from a window or a control down to a color or an integer. You may access the item’s properties through its Properties collection and make changes to the values of the properties.



 public abstract class ModelItem : INotifyPropertyChanged


    {


        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]


        protected ModelItem();


        public abstract AttributeCollection Attributes { get; }


        public abstract ModelProperty Content { get; }


        public abstract Type ItemType { get; }


        public abstract string Name { get; set; }


        public abstract ModelItem Parent { get; }


        public abstract IEnumerable<ModelItem> Parents { get; }


        public abstract ModelPropertyCollection Properties { get; }


        public abstract ModelItem Root { get; }


        public abstract ModelProperty Source { get; }


        public abstract IEnumerable<ModelProperty> Sources { get; }


        public abstract DependencyObject View { get; }


        public abstract event PropertyChangedEventHandler PropertyChanged;


        public abstract ModelEditingScope BeginEdit();


        public abstract ModelEditingScope BeginEdit(string description);


        public abstract object GetCurrentValue();


        public override string ToString();


    }


To respond to property change event (The developer set a certain property of the activity) we have to register to ModelItem.PropertyChanged event.


To create new variables \ arguments we will use : ModelItem.Properties


ModelItem.Properties allows to access each of the activity's properties.
To create new variables dynamically we need to create a property of collection of variables in our activity. To create a new dynamic variable all we need to do is to create an instance of a variable and add it to this collection.



this.ModelItem.Properties["Variables"].Collection.Add(new Variable<string> { Name = "VatiableName" });


To summarize I attach a sample activity and the designer:



<sap:ActivityDesigner x:Class="DynamicActivityLibrary.DynamicActivityDesigner"


   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


   xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"


   xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">


    <Grid>


    </Grid>


</sap:ActivityDesigner>



// Interaction logic for DynamicActivityDesigner.xaml


public partial class DynamicActivityDesigner


{


    private static List<string> newVariables = new List<string>();


    public DynamicActivityDesigner()


    {


      InitializeComponent();


      this.Loaded += new RoutedEventHandler(DynamicActivityDesigner_Loaded);


    }


public void DynamicActivityDesigner_Loaded(object sender, RoutedEventArgs e)


{          


   this.ModelItem.PropertyChanged +=
              new PropertyChangedEventHandler(Value_PropertyChanged);          


}


void Value_PropertyChanged(object sender, PropertyChangedEventArgs e)


{


  if (this.ModelItem.Properties["Text"].ComputedValue != null)


  {


    if (this.ModelItem.Properties["Text"].ComputedValue.ToString() == "person")


     {


        if (!newVariables.Contains("personName"))


        {


             this.ModelItem.Properties["Variables"].Collection.Add(new Variable<string> { Name = "personName" });


             this.ModelItem.Properties["Arguments"].Collection.Add(new InArgument<string>());


              newVariables.Add("personName");


        }


      }


  else if (this.ModelItem.Properties["Text"].ComputedValue.ToString() == "employee")


  {


      if (!newVariables.Contains("employeeName"))


      {


       this.ModelItem.Properties["Variables"].Collection.Add(new Variable<string> { Name = "employee" });


       this.ModelItem.Properties["Arguments"].Collection.Add(new InArgument<string>());


        newVariables.Add("employeeName");


       }


    }


  }


}


}


Now the activity code:



    [Designer(typeof(DynamicActivityDesigner))]


    public class MyDynamicActivity : NativeActivity


    {


        Collection<Variable> variables;


        Collection<InArgument<string>> arguments;


        string text;


        [Browsable(false)]


        public Collection<Variable> Variables


        {


            get


            {


                if (this.variables == null)


                {


                    variables = new Collection<Variable>();


                }


                return variables;


            }


        }


        [Browsable(false)]


        public Collection<InArgument<string>> Arguments


        {


            get


            {


                if (this.arguments == null)


                {


                    arguments = new Collection<InArgument<string>>();


                }


                return arguments;


            }


        }


        public string Text


        {


            get


            {


                return text;


            }


            set


            {


                text = value;


            }


        }


        public MyDynamicActivity()


            : base()


        {


            text = string.Empty;


        }


        protected override void Execute(NativeActivityContext context)


        {


        }


    }

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*