In this article I will show you how you can improve the workflow designer
experience by leveraging the power of the PropertyGrid
control used by AgilePoint Envision to display and edit the properties of
workflow activities.

Image 1: Screenshot of the AgilePoint Envision property grid.
Before we start, let me warn you that I will be assuming somewhat advanced knowledge of the
.NET component model (see the System.ComponentModel namespace) as well as
development of AgilePoint (and ADAM Workflow) building blocks.
Setting up the project
Using Visual Studio 2008, create a new class library project named
Adam.Workflow.CustomActivities and add references to the following assemblies:
- Adam.Core.dll
- Adam.Tools.dll
- Adam.Workflow.Core.dll
- Adam.Workflow.Core.Design.dll
- Adam.Workflow.AgilePoint.dll
- Adam.Workflow.AgilePoint.Components.dll
- Ascentn.Workflow.Share.dll
- Ascentn.Workflow.WFBase.dll
Creating a RunMaintenanceJobs block
As an example, we will create a block that runs ADAM pending maintenance
jobs using the MaintenanceManager class.
We start by creating the actual activity class, which is invoked by AgilePoint
Server when executing the building block. This article focusses on the
descriptor class of the building block, so we won't go into the details of
actually calling the MaintenanceManager class and executing the maintenance jobs
here. This is left as an exercise to the reader (comments!).
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
/// <summary>vides a building block for running pending maintenance jobs.
/// </summary>
[AgilePart(AgilePartName)]
public sealed class RunMaintenanceJobs : AutomaticWorkItem
{
/// <summary>
/// Contains the unique name of the building block.
/// </summary>
public const string AgilePartName = "Adam.Workflow.CustomActivities.RunMaintenanceJobs";
/// <summary>
/// The entry point of the building block called by the workflow system when the building
/// block needs to execute.
/// </summary>
/// <param name="processInstance"></param>
/// <param name="workItem"></param>
/// <param name="api"></param>
/// <param name="parameters"></param>
[AgilePartDescriptor(typeof(RunMaintenanceJobsDescriptor))]
[Description("Runs all pending maintenance jobs.")]
public void Main(WFProcessInstance processInstance, WFAutomaticWorkItem workItem, IWFAPI api, NameValue[] parameters)
{
// Call the base class execute method, which will parse the parameters
// and initialize the ADAM context.
Execute(processInstance, workItem, api, parameters);
}
/// <summary>
/// Executes the building block.
/// </summary>
/// <returns></returns>
protected override bool OnExecute()
{
// TODO: Call the MaintenanceManager.Execute() method here.
return true;
}
}
|
As you already know, the design-time properties for the building block are
provided by the descriptor class, which is linked to the building block by decorating the entry-point method with the
the AgilePartDecriptorAttribute attribute.
Now we can add the RunMaintenanceJobsDescriptor class to our project, which is
the descriptor class for our building block. This class basically lists all
available properties on the building block, and allows for serializing the
values set by the workflow designer into the workflow XML template.
Behold the initial code for the RunMaintenanceJobsDescriptor
class. Note that we're being a nice citizen in the .NET component model and we
decorate our properties accordingly with the CategoryAttribute,
DescriptionAttribute and DefaultAttribute attributes.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
/// <summary>
/// Provides the design-time properties for the <see cref="RunMaintenanceJobs"/> building block.
/// </summary>
public sealed class RunMaintenanceJobsDescriptor : AutomaticWorkItemDescriptor
{
[Category("Behavior")]
[Description("Specifies whether or not notification is enabled.")]
[DefaultValue(true)]
public bool NotificationEnabled
{
get { return GetPropertyValue("NotificationEnabled", true); }
set { SetPropertyValue("NotificationEnabled", value); }
}
[Category("Behavior")]
[Description("Specifies the threading mode used for the maintenance manager.")]
[DefaultValue(ThreadingMode.SingleThreading)]
public ThreadingMode ThreadingMode
{
get { return GetPropertyValue("ThreadingMode", ThreadingMode.SingleThreading); }
set { SetPropertyValue("ThreadingMode", value); }
}
[Category("Behavior")]
[Description("Specifies the number of threads used by the maintenance manager if multithreading is allowed.")]
[DefaultValue(1)]
public int ThreadCount
{
get { return GetPropertyValue("ThreadCount", 1); }
set { SetPropertyValue("ThreadCount", value); }
}
[Category("In Parameters")]
[DisplayName("Job Ids")]
[Description("Specifies the collection of ids of maintenance jobs to execute.")]
public string JobIds
{
get { return GetPropertyValue("JobIds", string.Empty); }
set { SetPropertyValue("NotificationEnabled", value); }
}
}
|
Adding some seasoning...
Now we have a plain dull descriptor class, which does little more than just
giving the user text fields to bluntly type in values. Let's take this to the
next level and add some user experience to this block!
We will allow the user to set the threading mode to either SingleThreading or
MultiThreading, and only when MultiThreading is selected, we will allow the user
to set the number of threads that need to be started. Furthermore, we will
provide a list of default values for the ThreadCount property that suggests
values from 1 up to and including the processor count.
In order to do this, we will need to develop a custom TypeDescriptor that will
modify the way the PropertyGrid control looks at our descriptor class at
runtime.
First things first, we also need a custom TypeDescriptionProvider in order
to link our TypeDescriptor
to our descriptor class using the TypeDescriptionProviderAttribute
attribute.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public sealed class RunMaintenanceJobsTypeDescriptionProvider : TypeDescriptionProvider
{
private static readonly TypeDescriptionProvider DefaultProvider = TypeDescriptor.GetProvider(typeof(RunMaintenanceJobsDescriptor));
public RunMaintenanceJobsTypeDescriptionProvider()
: base(DefaultProvider)
{
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
ICustomTypeDescriptor baseTypeDescriptor = base.GetTypeDescriptor(objectType, instance);
return instance == null ? baseTypeDescriptor : new RunMaintenanceJobsTypeDescriptor(baseTypeDescriptor, instance);
}
}
|
And apply that to our descriptor class:
| C# |
1
2
3
4
5
|
[TypeDescriptionProvider(typeof(RunMaintenanceJobsTypeDescriptionProvider))]
public sealed class RunMaintenanceJobsDescriptor : AutomaticWorkItemDescriptor
{
// ...
}
|
Now we will create the RunMaintenanceJobsTypeDescriptor class and implement the rules for displaying/hiding the ThreadCount property.
We will inherit from the CustomTypeDescriptor class and provide an overridden
version of the GetProperties() method, that will allow us to modify the
attributes on our descriptor class' properties, so we can ultimately hide them
at will.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public sealed class RunMaintenanceJobsTypeDescriptor : CustomTypeDescriptor
{
public RunMaintenanceJobsTypeDescriptor(ICustomTypeDescriptor parent, object instance)
: base(parent)
{
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection baseCollection = GetProperties();
PropertyDescriptor[] propertyDescriptors = new PropertyDescriptor[baseCollection.Count];
baseCollection.CopyTo(propertyDescriptors, 0);
for (int index = 0; index < propertyDescriptors.Length; index++)
{
PropertyDescriptor propertyDescriptor = propertyDescriptors[index];
object instance = GetPropertyOwner(propertyDescriptor);
if (instance != null && instance is RunMaintenanceJobsDescriptor)
{
RunMaintenanceJobsDescriptor descriptor = (RunMaintenanceJobsDescriptor)instance;
switch (propertyDescriptor.Name)
{
case "ThreadCount":
List<Attribute> newAttributes = new List<Attribute>();
if (descriptor.ThreadingMode != ThreadingMode.MultiThreading)
{
// We're programatically adding a Browsable(false) attribute to the property descriptor
// which will hide the property in the property grid.
newAttributes.Add(new BrowsableAttribute(false));
}
propertyDescriptors[index] = TypeDescriptor.CreateProperty(
instance.GetType(),
propertyDescriptor,
newAttributes.ToArray());
break;
}
}
}
return new PropertyDescriptorCollection(propertyDescriptors);
}
}
|
The pièce de résistance
And now
for our grand finale: we're going to show a drop-down list instead of the dull
text box that comes with the ThreadCount property. Fortunately, we have done a lot of the work already, and we can just ad a TypeConverter deriving from Int32Converter and add it to our property descriptor.
By overriding the GetStandardValuesSupported, GetStandardValuesExclusive and
GetStandardValues methods we can control which items are in the drop-down list
and whether or not the user can provide custom values.
Please note that you cannot just add the TypeConverterAttribute property to the property in the descriptor class because of a limitation in AgilePoint Envision's PropertyGrid.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class ThreadCountConverter : Int32Converter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
// We will provide a set of recommended default values.
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
// We will allow user values.
return false;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
Collection<int> values = new Collection<int>();
for (int index = 0; index < Environment.ProcessorCount; index++)
{
values.Add(index + 1);
}
return new StandardValuesCollection(values);
}
}
|
Now we go back to our type descriptor and add the code to push our type converter to the ThreadCount property.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// ...
switch (propertyDescriptor.Name)
{
case "ThreadCount":
List<Attribute> newAttributes = new List<Attribute>();
// We're going to add the TypeConverterAttribute programatically here, because the
// AgilePoint Envision version of the PropertyGrid does not support adding this attribute
// directly to the property in the descriptor class.
newAttributes.Add(new TypeConverterAttribute(typeof (ThreadCountConverter)));
if (descriptor.ThreadingMode != ThreadingMode.MultiThreading)
{
// We're programatically adding a Browsable(false) attribute to the property descriptor
// which will hide the property in the property grid.
newAttributes.Add(new BrowsableAttribute(false));
}
propertyDescriptors[index] = TypeDescriptor.CreateProperty(
instance.GetType(),
propertyDescriptor,
newAttributes.ToArray());
break;
}
// ...
|
Further notes
Above all this, you can also add a UI editor to the properties using the
EditorAttribute that comes with the System.ComponentModel namespace. While this
will
undoubtedly be the subject of another blog post, I will give you a short heads-up on
how to use it. Fast forward to the RunMaintenanceJobDescriptor.JobIds property:
| C# |
1
2
3
4
5
6
7
8
9
|
[Category("In Parameters")]
[DisplayName("Job Ids")]
[Description("Specifies the collection of ids of maintenance jobs to execute.")]
[Editor(typeof(WindowsFormsEditor<GuidListEditorForm>), typeof(UITypeEditor))]
public string JobIds
{
get { return GetPropertyValue("JobIds", string.Empty); }
set { SetPropertyValue("JobIds", value); }
}
|
We have implemented a few common editor forms that you can reuse (like the one
you see here), but you are free to build your own. More to come later!
That concludes our deep dive into building block development, hope you enjoyed
it. Always looking forward to your comments, and as you know, you can download
the source code below when you're logged on.
Happy coding!