In the previous article I've shown how to improve the design-time experience
of your custom workflow blocks by creating a TypeDescriptor class and take
advandage of the PropertyGrid
control used by AgilePoint Envision to display and edit the properties of
workflow blocks. I promised to continue on this topic and elaborate on how to
add custom editors for your properties. This article is all about that.
Before we start, let me warn you again 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. This time, I also
expect you to have some System.Windows.Forms
awareness.
Setting up the project
For this article, I'm going to continue with the project we created last time.
First off, we need to add an extra reference to our project:
- System.Windows.Forms.dll
- System.Drawing.Design.dll
Starting easy
As I mentioned at the end of the previous article, the ADAM Workflow API
provides an easy shortcut for adding custom form editors to workflow block
properties. A few default forms are also available, but you can roll your own as
well.
We start by adding a new form named GuidCollectionEditorForm to the project, and have it derive from the
WindowsFormsEditorForm class.

Image 1: Project context menu for adding a new
Windows Form.
When switching back to the form designer after changing the base class, you
should see that the basic controls for this type of form are already present:

Image 2: Screenshot of GuidCollectionEditorForm
form designer in Visual Studio 2008.
You can design your form inside the panel in this form. I will add a listbox, a
few labels and such, until I get roughly the following layout:

Image 3: Layout sketch of the
GuidCollectionEditorForm form.
Now we will override two methods from the base class that we need for passing
around the value of the target property: The LoadFormValue and SaveFormValue
methods. My implementation here is quite basic, but you have the freedom to do
whatever you want (like checking whether the provided string is an AgilePoint
variable reference for instance) in here as long as the values being passed
around are correct for the target properties.
| 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 override void LoadFormValue(object value)
{
if (value == null)
{
return;
}
if (value.GetType() == typeof(string))
{
listBox.Items.AddRange(Array.ConvertAll(
((string)value).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries),
item => (object)GuidConverter.Parse(item)));
}
}
public override object SaveFormValue()
{
List<string> items = new List<string>();
foreach (object item in listBox.Items)
{
items.Add(item.ToString());
}
return string.Join(";", items.ToArray());
}
|
We can use this editor form in AgilePoint Envision after decorating the JobIds
property with the EditorAttribute attribute properly:
| 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<GuidCollectionEditorForm>), typeof(UITypeEditor))]
public string JobIds
{
get { return GetPropertyValue("JobIds", string.Empty); }
set { SetPropertyValue("JobIds", value); }
}
|
As a side note, I've mentioned before that a few designer forms are availbable
out of the box. Below is a comprehensive list of those and when to use them (you
can find them in the Adam.Workflow.AgilePoint.Components.Design namespace):
| Class Name | Usage |
|---|
| GuidListRefEditorForm | Collection of unique identifiers
(semicolon-separated), or a reference to a variable containing such a collection |
| GuidRefEditorForm |
Single unique identifier, or a reference to a variable containing a unique
identifier. |
| StringEditorForm |
A single line string. |
|
StringListRefEditorForm | Collection of strings (semicolon-separated), or a
reference to a variable containing such a collection. |
|
StringRefEditorForm | A single line string, or a reference to a variable
containing such a string. |
| TextEditorForm |
Multiline text. |
Table 1: Overview of all available editor forms.
Creating custom drop-down editors
In the previous article, we added a drop-down list with suggested values for the
ThreadCount property. A more convenient way of allowing the user to select this
would be through a slider control (see the TrackBar control) that
will give the user more visual interaction.
To do this, we will start by creating a user control called SliderEditorControl with a single TrackBar
control by right-clicking and clicking the Add\New User Control context menu
item.

Image 4: Project context menu for adding a new
User Control.
Then we drag in a TrackBar
control from the toolbox and resize it so it will fit in our control. It's
recommended to set the Anchor property of the control so it will resize nicely
and you can add some other visuals if you like to. I ended up with this:

Image 5: Screenshot of the SliderEditorControl
control designer in Visual Studio 2008.
Now we'll leave this control for now, and jump to the plumbing between our
ThreadCount property and this editor we're creating. In order to tell the
PropertyGrid it needs to show this editor instead of the default one, we need to
use the EditorAttribute attribute on our property. This attribute
will tell which type defines the editor for the target property, and which base
type it uses.
However, we cannot simple hand over our user control's type to the editor, since
it must derive from the UITypeEditor class.
So first, we need to create a companion class for our SliderEditorControl control that
inherits from the UITypeEditor class.
Enter the SliderEditor class:
| 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
47
48
49
50
51
52
53
54
55
56
57
58
59
|
public sealed class SliderEditor : UITypeEditor
{
private IWindowsFormsEditorService _EditorService;
public override bool IsDropDownResizable
{
get
{
return false;
}
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
// This tells the property grid to show the control as a drop-down item.
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (context != null)
{
if (context.PropertyDescriptor.PropertyType != typeof (int))
{
throw new InvalidOperationException(
"The SliderEditor editor can only be used on System.Int32 properties.");
}
}
if (provider != null)
{
// Find the editor service on the provider.
_EditorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
}
if (_EditorService != null)
{
SliderEditorControl control = new SliderEditorControl();
if (context != null)
{
// We have defined a range custom attribute that specifies the
// slider's range.
RangeAttribute range = context.PropertyDescriptor.Attributes[typeof(RangeAttribute)] as RangeAttribute;
if (range != null)
{
control.Minimum = range.Minimum;
control.Maximum = range.Maximum;
}
}
control.Value = (int)value;
_EditorService.DropDownControl(control);
value = control.Value;
}
return value;
}
}
|
I have also added a RangeAttibute attribute that helps to set the range of the
slider in our control. The source code for this attribute can be found in the attached
project.
Now we can decorate our ThreadCount property with the EditorAttribute
attribute like shown here:
| C# |
1
2
3
4
5
6
7
8
9
10
|
[Category("Behavior")]
[Description("Specifies the number of threads used by the maintenance manager if multithreading is allowed.")]
[DefaultValue(1)]
[Range(1, 20)]
[Editor(typeof(SliderEditor), typeof(UITypeEditor))]
public int ThreadCount
{
get { return GetPropertyValue("ThreadCount", 1); }
set { SetPropertyValue("ThreadCount", value); }
}
|
Now, after removing the custom TypeConverter from the previous article in our
custom TypeDescriptor, behold the SliderEditor in
all its glory:

Image 6: Screenshot of the SliderEditor in action
in AgilePoint Envision.
Further notes
Most of what we've achieved during this article series is done using the plain
old System.ComponentModel and System.Windows.Forms
namespaces. There's much more
that can be explored than we can do blog posts on. If you're interested in the
matter, here's the one-stop source for all information you need:
http://msdn.microsoft.com/en-us/library/37899azc.aspx.
So from now on, you can improve the design-time support for your workflow blocks,
and by extension for all controls you write (most of this information also
applies to Windows controls and Web controls).
Live long, and prosper.