简体   繁体   中英

Serialize alread-built instance of a WinForms.Form to VB or C# Sourcecode, just like the IDE does

I have about 500 WinForms that are generated dynamically from XML files by an old app. I would like to automatically convert these to VB or C# code I can edit in the designer.

I can do it by hand, which would take forever...

Or write an app that loads each XML, calls the old app's xml-to-winform-builder to convert it to a completed WinForm, and examine every control's property and use StringBuilder to generate code that the designer will swallow. Which would be arduous at best.

BUT: I would like to use the same code that the designer uses. This I think will save me time with corner cases.

What I would like to do is, take an already built WinForm object, with control arrays full of child controls (created by the old apps xml-to-winform code), throw it at the same code the IDE uses to serialize the designed form to CodeDom, and get back a CodeDom list of statements I can save in a real language. Once I get the CodeDOM I'll be happy!

EDIT

This Codeproject article embodies the 'concept' of what I want to do: Start with a completed form, convert it ('serialize it') as code. However, it has two lacks: (1) it generates the .aspx page using templates/stringbuilder, and that is where the properties are (fine for webforms, but winforms serializes the properties into the actual .Designer.vb file); (2) it does this all from the ground up. I want to re-use visual studio's routines. They let you do this with many things, for example, the property grid. I'm just seeking a link to an article (maybe my google-fu was too weak), or, a short code sample from something somebody has already done.

So this may or may not fit your needs. I have a solution but it will require that you tweak your XML -> WinForms rendering.

This relies heavily on the use of this project: http://ivanz.com/files/docs/designerhosting/create-and-host-custom-designers-dot-net.html

Download the EXE (which is just a compressed version of the source with a EULA) and build the solution, I had some issues with building until I removed references to the graphing library (and the associated calls to it from the Host library). Why the Host library needs to draw graphs I'm not really sure...

Then I built a new WinForms project (can't be console because the DesignSurface fails to hook into drag/drop event). Reference the Host and Loader projects from the above project in your new winforms project.

Here is where you will undoubtably need to tweak your existing process. Merge your existing process so that it fits into this type of form building shown for my label below:

        HostSurfaceManager hsm = new HostSurfaceManager();
        HostControl hc = hsm.GetNewHost(typeof(Form), LoaderType.CodeDomDesignerLoader);

        var l = new Label() { Text = "TEST!!!!" };

        hc.DesignerHost.Container.Add(l);

        richTextBox1.Text = ((CodeDomHostLoader)hc.HostSurface.Loader).GetCode("C#");

This generates your form.cs file content (see generated code below). This is an all in one file, you do not need to create a seperate form.cs.designer file to get designer support. I copied the code generated above, saved it to a .cs file, and visual studio recognized it as a winform and gave me design support.

Your copy will probably include a sub Main. I went into the Loader -> CodeGen.cs file and commented out the section related to Main, I would suggest you do the same.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.5448
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace DesignerHostSample
{
    using System;
    using System.ComponentModel;
    using System.Windows.Forms;


    public class Form1 : System.Windows.Forms.Form
    {

        private System.Windows.Forms.Label label1;

        public Form1()
        {
            this.InitializeComponent();
        }

        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // label1
            // 
            this.label1.Location = new System.Drawing.Point(0, 0);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(100, 23);
            this.label1.TabIndex = 0;
            this.label1.Text = "TEST!!!!";
            // 
            // Form1
            // 
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Name = "Form1";
            this.ResumeLayout(false);
        }
    }
}

edit by FastAl

Peter, you rock!!! This is pretty much exactly what I wanted. Well, I can't exactly just throw the object at it yet; using just the above method, none of the .Controls properties were filled, so I got a blank form. Also it seems like the form has to be created by GetNewHost. Fortunately my XMl-to-screen routine doesn't actually create the container, it just returns a flat list of controls that I must properly reparent (SetChildren sub). Note the comment where I have to add them to the host container for it to know about them. NOW it works perfectly!

Public Module Main
    Public Sub Main()
        Dim FormSpecAsText As String = ...  read XML form def from file
        Dim Outfile As String = ... output file is in my project 

        ' Setup for Winforms platform
        Dim dsg As New DynamicScreenGenerator
        Dim ListOfControls As New PanelObjectList
        ControlFactoryLocator.AddService( _
            New PanelObjectFactoryWinFormBasicControls)
        ControlFactoryLocator.AddService(_
            New PanelObjectFactoryWinFormAppSpecificCtls)
        ControlFactoryLocator.AddService(_
            New PanelObjectFactoryWinFormFormEditorCtls)

        ' Deserialize FormSpecAsText into a flat list of Controls
        ListOfControls.AddRange( _
            dsg.BuildDSGLists(FormSpecAsText, ListOfControls).ToArray)

        ' setup for serialization to Code
        Dim hsm As New Host.HostSurfaceManager
        Dim hc As Host.HostControl = _
          hsm.GetNewHost(GetType(Form), Host.LoaderType.CodeDomDesignerLoader)

        ' Get main form that was created via GetNewHost, autosize it
        Dim HostUserControl = _
            CType(hc.DesignerHost.Container.Components(0), Form)

        ' Parent them properly, and add to host (top lvl ctls have parent="")
        SetChildren(HostUserControl, "", dsg, hc.DesignerHost.Container)
        HostUserControl.AutoSize = True

        ' write serialized version to a file in my project
        IO.File.WriteAllText(Outfile, _
          CType(hc.HostSurface.Loader, Loader.CodeDomHostLoader).GetCode("VB"))
    End Sub

    Sub SetChildren(ByVal Parent As Control, ByVal ParentName As String, _
           ByVal dsg As DynamicScreenGenerator, ByVal ctr As IContainer)
        For Each PO In (From p In dsg.POList Where p.Parent = ParentName)
            Dim child = CType(dsg.CTLList(PO), Control)
            ctr.Add(child, PO.Name) ' seem to have to add to container while 
             '  parenting them or .Controls isn't serialized and form is blank.
            Parent.Controls.Add(child)
            SetChildren(child, PO.Name, dsg, ctr)
        Next
    End Sub
End Module

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM