Macaw

Macaw

Sunday, April 8, 2007

WSS 3.0 SPField derrived classes

I've been working with WSS 3.0 for some time now. One of the things I really like is to create my own fieldtypes.
Although the WSS 3.0 SDK has more To Do entries than usable explainations I managed to create my own fieldtypes.
One thing I noticed is that you cannot derrive from the SPFieldLookup. In code you can, but you have to create your own field editor, and there comes the problem. The field editor is the part where you configure the column settings. It's a user control. If you use the normal lookupfieldeditor it won't work, after de-assembling this class I noticed it will always save the field as a SPFieldLookup, and not your custom field.
So I tried to find out, where the lookupfield editor was derrived from. It implements the IFieldEditor. I started working, and coding. And done I was, I thought. It didn't work some of the SPFieldLookup members are made internal; Thank you WSS team.
How did I solve it, I derrived from a different class, the SPFieldMultiChoice field. This worked and now I have my own multi lookup, treeview field.

Below here, some examples, have fun!

My field class derrived from SPFieldMultiChoice:

public class MyLookupField: SPFieldMultiChoice
{
public MyLookupField: (SPFieldCollection fields, string fieldName) : base(fields, fieldName)
{
}

public MyLookupField: (SPFieldCollection fields, string fieldName, string displayName) : base(fields, fieldName, displayName)
{
}

public override void Update()
{
}
}

My field editor class derrived from usercontrol and implemented IFieldEditor (ascx template):

public class MyLookupFieldPropertyEditor: UserControl, IFieldEditor
{
public bool DisplayAsNewSection
{
get { return false; }
}

public void InitializeWithField(SPField field)
{
MyLookupField lookupField = field as MyLookupField;
}

public void OnSaveChange(SPField field, bool isNewField)
{
}

protected override void CreateChildControls()
{
}
}

My lookup field , field control (the field in the edit form) (ascx template):

public class MyLookupFieldControl: BaseFieldControl
{
protected override DefaultTemplateName
{
return "TheNameOfYourAscxTemplate";
}

public override object Value
{
get {}
set
{
SPFieldMultiChoiceValue values = value as SPFieldMultiChoiceValue;
}
}

protected override CreateChildControls()
{
}
}

The xml fieldtypes_*.xml file example
FieldType>
Field Name="TypeName">MyLookupField /Field>
Field Name="TypeDisplayName">My Custom lookup /Field>
Field Name="TypeShortDescription">My Custom lookup fieldtype /Field>
Field Name="ParentType">MultiChoice /Field>
Field Name="UserCreatable">TRUE /Field>
Field Name="FieldTypeClass">My.Assembly.FieldTypes.MyLookupField,My.Asembly,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=223b8b7925bde61b /Field>

Field Name="FieldEditorUserControl">
/_controltemplates/MyLookupFieldPropertyEditor.ascx /Field>
Field Name="Sortable">TRUE /Field>
Field Name="Filterable">TRUE /Field>
/FieldType>
/FieldTypes>

Both ascx template files (just some html) should be placed in the controltemplates directory of the WSS TEMPLATE hive. The assemblies should be placed in the GAC. And a fieldtypes_*.xml should be made to let WSS know you have some other field types.

10 comments:

Anonymous said...

I have the same problem, do you have a fully working example?

Thanks

Anonymous said...

Excellent. Any saurce available?

Beeker said...

I would say that the "saurce" is right up there. Great Post Willem, I was looking for the override object value snippet for a while now. I created a custom field that is a drop down with special values and I coudn't figure out how to save all the values into the field, thanks! the SPFieldMultiChoiceValue did it!

Anonymous said...

It is possible to create a custom field type based on a lookup. There is (imho) a bug (or call it: a severe design problem) with the lookup fields: there are 3 types of lookup fields (see http://msdn.microsoft.com/en-us/library/ms437580.aspx: Lookup, LookupMulti, UserMulti), while there are just 2 classes in code that handle the types. The Lookup and LookupMulti are handled by the same class, which internally changes the Type attribute if the property AllowMultipleValues on the SPFieldLookup is changed.

My feeling is that this is a left over design problem from earlier days, needed to be supported for backward compatibility, so only partially updated but not in a very smart way.

Back to the Lookup field:
If you derive a field on SPFieldLookup, your custom field Type attribute (e.g. "CustomLookup") will be overwritten by either Lookup or LookupMulti.

How does the field creation through the UI work? The page creates your custom field and the custom field editor based on the settings in the fldtypes_xxx.xml. At this moment, your field is not connected to the list yet. The user configures your custom field, and the page then instructs the field editor to save the properties. Then it gets the SchemaXml, and eventually calls the SPFieldCollection.AddFieldAsXml on the list.
At that moment, a new instance of field is created based by the SPFieldCollection. Which field? That is based on the Type attribute, which is overwritten by the AllowMultipleValues property setter. Your custom field class will never be instantiated.

But if you don't touch the buggy property, you'll be fine.

In my case, I needed to have the multi-select stuff on my custom lookup, so I also used inheritance for my field editor: I derived it from the standard field editor, copied the controls definition from the standard ascx into my own, and added my own stuff.

Which made me vulnarable to the SharePoint bug...

Now: on to working around the bug.

My first attempt was to do something smart in the OnSaveChange. But the actual problem is in the property setter. Luckily, this property is marked virtual so I can override it.

I've looked to all the available "legal" options, but there none: the internal data structure (the field definition XML) is "internal", i.e. only to be used by code in the same assembly. OK, time for the box of tricks: Reflection.

My solution:

1. opening the back door to an internal method that allows me to set field attributes (you need to add the System.Reflection namespace):

private void SetFieldAttributeValue_Internal(string attrName, string attrValue)
{
Type baseType = this.GetType().BaseType;
object obj = baseType.InvokeMember(
"SetFieldAttributeValue",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
null, this, new object[] {attrName, attrValue});
}

2. overriding the buggy property

public override bool AllowMultipleValues
{
get
{
return base.AllowMultipleValues;
}
set
{
base.AllowMultipleValues = value;
// Fix SharePoint bug: SPLookupField overwrites the Type
// Set it back to our type
SetFieldAttributeValue_Internal("Type", "MyCustomLookup");
}
}


While this breaks the principles of OO encapsulation, this method has not been changed from WSS2 to WSS3, so I think I'm fairly safe.

Cheers!
Alex

Anonymous said...

good stuff. besides the obvious downside of breaking the oo encapsulation, is there any advantage to creating the cft based on a lookup rather than the multi-choice column?

also, i am still searching but the cfts that i have built do not display their existing custom properties when the column settings are changed (FldEditEx.aspx).

Anonymous said...

FYI... I found a Hot Fix for editing the custom field properties...

http://support.microsoft.com/kb/932055

Vamshi Govind said...

Fantastic Alex,

your suggestion worked like a charm. Thanks a lot for saving my life :)

Peter Holpar said...

The trick from Alex worked me too, thanks for that, but here is the official hotfix for this issue:

Description of the Windows SharePoint Services 3.0 Cumulative Update Server hotfix package (Sts-x-none.msp): December 15, 2009
http://support.microsoft.com/default.aspx/kb/977022/

"You develop a custom field type that inherits from the SPFieldLookUp class. You want to store multiple values in a field of that custom field type. Therefore, you set the AllowMultipleValues property to True. However, after you set the AllowMultipleValues property to True, the field type is displayed as Lookup instead of the custom field type."

Hope that helps.

Peter

Alex Hekstra said...

I'm curious to see what MS changed in the property setter in the hot fix. If only I had some more time...

Alex Hekstra said...

@JLT: I needed a lookup to a list, but not show all values from the remote list. So actually, all I wanted was to be able to manipulate the query that fed the lists in the standard SPFieldLookup field controls.

For this, I needed a custom property on the field, so I needed a custom field, a custom field editor, and a custom field control. The first two I could override, with the mentioned tricks, but the field control I needed to build myself.
That last bit, in the end, turned out to be rather complex. I could not override the field control, only beacuse of poor OO design (at least: design without extensibility in mind).