Dependency Properties


In earlier chapters, we have seen example activity types with properties whose implementations differ from the standard "store the property value in a private field" approach. These properties have getters and setters that call GetValue and SetValue methods inherited from the base class of Activity, which is a type called DependencyObject.

The inheritance hierarchy for activity types is shown in Figure 7.1.

Figure 7.1. Activity inheritance hierarchy


To get us started, the DependencyObject type is shown in Listing 7.1.

Listing 7.1. DependencyObject

 namespace System.Workflow.ComponentModel {   public abstract class DependencyObject :     System.ComponentModel.IComponent  {     public object GetValue(DependencyProperty property);     public void SetValue(DependencyProperty property,       object value);     public ActivityBind GetBinding(DependencyProperty property);     public void SetBinding(DependencyProperty property,       ActivityBind bind);     protected internal bool DesignMode { get; }     /* *** other members *** */   } } 


In the sections that follow, we will focus on the details of how DependencyObject helps activities manage their property values. Specifically, we will look at three special, and distinct, kinds of properties that DependencyObject supports:

  • Metadata properties

  • Databinding-enabled properties

  • Attached properties

Activity Metadata

When we develop a C# program, such as the simple one shown here, we can compile it and run it any number of times:

 using System; class Program {   static void Main()   {     string s = Console.ReadLine();     Console.WriteLine(s);   } } 


Every time we run this Echo program, the same program statements execute, according to the program logic that is defined in our source code.

If we wish to comment out the Console.WriteLine statement, we modify the source code like this:

 // Console.WriteLine(s); 


Once that program statement is commented out, it remains commented out unless and until we return to the source code and uncomment it. All of this is very obvious for a C# program.

WF programs are hierarchies of activities. When a WF program instance is executed, the WF runtime needs instances of activity typesactual CLR objectson which it can invoke methods such as Activity.Execute. As we explored in depth in Chapters 1, "Deconstructing WF," and 2, "WF Programs," the representation of program statements as objects goes hand in hand with the WF approach to bookmarking that supports the development of resumable programs.

But the fact that program statements are objects (within an executing WF program instance) presents some challenges.

As we have seen, Activity defines properties such as Name and Enabled. What would happen if the execution logic of an activity decides to change the value of its Enabled property, or even its Name? Changing Enabled to false would imply that the activity, in that specific WF program instance, is now commented out. But this makes no sense. Changing the Name property is problematic too because the execution logic of other activities in the WF program (and also activity databinding) can depend vitally on an assumption that activity names are unchanging.

Let's take the Enabled property as our example, and see exactly what is going on.

When we comment out a C# Console.WriteLine statement, we are removing this program statement from the definition of our C# program. For a comparable WF program expressed in XAML, we could use an XML comment to do the same thing, but really what we need is a Boolean value associated with WriteLine because a WF program can be expressed in a number of different waysnot just as XAMLand we would like a single way of expressing the fact that an activity is "commented out of" a WF program. Because WF programs can be represented in various formats at design-time, and equivalently as an in-memory activity tree at runtime, we do not want to devise format-specific notation for indicating that an activity is commented out.

One way that WF could have tackled this problem would have been to define a CLR attribute type (let's hypothetically call it EnabledAttribute) that could be applied to activities. But CLR attributes (a form of CLR metadata) are applied to types, not objects (instances of types), so that means we would need two versions of our WriteLine activity:

 [Enabled(true)] public class WriteLine : Activity { ... } [Enabled(false)] public class DisabledWriteLine : Activity { ... } 


Clearly this is not a desirable solution. In order to comment out a WriteLine activity, we need to switch to a DisabledWriteLine, which is an altogether different activity type! And this approach is doomed as soon as we introduce the need for associating other, similar pieces of metadata with activities. The combinatorial explosion of the number of activity types would quickly overwhelm us.

Let's go another direction, then, and try using a Boolean property whose value will determine whether or not a WriteLine activity is commented out of a WF program:

 using System; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class WriteLine : Activity   {     private bool enabled;     public bool Enabled     {       get { return this.enabled; }       set { this.enabled = value; }     }     ...   } } 


This approach appears to work much better because we express whether or not an activity is commented out simply by setting the value of the Enabled property. In XAML it looks like this:

 <WriteLine Enabled="false" Text="hello, world" /> 


We seem to have hit upon the right solution. We can easily comment and uncomment this WF program statement by changing the value of its Enabled property.

However, as we mentioned earlier, when we comment out a program statement in our C# program, that statement remains commented out for all instances of the program that are executed. The fact that a program statement is commented out cannot change unless you go back to the definition of the program and uncomment the statement.

Because Enabled is just a property, it is possible for its value to change during the execution of a WF program instance (as we said earlier, the WF runtime will require an object of type WriteLine when we run our WF program). For the Text property defined by the WriteLine activity, this is exactly the behavior we want. The value of the Text property can be set during the execution of the WF program instance (just as the variable s is assigned at runtime in the C# Echo program). For the Enabled property, however, this is a fiasco. Program statements cannot be allowed to change from being uncommented to being commented out (or vice versa) during the execution of a WF program instance!

The Enabled property is what we described in Chapter 3 as a metadata property. The values of activity metadata properties are established at design-time (when a WF program is developed), and cannot change during the execution of instances of a WF program.

It may be helpful to consider another analogy. In C#, when you define a static read-only field for a type, at runtime that field is given a value exactly once within a particular CLR application domain (which is the boundary for access to a loaded type). The value, once set, is immutable and is effectively shared by all objects of that type within that application domain.

We encountered the same idea in Chapter 5, "Applications," when we learned that a WF program prototype is shared by multiple WF program instances. The value of a metadata property is shared by all instances of that activity (across instances of that WF program), and it is the program prototype that holds the actual value of the property.

Now we come to the implementation of metadata properties. How is it that the Enabled property is settable at design-time but is immutable for an activity within a running WF program instance? If we assume the presence of a Boolean propertyhypothetically called IsRunningthat indicates whether or not an activity instance is part of an executing WF program instance, the property implementation might look like this:

 private bool enabled; public bool Enabled {   get { return this.enabled; }   set   {     if (this.IsRunning)       throw new InvalidOperationException(...);     else       this.enabled = value;   } } 


This implementation gives us exactly the behavior we are seeking.

As an activity writer, it would be nice to not have to remember to implement this pattern for every activity metadata property. If a standard implementation could somehow be inherited, this would serve the cause of consistency (for example, ensuring that the message associated with the System.InvalidOperationException is a standard one, used by all activity types) and also make the job of developers easier. In fact, the WF programming model does provide exactly this pattern to developers of activity types via DependencyObject.

The pattern shown here is the standard implementation of a metadata property:

 using System; using System.Workflow.ComponentModel; public class Widget : Activity {   public static readonly DependencyProperty SizeProperty     = DependencyProperty.Register("Size",         typeof(int), typeof(Widget),         new PropertyMetadata(           DependencyPropertyOptions.Metadata)       );   public int Size   {     get { return (int) GetValue(SizeProperty); }     set { SetValue(SizeProperty, value); }   }   ... } 


This pattern is utilized by the Activity type in its implementation of metadata properties such as Name and Enabled and can also be utilized by any derivative of DependencyObject that you develop, including, of course, any activity types.

The implementation of the Widget.Size property has a couple of interesting aspects.

First, the Widget class does not (as might typically be expected) declare a private field of type int to store the value of the Size property. Instead, the Widget class has a static read-only field of type System.Workflow.ComponentModel.DependencyProperty. A dependency property is essentially the declaration of a special kind of property. DependencyProperty supports the definition of several special kinds of properties, one of which is a metadata property.

The Register method that is invoked to initialize the SizeProperty field basically declares four things:

  • There is a special property called "Size".

  • The type of this property is int.

  • This property is being defined on the Widget type.

  • This property is a metadata property.

The second interesting part of the implementation of the Size property is that the property getter and setter invoke inherited methods, GetValue and SetValue, in order to get and set the actual value of the property. The GetValue and SetValue methods are defined by the abstract System.Workflow.ComponentModel.DependencyObject type, which, as we saw in Figure 7.1, is the base class of Activity.

Dependency objects are entities in WF programs that support dependency properties, which as we know now are just special kinds of properties. The kind of special property we are defining here is a metadata propertybut there are other kinds, which we will discuss in the sections that follow. Dependency properties can be used profitably in the design of types other than just activity types, so support for them is not found directly within Activity but instead is implemented in the DependencyObject type from which Activity inherits.

Conceptually, what happens at runtime in our Widget example is that DependencyObject provides storage for the actual value of a Widget object's Size property. In the context of WF, we say that the Size property is "backed by" SizeProperty, which is a field of type DependencyProperty. The association between Size and SizeProperty is twofold. First, there is a naming convention, which must be followed for all dependency properties: The name of the dependency property field (SizeProperty) must be the name of the property (Size) suffixed with "Property". Additionally, the first parameter of the Register method ("Size" in the example code snippet) confirms the name of the property that will be backed by the dependency property being registered.

The GetBinding and SetBinding methods of DependencyObject will be discussed later in this chapter, in the context of activity databinding.

The System.Workflow.ComponentModel.DependencyProperty type is shown in Listing 7.2.

Listing 7.2. DependencyProperty

 namespace System.Workflow.ComponentModel {   public sealed class DependencyProperty   {     public static DependencyProperty Register(string name,       Type propertyType, Type ownerType);     public static DependencyProperty Register(string name,       Type propertyType, Type ownerType,       PropertyMetadata propertyMetadata);     public string Name { get; }     public Type OwnerType { get; }     public Type PropertyType { get; }     /* *** other members *** */   } } 


As we saw earlier, the registration of a DependencyProperty specifies the name of the property, the type of the property, and the type that is declaring the property (this is sometimes called the owner type of the dependency property). The overload of the Register method that we used in the definition of the Widget activity type has a fourth parameter of type System.Workflow.ComponentModel.PropertyMetadata that explicitly specifies the nature of the special property being registered.

To summarize what we have learned in this section, DependencyObject is the type from which Activity derives, and from which all activity types inherit an ability to define special kinds of properties. One special kind of property is a metadata property; the value of a metadata property is set (on an activity declaration within a WF program) at design-time and is then immutable across instances of that activity within running instances of the WF program.

The Activity class defines an Enabled property that is a metadata property. Its purpose is exactly what we described earlier: If the value of this property is false (the default value is TRue), the activity is considered commented out of the WF program in which it has been declared.

The Activity class also defines a Name property, which, like Enabled, is a metadata property. This means that every activity declaration (program statement) in a WF program can be given a name, which can be used thereafter to refer to that activity. If activities were not named, we would have to resort to referring to them by position. Because Name is a metadata property, we know that its value is supplied at design-time and then cannot change during the execution of instances of the WF program. As we will see shortly, we will rely upon the fact that Name is an activity metadata property when we use activity databinding to implement the WF version of our C# Echo program.

Activity Databinding

Let's return now to our C# Echo program that we want to implement as a WF program. This program contains the following program statements within its Main method:

 string s = Console.ReadLine(); Console.WriteLine(s); 


To implement this program we are going to need a ReadLine activity with a Text property that holds the string that is provided by an external entity (which can read from the console). The implementation of ReadLine was shown and explained in Chapters 2 and 3. What we are interested in probing here is how the Text property of a WriteLine activity can take its value, at runtime, from the Text property of a ReadLine activity.

The answer is activity databinding.

In the first implementation of WriteLine way back in Chapter 2, we implemented the Text property in the standard way, using a private field of type string. WriteLine.Text is not a metadata property because its value can differ, for a given WriteLine activity, across instances of a WF program. So, using a private field is a perfectly legitimate way of implementing the Text property; unfortunately, it does not help us solve our data flow problem.

DependencyObject helps us. The second kind of special property that is supported by any dependency object is a databinding-enabled property. A databinding-enabled property can be assigned a databinding expression that is evaluated at runtime in order to obtain the actual value of the property. A databinding-enabled property can also be assigned a regular value (of the appropriate type) in lieu of a databinding expression; in this way, use of activity databinding is a choice made by the user of the activity.

Metadata properties and databinding-enabled properties are mutually exclusive sets. If a property is a metadata property, its value is supplied at design-time. The whole point of a databinding-enabled property is that the value will be procured at runtime by evaluation of a databinding expression. Thus, Activity.Name is not a databinding-enabled property; the Text property of WriteLine, however, is a perfect candidate for us to enhance with this databinding capability.

A reimplementation of the WriteLine.Text property, which turns it into a databinding-enabled property, is shown here:

 using System; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class WriteLine : Activity   {     public static readonly DependencyProperty TextProperty       = DependencyProperty.Register("Text",         typeof(string), typeof(WriteLine));     public string Text     {       get { return (string) GetValue(TextProperty); }       set { SetValue(TextProperty, value); }     }     ...   } } 


The implementation of the Text property's getter and setter have changed; they now look very much like our earlier implementation of the Widget.Size property. A dependency property field, TextProperty, has been declared, and is being used to back the Text property.

The parameters to the DependencyProperty.Register method are in this case not indicating that Text is a metadata property. When a dependency property is registered using the overload of Register that only accepts three parameters (as is the case here), it is presumed that the kind of property being specified is a databinding-enabled property (in other words, this is the default). We could equivalently use the same overload of Register that we had used for Widget.Size with the following value for the fourth parameter:

 new PropertyMetadata(DependencyPropertyOptions.Default) 


Now that we have implemented WriteLine.Text as a databinding-enabled property, we can assign to it an activity-databinding expression, like so:

 <ReadLine x:Name="r1" /> <WriteLine x:Name="w1" Text="{wf:ActivityBind r1,Path=Text}" /> 


To declare an activity-databinding expression, we require the use of the ActivityBind type, which is shown in Listing 7.3.

Listing 7.3. ActivityBind

 namespace System.Workflow.ComponentModel {   public sealed class ActivityBind : MarkupExtension   {     public ActivityBind();     public ActivityBind(string name);     public ActivityBind(string name, string path);     public string Name { get; set; }     public string Path { get; set; }     public object GetRuntimeValue(Activity activity);     public void SetRuntimeValue(Activity activity, object value);     /* *** other members *** */   } } 


The Name property of ActivityBind names an activity within the same WF program. The Path property indicates a field or property on the activity indicated by the Name property. In our previous example XAML snippet, the value of the Path property is simply "Text" (indicating a binding to the Text property of the ReadLine) but the value can also be a dotted path such as "PropA.PropB.PropC" that indicates the location of a nested property (or field) within a compound property (or field) defined on the activity type.

The WriteLine.Text property getter is called by the Execute method of WriteLine. When this happens, the GetValue method (inherited from DependencyObject) is called by the implementation of the Text property's getter. Because Text is a databinding-enabled property, the DependencyObject type's implementation of GetValue knows that it will find either a string object (the type of the Text property is string, as indicated by the Register method) or an ActivityBind object in its internal table of dependency property values when it looks for a value to return. If a string is found, that value is returned. If an ActivityBind object is found, the ActivityBind.GetRuntimeValue method is invoked in order to evaluate the databinding expression. The result of evaluation is then returned. This process is depicted in Figure 7.2. To clarify, what we have just described are a few of the internals of the DependencyObject type. The developer of the WriteLine activity type, as well as the developer of WF programs that utilize WriteLine, needn't worry about the underlying mechanics of how databinding expressions are evaluated.

Figure 7.2. Evaluation of an activity-databinding expression


In our example XAML snippet, we specify the databinding expression as the value of the Text property using a special syntax, Text="{wf:ActivityBind r1,Path=Text}". What happens beneath the surface, in this example, is that an ActivityBind object is created, its Name property is set to "r1", and its Path property is set to "Text". The ActivityBind object is then associated with the Text property of the WriteLine activity using the DependencyObject.SetBinding method (refer to Listing 7.1), which allows a databinding-enabled property to be assigned a databinding expression.

It is critical here that the Name property of ReadLine (inherited from Activity) is a metadata property; if it were not, activity names would be able to change at runtime. Because activity-databinding expressions refer to activities by name, their evaluation yields predictable results only if Activity.Name cannot change at runtime.

To complete our WF program, we use the Sequence composite activity as a container of a ReadLine and a WriteLine:

 <Sequence xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow">   <ReadLine x:Name="r1" />   <WriteLine x:Name="w1" Text="{wf:ActivityBind r1,Path=Text}" /> </Sequence> 


The preceding XAML is a complete WF program and, when executed by the WF runtime, will produce the same result as the C# Echo program we saw earlier in the chapter.

Attached Properties

There is a third kind of dependency property that is known as an attached property. Attached properties are a general-purpose capability of dependency objects, but are particularly useful in the development of composite activities that must be provided with special information about each of their child activities.

To illustrate how attached properties work, we will return to the PrioritizedInterleave activity that we introduced in Chapter 3. PrioritizedInterleave executes its child activities in priority order. First, all child activities with a priority of 1 are executed (in an interleaved fashion); when those are all completed, child activities with a priority of 2 are executed (again, in an interleaved fashion). This continues until all child activities have completed their execution. We would like this composite activity to be general-purpose so that activities of any type can be contained within it. This seems a tall order because we know that not all activities (in fact, none of the ones we've developed) define a property called Priority.

This is precisely where an attached property can help us out. Essentially, an attached property is a property that is defined by one type (the owner type), but is applied to other types, which then act as if they had defined such a property.

Listing 7.4 shows the PrioritizedInterleave activity implementation.

Listing 7.4. PrioritizedInterleave Activity that Uses an Attached Property

 using System; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class PrioritizedInterleave : CompositeActivity   {     public static readonly DependencyProperty PriorityProperty       = DependencyProperty.RegisterAttached("Priority",           typeof(Int32),           typeof(PrioritizedInterleave),           new PropertyMetadata(             DependencyPropertyOptions.Metadata)     );     public static object GetPriority(object dependencyObject)     {       return ((DependencyObject)         dependencyObject).GetValue(PriorityProperty);     }     public static void SetPriority(object dependencyObject,       object value)     {       ((DependencyObject)dependencyObject).SetValue(         PriorityProperty, value);     }     // Execution logic...   } } 


Listing 7.4 shows the standard boilerplate code for an attached property, which is somewhat larger than the code for the other kinds of dependency properties we have seen. First of all, we are using the RegisterAttached method (not Register) of DependencyProperty to register the attached property. Second, we provide public static methods for getting and setting the value of the attached property. These methods take the place of the strongly typed (and nonstatic) getter and setter that exist for standard properties. Such a strongly typed property is by definition not possible for an attached property because the property will be attached to objects of already-compiled types. There is a requirement that an object to which the property is being attached is a derivative of DependencyObject.

With the PriorityProperty dependency property defined and registered, we can define WF programs that use the PrioritizedInterleave composite activity. Such a program is shown in Listing 7.5. In our example implementation, we've chosen to make Priority a metadata property but this needn't be the case; attached properties can also carry values that are determined at runtime.

Listing 7.5. A WF Program that Uses the PrioritizedInterleave Activity

 <PrioritizedInterleave x:Name="i1" xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow">   <Sequence x:Name="s1" PrioritizedInterleave.Priority="2" >   ...   </Sequence>   <Sequence x:Name="s2" PrioritizedInterleave.Priority="1" >   ...   </Sequence>   <Sequence x:Name="s3" PrioritizedInterleave.Priority="3" >   ...   </Sequence>   <Sequence x:Name="s4" PrioritizedInterleave.Priority="2" >   ...   </Sequence>   <Sequence x:Name="s5" PrioritizedInterleave.Priority="3" >   ...   </Sequence> </PrioritizedInterleave> 





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