As the ShapeAppAdvancedCSharp sample demonstrates, CodeDom offers a generalized representation of source code that works nicely with VSTA (ie: IVstaProjectHostItem.AddMethod()) and the DTE (ie: DTE.Solution.SolutionBuild.Build()).
With this approach, your macro recorder can generate source for either C# or VB from a single code execution path. Using the CodeDom, you can build up a 'graph' of your new macro that can be easily traversed and modified.
To improve on this example we will refactor the code to create a more specialized, limited set of methods that reflect the unique object syntax and macro recording activity patterns of ShapeApp:
At present, each recorded command is built up with individual, repetitive CodeDom expressions that generate one line of C# source. For example, the following class ShapeColorChangeCommand generates the C# statement:
this.ActiveDrawing.Shapes[0].Color = this.CreateColor(-16744193);
===
internal class ShapeColorChangeCommand : Command
{
private int index;
private Color color;
internal ShapeColorChangeCommand(int index, Color color)
{
this.index = index;
this.color = color;
}
/// <summary>
/// Generates code for the ShapeColorChangeCommand object.
/// This method will generate code of the form:
/// this.ActiveDrawing.Shapes[index].Color = this.CreateColor(argb);
/// </summary>
/// <param name="w">The command writer to write the statement to.</param>
internal override void Save(ICommandWriter w)
{
CodePropertyReferenceExpression drawingRef =
new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "ActiveDrawing");
CodePropertyReferenceExpression shapesRef =
new CodePropertyReferenceExpression(drawingRef, "Shapes");
CodeIndexerExpression shapeRef =
new CodeIndexerExpression(shapesRef, new CodePrimitiveExpression(index));
CodePropertyReferenceExpression colorRef =
new CodePropertyReferenceExpression(shapeRef, "Color");
CodeMethodInvokeExpression createColorStatement =
new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "CreateColor",
new CodePrimitiveExpression(System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B).ToArgb()));
CodeAssignStatement assignStatement =
new CodeAssignStatement(colorRef, createColorStatement);
w.Write(assignStatement);
}
}
===
...this can be dramatically refactored specifically for use in ShapeApp. As an example, we'll reduce ShapeColorChangeCommand.Save() from 7 lines down to 1 line via code reuse:
===
/// <summary>
/// Generates code for the ShapeColorChangeCommand object.
/// This method will generate code of the form:
/// this.ActiveDrawing.Shapes[index].Color = this.CreateColor(argb);
/// </summary>
/// <param name="w">The command writer to write the statement to.</param>
internal override void Save(ICommandWriter w)
{
//refactored to one line!!
w.Write(CommandHelper.AssignColor(
CommandHelper.SelectedShapeExpression(null,index),color));
}
===
...where the CommandHelper methods:
AssignColor() and SelectedShapeBLOCKED EXPRESSION
are reusable for every future macro line where a selected shape or a color is used:
===
public static CodeExpression SelectedShapeExpression(
CodeExpression preFixExpression, int shapeIndex)
{
CodePropertyReferenceExpression drawingRef =
new CodePropertyReferenceExpression(preFixExpression == null ?
new CodeThisReferenceExpression() : preFixExpression, "ActiveDrawing");
CodePropertyReferenceExpression shapesRef =
new CodePropertyReferenceExpression(drawingRef, "Shapes");
CodeIndexerExpression shapeRef =
new CodeIndexerExpression(shapesRef, new CodePrimitiveExpression(shapeIndex));
return shapeRef;
}
public static CodeAssignStatement AssignColor(
CodeExpression preFixExpression, Color color)
{
CodePropertyReferenceExpression colorRef =
new CodePropertyReferenceExpression(preFixExpression == null ?
new CodeThisReferenceExpression() : preFixExpression, "Color");
CodeMethodInvokeExpression createColorStatement =
new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "CreateColor",
new CodePrimitiveExpression(
System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B).ToArgb()));
CodeAssignStatement assignStatement =
new CodeAssignStatement(colorRef, createColorStatement);
return assignStatement;
}
}
===
And finally...
Another possible approach is to add macro text by 'spitting' the source code of your macro, as raw text, into a VB/or C# Code editor:
===
TextDocument txtDoc = this.macroProject.DTE.ActiveDocument as TextDocument;
if(txtDoc.Language == CodeModelLanguageConstants.vsCMLanguageCSharp)
{
txtDoc.StartPoint.CreateEditPoint();
txtDoc.Selection.Insert("//Comment",
(int)vsInsertFlags.vsInsertFlagsCollapseToEnd);
}
===
This is similar to VisualStudio automation
(ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_extvbcs/html/2b46709c-5cef-4ec5-9d8b-e2f6127613f7.htm)
But inserting raw text into a document this way yields a much more brittle macro recorder full of static string constants that are language-specific, and may be more difficult to maintain. Also, text code-spit does not allow for the OO-benefits of codeDom and the textdocument contains only raw text until the document is compiled.