Exploring CodeDomSerializer
Sometimes we want absolute control over the code that the visual studio designer generates. Imagine that we have a UserControl with a Number property and instead of the default “this.userControl1.Number = 27;” code that the designer would generate we want it like “this.userControl1.Number = 1 + 3 + 23”. In order to achieve this we first have to inform the designer that we want custom serialization. This is done by adding a DesignerSerializerAttribute to our UserControl
[DesignerSerializer(typeof(PrimeSerializer), typeof(CodeDomSerializer))]
public partial class UserControl1 : UserControl
{
private int number;
public int Number
{
get { return this.number; }
set { this.number = value; }
}
// ...
}
And now it’s time to implement the PrimeSerializer for the custom assignment code
public class PrimeSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
UserControl1 uc = value as UserControl1;
if (uc == null) { return null; }
// Instead of implementing all the serialization code, we'll rely on the implementation of the baseclass, namely UserControl
CodeDomSerializer baseClassSerializer = manager.GetSerializer(typeof(UserControl1).BaseType, typeof(CodeDomSerializer)) as CodeDomSerializer;
Object codeObject = baseClassSerializer.Serialize(manager, value);
// The only thing we have to do is find the statement where the assigment to the Number property is made, and replace that...
CodeStatementCollection codeStatements = codeObject as CodeStatementCollection;
CodeAssignStatement numberAssignmentStatement = this.FindNumberCodeStatement(codeStatements) as CodeAssignStatement;
numberAssignmentStatement.Right = new CodeSnippetExpression(GetNumberAsSumOfPrimes(uc.Number));
return codeObject;
}
private CodeStatement FindNumberCodeStatement(CodeStatementCollection codeStatements)
{
foreach (CodeStatement codeStatement in codeStatements)
{
CodeAssignStatement codeAssignment = codeStatement as CodeAssignStatement;
if (codeAssignment != null)
{
CodePropertyReferenceExpression left = codeAssignment.Left as CodePropertyReferenceExpression;
if (left != null && left.PropertyName == "Number")
{
return codeStatement;
}
}
}
throw new Exception("The CodeStatement for Number was not found");
}
private static string GetNumberAsSumOfPrimes(int number)
{
StringBuilder sb = new StringBuilder();
int[] primes = new int[] { 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101 };
for (int i = primes.Length -- 1; i >= 0 && number > 0; --i)
{
if (primes[i] <= number)
{
sb.Insert(0, primes[i]); sb.Insert(0, " + ");
number -= primes[i];
}
}
return sb.ToString().Substring(3);
}
}
And now we can look at the generated code in Form1.Designer.cs to verify everything works as expected
//
// userControl1
//
this.userControl1.BackColor = System.Drawing.Color.Maroon;
this.userControl1.Location = new System.Drawing.Point(50, 23);
this.userControl1.Name = "userControl11";
this.userControl1.Number = 1 + 3 + 23;
this.userControl1.Size = new System.Drawing.Size(686, 294);
this.userControl1.TabIndex = 0;
I’ll leave the implementation of the Deserialize method up to you. By adding the DesignerSerializer attribute to our IExtenderProvider implementations we can get full control over their code generation too 🙂