1
\$\begingroup\$

Is there a better way to create Form Controls for properties of a class? Right now, I iterate through all properties of a class and have a method to create the Form Control for that property based on the type of the property.

private void AddControl(PropertyInfo p, string name = null, Color? c = null, int count = 0)
{
    Label newLabel = new Label();
    TableLayoutPanel tp = new TableLayoutPanel();
    Control ctrl = new Control();

    tp.ColumnCount = 2;
    tp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 200F));
    tp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 215F));
    tp.Margin = new Padding(0);
    tp.Name = "tp";
    tp.RowCount = 1;
    tp.RowStyles.Add(new RowStyle(SizeType.Absolute, 28F));
    tp.Size = new System.Drawing.Size(380, 28);
    tp.Location = new Point(0, 14 + num * 28);

    newLabel.Name = "lbl_" + num; 
    newLabel.Text = GetPropertyAttributes(p); // gets a custom attributes for the property
    newLabel.AutoSize = true;
    newLabel.Anchor = AnchorStyles.Left;

    tp.Controls.Add(newLabel);
    ToolTip myToolTip = new ToolTip();

    switch (p.PropertyType.Name)
    {
        case "String":
            ctrl = new TextBox();
            ctrl.Tag = "String";
            ctrl.Size = new System.Drawing.Size(170, 20);
            goto case "common";
        case "Boolean":
            ctrl = new CheckBox();
            goto case "common";
        case "Array[]":
            Console.WriteLine("Array");
            break;
        case "Int32":
            ctrl = new TextBox();
            ctrl.Tag = "Int";
            ctrl.Size = new System.Drawing.Size(40, 20);
            goto case "common";
        case "Double":
            ctrl = new TextBox();
            ctrl.Tag = "Double";
            ctrl.Size = new System.Drawing.Size(40, 20);
            goto case "common";
        case "IList`1":
            if (ClassExists(p.Name)) // ClassExists checks if the property is a class itself
            {
                ctrl = new Button();
                ctrl.Text = "Add";
                ctrl.Tag = p;
                ctrl.AutoSize = true;
                ctrl.Click += new EventHandler(AddButton_Click);
            }
            else
            {
                // TODO create gridview 
            }
            goto case "common";
        case "common":
            ctrl.Anchor = AnchorStyles.Left;
            ctrl.Name = name ?? GetPropertyAttributes(p);
            ctrl.Font = new Font("Consolas", 9);
            ctrl.KeyPress += new KeyPressEventHandler(textBoxKeyPress);
            if (c != null)
            {
                tp.BackColor = c ?? Color.Black;
            }
            tp.Controls.Add(ctrl);
            formControls.Add(tp); // List<Control>
            break;
        default:
            ctrl = new Button();
            ctrl.Text = "Add";
            ctrl.Tag = p;
            ctrl.AutoSize = true;
            ctrl.Click += new EventHandler(AddButton_Click);
            goto case "common";
    }
}

private void AddControls()
{
    foreach (Control ctrl in formControls)
    {
        MainPanel.Controls.Add(ctrl); // MainPanel is a Panel Control
    }
}

Can it be simplified? I may have to add more types to it.

\$\endgroup\$
  • \$\begingroup\$ yes, use xaml and a view model \$\endgroup\$ – Ewan Sep 27 '15 at 17:41
  • \$\begingroup\$ Could you elaborate that a bit more? \$\endgroup\$ – vice_versa Sep 27 '15 at 17:53
  • 2
    \$\begingroup\$ @Ewan ... it's winforms, not WPF. And what's more you should also elaborate \$\endgroup\$ – Robert Snyder Sep 27 '15 at 18:10
  • \$\begingroup\$ I recommend using the Property Grid class. It's a winforms class that does essentially what you are doing. For areas that you need a little more control there is the ability to edit how it shows certain types of values. Here is an example of use c-sharpcorner.com/UploadFile/mgold/… \$\endgroup\$ – Robert Snyder Sep 27 '15 at 18:16
  • 1
    \$\begingroup\$ Thanks for the explanation, I didn't know that WPF is the newer language, I will give it a try, since I have no deadline at all, I have plenty of time to learn :) \$\endgroup\$ – vice_versa Sep 29 '15 at 12:38
2
\$\begingroup\$

In my commented I suggested a class that could do what you want it to do out of the box. However sometimes having a custom piece of code to show things in a better way is the goal. I can see this going in two different ways. One is to use a variation of the Visitor pattern and a list, and the other is to use a dictionary with Type and the key, and the class that represents how to view it as the value.

The first way you could make the IVisitor interface one of two ways.

public interface IVisotor
{
    //option 1
    void Accept(Control control);
    //option 2
    bool Accept(Control control);
}

then to implement you'd make a new class per type that you want to show a specific way. Here is an example with both.

public class StringVisitor : IVisitor
{
    public bool Accept(PropertyInfo property, Control control)
    {
        if(property.PropertyType != typeof(string))
            return false;

        Accept(control);
        return true;
    }

    public void Accept(Control control)
    {
        control = new TextBox();
        control.Tag = "String";
        control.Size = new System.Drawing.Size(170, 20);
    }
}

Now in your class you have two options, make a List<IVisitor> or make a Dictionary<Type, IVisitor>. Here is an example of what I mean. It is by no means complete (and might even have a compiler error as I did it in Notepad++)

public class PropertyViewer
{
    private readonly List<IVisitor> visitors1;
    private readonly Dictionary<Type, IVisitor> visitors2;

    public PropertyViewer()
    {
        visitors1 = new List
        {
            new StringVisitor(),
            //...
        };

        visitors2 = new Dictionary<Type, IVisitor>()
        {
            {String, new StringVisitor()},
            //...
        }
    }
    private void AddControl(PropertyInfo p, string name = null, Color? c = null, int count = 0)
    {
        Control control = null;
        //option 1
        foreach(var visitor in visitors1)
        {
            if(visitor.Accept(p, control))
                break;
        }
        //option 20
        if(visitors2.ContainsKey(p.PropertyType))
        {
            visitors2[p.PropertyType].Accept(control);
        }

        //do your common control stuff here.
    }
}

There are other ways as well but that should encapsulate your work some and make it easier to find where a mistake is. Because of the nature of your work on this it won't be super easy to test that everything is visually pleasing, but the code should be encapsulated enough to make it easy to adjust what you need adjusted.

\$\endgroup\$
  • \$\begingroup\$ Marking your answer as the right one, since it is the best approach \$\endgroup\$ – vice_versa Sep 29 '15 at 7:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.