fancy code generation

Hi all,

I was wondering if it is possible by using an appropriate custom CodeDomSerializer to generate code outside of the InitializeComponent() method.

All the examples I've seen generate a few additional statements inside this method. 

But, for instance can I add another instance variable to the form/control   Or add a new method 

thanks,

Bert


Answer this question

fancy code generation

  • jdeguenther

    Thanks for the info about CodeTypeDeclaration (so many unknown services...). I would agree that most people who want to do this don't necessarily need the extra code generation. However it can be useful. For example, we have a root designer that generates a nested helper class that is used at run-time to bind custom expressions to objects. Without the helper class we would have to use a lot of boxing and an interpreter. So it can be very useful sometimes. Luckily our generated code is completely tied to string properties, so it changes every time a property changes, and we don't have to parse it at all.

    The root designer is used for real-time data binding, alarms, etc. 

  • StephenBarclay

    Some of these are undocumented interfaces or Types; you might want to be careful using them.
  • ___AUTUMNS_ENDING___

    I experimented with this idea before. It is possible to add whatever members you want during the code generation. The problem is the deserialization. You don't get access to any code outside InitializeComponents, so you have to either store the data somewhere else, which kind of defeats the purpose, or you have to find another way to parse the code. The other problem is the designer transactions. If the generated code is not part of InitializeComponents and some components specifically, the undo/redo engine will not be able to undo/redo changes to your code, because they do not represent changes to properties. If you tie the code generation to some properties, even design-time only properties, that problem is fixed.

    It is possible to use the DTE and the code model to get some info instead of the deserializer. You can do whatever you want this way, but you have to synchronize it with the codedomserializer yourself. Anyway, I wrote some code to inject a member into a class using the DTE. Here it is:


      internal class CodeInjector
      {
        public static void InsertCode(DTE dte, IDesignerHost host, string code)
        {
          Document ourDoc = FindOurDocument(dte, host);
          //      OpenCodeDoc(dte, ourDoc);  // may not be necessary
          // find our class
          string className = host.RootComponentClassName;
          FileCodeModel cm = ourDoc.ProjectItem.FileCodeModel;
          CodeClass cc = null;
          foreach (CodeElement ce in cm.CodeElements)
          {
            cc = FindClass(ce, className);
            if (cc != null)
              break;
          }
          if (cc != null)
          {
            // first create codedom
            CodeCommentStatement comment = new CodeCommentStatement(code);
            // cannot generate code from a method -- have to use a type, or
            // hope the method is already there

            // generate a string from the codedom
            CodeDomProvider provider;
            if (ourDoc.Language == "CSharp")
              provider = new Microsoft.CSharp.CSharpCodeProvider();
            else if (ourDoc.Language == "VB")
              provider = new Microsoft.VisualBasic.VBCodeProvider();
            else
              return;
            ICodeGenerator generator = provider.CreateGenerator();
            StringBuilder sb = new StringBuilder();
            TextWriter writer = new IndentedTextWriter(new StringWriter(sb));
            generator.GenerateCodeFromStatement(comment, writer, null);
            writer.Flush();
            
            // if the method already exists, delete body
            EditPoint start = null;
            foreach (CodeElement ce in cc.Members)
            {
              CodeFunction cf = ce as CodeFunction;
              if (cf != null && cf.Name == "test")
              {
                start  = cf.StartPoint.CreateEditPoint();
                start.LineDown(2);
                start.StartOfLine();
                EditPoint end  = cf.EndPoint.CreateEditPoint();
                end.LineUp(1);
                end.EndOfLine();
                start.Delete(end);
                break;
              }
            }

            if (start == null)
            {
              if (ourDoc.Language == "CSharp")
              {
                CodeFunction cf = cc.AddFunction("test", vsCMFunction.vsCMFunctionFunction,
                  vsCMTypeRef.vsCMTypeRefInt, -1, vsCMAccess.vsCMAccessPrivate, null);
                start = cf.StartPoint.CreateEditPoint();
                start.LineDown(2);
                start.StartOfLine();
              }
              else
              {
                // TODO: here error message
              }
            }

            // insert the string into the function
            start.Insert(sb.ToString());
          }
        }

        private static CodeClass FindClass(CodeElement root, string className)
        {
          CodeClass cc = root as CodeClass;
          if (cc != null)
          {
            if (cc.FullName == className)
              return cc;
            foreach (CodeElement ce in cc.Members)
            {
              if (ce is EnvDTE.CodeNamespace || ce is CodeClass)
              {
                cc = FindClass(ce, className);
                if (cc != null)
                  return cc;
              }
            }
          }
          else
          {
            EnvDTE.CodeNamespace cn = root as EnvDTE.CodeNamespace;
            if (cn != null)
            {
              foreach (CodeElement ce in cn.Members)
              {
                if (ce is EnvDTE.CodeNamespace || ce is CodeClass)
                {
                  cc = FindClass(ce, className);
                  if (cc != null)
                    return cc;
                }
              }
            }
          }
          return null;
        }

        private static Document FindOurDocument(DTE dte, IDesignerHost host)
        {
          foreach (Document doc in dte.Documents)
          {
            if (doc.Kind != Constants.vsDocumentKindText)
              continue;
            foreach (Window window in doc.Windows)
            {
              IDesignerHost windowHost = window.Object as IDesignerHost;
              if (windowHost != null && windowHost == host)
                return doc;
            }
          }
          return null;
        }

        private static void OpenCodeDoc(DTE dte, Document document)
        {
          bool codeOpen = dte.get_IsOpenFile(Constants.vsViewKindCode, document.FullName);
          if (!codeOpen)
          {
            ItemOperations ItemOp = dte.ItemOperations;
            ItemOp.OpenFile(document.FullName, Constants.vsViewKindCode);
          }
        }  
      }

  • kanand

    Agreed - I've discovered several methods that are not visible via intellisense, but are public... I've found them in many places in the framework, and haven't figured out exactly why they are hidden, even from class browser.
  • AndrewKnight

    There are a couple in System.Windows.Forms.Design, but most are in ComponentModel.Design. The interesting thing about CodeTypeDeclaration: it is not an interface, but a class. So I suppose the only way to figure it out is to call getservice and see if it works. You have to know what you need, and what might work. IServiceContainer has no enumerator.
  • Nora6207

    Have you seen the article <a href="http://msdn.microsoft.com/library/default.asp url=/library/en-us/dndotnet/html/custcodegen.asp frame=true">here</a>
  • Andreas Sieber

    You can GetService for the CodeTypeDeclaration and then party on that all you like.  That type declaration is the same one the code generator uses when it generates its own code.  However, there are two big warnings I want to give to anyone who wants to deviate far from the standard code generator:

    First, be wary that many languages can generate richer code than they can parse.  To date, only Managed C++ can parse everthing it generates in the code dom.  So, if you try to get too fancy, you could get errors reparsing your code.  Also, parsing code is done on demand, so the more methods you add the slower you will make the designer.

    Second, don't assume that we will forever have a single method called InitializeComponent that stores all of our settings.  You'll notice that it's hard to find that method today (you can actually check the CotextStack in your serializer for CodeMemberMethod to find out what method you're in), and we did that on purpose.  It's possible (although we're not currently planning on doing it) that we may find that a huge method doesn't JIT as efficiently as several smaller ones, so we could*space
    space*it up for runtime performance reasons.

    Even internally we have a lot of people who want to change the way code is generated.  It's a natural thing to want to do, but it does have pitfalls down the road you may not see.  Most of the time, when I see people doing custom code generation, it's because they really want an object model that is different from what Windows Forms currently offers.  Often that's not the best choice for customers who are having enough trouble adapting to the new world of Windows Forms -- here, consistency can be a good thing.

  • Christian Kuendig

    Interesting information...

    I also agree that there are so many unknown services available for consumption at design time... 

    I have figured out many available services by going through the MSDN documentation for the ComponentModel (and nested) namespace, but keep coming accross other "undocumented" services that are available, and useful.

    Anyone know of a place / mechanism to "discover" the services that are available

  • lennon60000

    I've been looking for this for a while, and only minutes after posting to this thread, I found a site (http://www.livejournal.com/users/zerorange/) that had a nicely packaged list of available services for Sited components.

    IComponentChangeService, IConfigurationService, IContainer, IDesignerHost, IDesignerLoaderService, IDesignerSerializationManager, IDesignerSerializationService, IEventBindingService, IEventHandlerService, IExtenderProviderService, IHelpService, IInheritanceService, ILicenseReaderWriterService, IMenuCommandService, IMenuEditorService, INameCreationService, IOleCommandTarget, IOverlayService, IPropertyValueUIService, IReferenceService, IResourceService, ISelectionService, ISelectionUIService, ISplitWindowService, ITypeDescriptorFilterService, ITypeResolutionService, CodeDomProvider, CodeTypeDeclaration, ComponentTray, InheritanceUI, ManagedPropertiesService, ProjectItem, SerializationResourceManager

    Many thanks to "Zero Range" for posting this list.

  • Aw Ali

    Yes I did read the article.

    The fanciest piece of code generation it does is described (at the end of the article) in the snippet below.

    As you can see, all it does is insert a statement at the beginning and one at the end of the CodeStatementCollection that makes up (I guess) the code of the InitializeComponent method.

    But that is not what I want. I want to generate code OUTSIDE of the InitalizeComponent method. If I want to generate for instance the definition of a new instance variable, I can't do that in the InitializeComponent method for obvious reasons.


    --- excerpt from article ----

    public override object Serialize(
    IDesignerSerializationManager manager, 
    object value) 
    {
    // first, locate and invoke the default serializer for 
    // the ButtonArray's  base class (UserControl)
    //
    CodeDomSerializer baseSerializer = 
    (CodeDomSerializer)manager.GetSerializer(
    typeof(ButtonArray).BaseType, 
    typeof(CodeDomSerializer));

       object codeObject = baseSerializer.Serialize(manager, value);

       // now add some custom code
       //
       if (codeObject is CodeStatementCollection) 
       {

          // add a custom comment to the code.
          //
          CodeStatementCollection statements = 
    (CodeStatementCollection)codeObject;   
          statements.Insert(0, 
    new CodeCommentStatement(
    "This is a custom comment added by a custom serializer on " + 
                   DateTime.Now.ToLongDateString()));

          // call a custom method.
          //
          CodeExpression targetObject = 
             base.SerializeToReferenceExpression(manager, value);
             if (targetObject != null) 
             {
                CodeMethodInvokeExpression methodCall = 
    new CodeMethodInvokeExpression(targetObject, "CustomMethod");
                statements.Add(methodCall);
             }
             
          }

          // finally, return the statements that have been created
          return codeObject;
       }
    }

    --- end ---

    Bert

  • fancy code generation