|

Introduction
Windows Workflow Foundation (WF) comes with a very cool UITypeEditor that allows selection of a type in the current project or any of its references:

Its type is System.Workflow.ComponentModel.Design.TypeBrowserEditor defined in the System.Workflow.ComponentModel.dll assembly. However, if you try to "attach" it directly to one of your component's properties, you will get an error saying "The service 'System.Workflow.ComponentModel.Compiler.ITypeProvider' must be installed for this operation to suceed. Ensure that this service is available.":

Being a general fan of the Component Model, I knew what this was about, and started working on providing the WF type browser editor with the services it needs. Here are the implementation steps I used to get it to work:
- It was obvious that I could not use the editor as-is, so the first step was to create a custom
UITypeEditor that would call the TypeBrowserEditor in WF:
public class TypeBrowser : UITypeEditor
{
TypeBrowserEditor editor = new TypeBrowserEditor();
ContextProxy flyweight;
public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider, object value)
{
if (flyweight == null)
{
flyweight = new ContextProxy(context);
}
else
{
flyweight.SetContext(context);
}
return editor.EditValue(flyweight, flyweight, value);
}
public override UITypeEditorEditStyle
GetEditStyle(ITypeDescriptorContext typeDescriptorContext)
{
return editor.GetEditStyle(typeDescriptorContext);
}
}
- The code highlighted above shows that I'm passing a custom
ITypeDescriptorContext to the WF editor. This custom context acts as a proxy to the real one received by the editor, and allows me to provide the additional services required by it. It provides the only service required for the editor to work, ITypeProvider:
private class ContextProxy : ITypeDescriptorContext
{
ITypeProvider typeProvider;
ITypeDescriptorContext realContext;
public ContextProxy(ITypeDescriptorContext realContext)
{
this.realContext = realContext;
}
internal void SetContext(ITypeDescriptorContext realContext)
{
this.realContext = realContext;
}
#region ITypeDescriptorContext Members
...
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
if (serviceType == typeof(ITypeProvider))
{
if (typeProvider == null)
typeProvider = new CustomTypeProvider(this);
return typeProvider;
}
return realContext.GetService(serviceType);
}
#endregion
}
- The custom
ITypeProvider instantiated and returned from the GetService call must give the browser the list of types available in the current project. For that, I took advantage of the VS-provided DynamicTypeService and ITypeDiscoveryService, as explained in my previous post:
private class CustomTypeProvider : ITypeProvider
{
Dictionary<string, Type> availableTypes;
public CustomTypeProvider(IServiceProvider provider)
{
DynamicTypeService typeService =
(DynamicTypeService)provider.GetService(
typeof(DynamicTypeService));
Debug.Assert(typeService != null,
"No dynamic type service registered.");
IVsHierarchy hier = VsHelper.GetCurrentHierarchy(provider);
Debug.Assert(hier != null,
"No active hierarchy is selected.");
ITypeDiscoveryService discovery =
typeService.GetTypeDiscoveryService(hier);
Project dteProject = VsHelper.ToDteProject(hier);
availableTypes = new Dictionary<string, Type>();
foreach (Type type in discovery.GetTypes(typeof(object), false))
{
if (!availableTypes.ContainsKey(type.FullName))
{
if (type.Assembly.GetName().Name !=
(string)dteProject.Properties.Item("AssemblyName").Value)
{
availableTypes.Add(type.FullName, type);
}
else
{
availableTypes.Add(type.FullName,
new ProjectType(type));
}
}
}
}
}
From that point on, all the ITypeProvider members use the availableTypes field for their implementation. The only caveat was that apparently the WF type browser determines that a type belongs to the current project by the presence of a null value in its Assembly property. However, the VS built-in ITypeDiscoveryService gives you fully loaded types, complete with the Assembly property and everything, even for the ones in the current project. Hence, the code above detects if the assembly of a type matches the current, and adds to the list a System.Reflection.TypeDelegator-derived class (ProjectType above) instead of the real type:
if (type.Assembly.GetName().Name !=
(string)dteProject.Properties.Item("AssemblyName").Value)
{
availableTypes.Add(type.FullName, type);
}
else
{
availableTypes.Add(type.FullName, new ProjectType(type));
}
The TypeDelegator class is a not-so-known type that allows a very elegant way to proxy calls to the Type it's delegating to, effectively allowing a kind of reflection interception. This interception is almost transparent, save for the creation of the type delegator with the real Type, as the delegator inherits from Type itself. My delegator is simple, and provides what the WF browser expects: a null value for the Assembly property:
private class ProjectType : TypeDelegator
{
public ProjectType(Type delegatingType) : base(delegatingType) { }
public override Assembly Assembly
{
get { return null; }
}
}
So the relevant members of the ITypeProvider interface take into account this trick as follows:
#region ITypeProvider Members
public Type GetType(string name, bool throwOnError)
{
if (String.IsNullOrEmpty(name))
{
return null;
}
if (availableTypes.ContainsKey(name))
{
Type type = availableTypes[name];
if (type is TypeDelegator)
{
return ((TypeDelegator)type).UnderlyingSystemType;
}
else
{
return type;
}
}
else
{
if (throwOnError)
{
throw new TypeLoadException();
}
else
{
return null;
}
}
}
public Type GetType(string name)
{
return GetType(name, false);
}
public Type[] GetTypes()
{
Type[] result = new Type[availableTypes.Count];
availableTypes.Values.CopyTo(result, 0);
return result;
}
The GetType method is called by the WF browser when you select a type in the listview, and for setting the final return value. So, I achieve the browser requirements for display, but get a fully qualified type name (or the type itself, depending on the type of the property/value being edited) as an output.
With that code in-place, you can simply annotate your component members as follows:
public partial class MyComponent : Component
{
public MyComponent()
{
InitializeComponent();
}
public MyComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
}
private Type someType;
[Editor(typeof(TypeBrowser), typeof(UITypeEditor))]
public Type DataContractType
{
get { return someType; }
set { someType = value; }
}
}
This could also be a property on a user control, of course. With this simple attribute, you can now get the cool WF type editor for your property too:

In the above screenshot, you see:
- A simple Class Library project containing a component that launches the WF type browser (no need for a WF project!!!).
- The component we showed previously in source form, being dropped on a WinForm, and its
DataContractType property having the "..." for launching the editor.
- The editor with the proper value selected (the one that was already set on the property) as well as the property in the property grid reflecting this value (serialization to code works flawlessly).
What you also see above that I have not explained yet is how to filter the dialog. In the screenshot, you see a custom label with the text "Showing types that have DataContractAttribute.", and also from all the types defined in the project or its references, only a few are shown in the treeview/listview. You can achieve this by providing custom filters that implement the ITypeFilterProvider interface. For example, the filter in action above is coded as follows:
public class DataContractFilter : ITypeFilterProvider
{
public DataContractFilter()
{
}
public DataContractFilter(IServiceProvider provider)
{
}
#region ITypeFilterProvider Members
public bool CanFilterType(Type type, bool throwOnError)
{
bool isDataContract = type.IsPublic &&
Attribute.IsDefined(type, typeof(DataContractAttribute));
if (throwOnError && !isDataContract)
{
throw new ArgumentException("Type is not a data contract.");
}
return isDataContract;
}
public string FilterDescription
{
get { return "types that have DataContractAttribute"; }
}
#endregion
}
Finally, you apply the filter to your property with another attribute:
[TypeFilterProvider(typeof(DataContractFilter))]
[Editor(typeof(TypeBrowser), typeof(UITypeEditor))]
public Type DataContractType
It took quite some time and reflecting, but I'm very satisfied with the result :D
Enjoy!
Related readings
History
- 2007-02-12: Updated source code
- 2007-01-31: Updated for .NET 3.0 RTM.
- 2006-10-01: Initial version complete with support for filters.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 23 of 23 (Total in Forum: 23) (Refresh) | FirstPrevNext |
|
|
 |
|
|
This sample is broken badly, and the P&P version that the author is pointing at does not work well as well. However, I was able to take the P&P code and some of the code from this article and got something working that works most of the time.
It will take a bit of time though to get it to work consistenly, and I don't know if I can publish it, because of the licenses.
Is the author thinking about updating his version?
Ruurd Boeke
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Think I found a better solution for GetCurrentHierarchy and your 'hack' to retrieve the IVsHierarchy of a project:
EnvDTE.DTE dte = (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE)); IVsSolution sln = (IVsSolution)Package.GetGlobalService(typeof(SVsSolution)); IVsHierarchy hier; sln.GetProjectOfUniqueName(dte.ActiveDocument.ProjectItem.ContainingProject.UniqueName, out hier);
Turns out you can also retrieve the IVsHierarchy of a project by its UniqueName instead of the GUID, for which you needed a hack to retrieve it by exploring the project file by hand.
Don't know the impact of using Package.GetGlobalService instead of IServiceProvider.GetService. I use the DTE.ActiveDocument instead of the DTE.SelectedItems.Item(1), which appears to be more appropriate to me.
The DTE.ActiveDocument.ProjectItem.ContainingProject seems not to give proper results always, because the ActiveDocument is not necessarily part of the project... Switching back to DTE.SelectedItems 
-- modified at 8:27 Friday 21st September, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
Has anybody got this to work? I'm attempting to combine the code contained in the Web Service Software Factory Guidance Package and the code included in this article and still can't get past the System.Exception problem. I think the error occurs internally when it tries to initialize the dialog.
I'm not 100% familiar with custom editors, but I thought I could figure out what was going wrong.
Is it other people's experience that the updated source code included in this ZIP is working out of the box? Maybe there's a problem on my end.
Thoughts?
Thx, Joel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
So I've pulled out all the code from the WSSF that pertains the TypeBrowser. I then applied the code to your solution updating and adding files as needed to the TypeBrowserLibrary.
However, I'm still getting the DummyDesignerHost created when I try to browse to a type and that eventually calls "GetDesigner" which throws a not implement exception.
Any ideas on how I should proceed?
Thx! Joel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Dan,
It would be greatly appreciated if you could update this sample for the released .Net 3.0.
I know you may be busy, but it would take us much longer to do it, and you could at least make it compile faster than I could.
Thanks...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
The code has been updated, but did you test or run it?
There is absolutely nothing in the Main() function, so the program does not run at all.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The Main function of course does not contain any code. It's NOT supposed to be RUN. The hole point is the *Design-time* experience. The type editor and browser will NEVER work outside VS. The sample contains a component and a control which both use the editor and filter attributes:
[TypeFilterProvider(typeof(ValueTypeFilter))] [Editor(typeof(TypeBrowser), typeof(UITypeEditor))]
Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi, this was just what i was trying to achieve... though unsuccessfully. im having the same problem as Xiangyu.Cao.
one thing i noticed is that you're passing the flyweight twice to the editvalue method of the editor. is this correct?
Thanks anyway
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The newest p&p Web Service Software Factory uses this same editor, so you can see how their code differs from mine and figure out why it doesn't work anymore.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
When I try to edit the DataContractType property of myComponent1 (pressing the "..." button) I receive this error:
Exception of type 'System.Exception' was thrown.
Thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This code was done and tested against WWF beta2, so there may be changes in the RTM version that prevent it from working. Will see if I can find some time to chase it. Alternatively, you can just use the source, debug it, and post a fix
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The code breaks because ContextProxy.GetService returns null for serviceType of WorkflowDesignerLoader.
I've got the 3.0 redistributable installed
ulu
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|